diff options
| author | 3gg <3gg@shellblade.net> | 2025-12-27 12:03:39 -0800 |
|---|---|---|
| committer | 3gg <3gg@shellblade.net> | 2025-12-27 12:03:39 -0800 |
| commit | 5a079a2d114f96d4847d1ee305d5b7c16eeec50e (patch) | |
| tree | 8926ab44f168acf787d8e19608857b3af0f82758 /contrib/SDL-3.2.8/test/testcontroller.c | |
Initial commit
Diffstat (limited to 'contrib/SDL-3.2.8/test/testcontroller.c')
| -rw-r--r-- | contrib/SDL-3.2.8/test/testcontroller.c | 2243 |
1 files changed, 2243 insertions, 0 deletions
diff --git a/contrib/SDL-3.2.8/test/testcontroller.c b/contrib/SDL-3.2.8/test/testcontroller.c new file mode 100644 index 0000000..b717b30 --- /dev/null +++ b/contrib/SDL-3.2.8/test/testcontroller.c | |||
| @@ -0,0 +1,2243 @@ | |||
| 1 | /* | ||
| 2 | Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org> | ||
| 3 | |||
| 4 | This software is provided 'as-is', without any express or implied | ||
| 5 | warranty. In no event will the authors be held liable for any damages | ||
| 6 | arising from the use of this software. | ||
| 7 | |||
| 8 | Permission is granted to anyone to use this software for any purpose, | ||
| 9 | including commercial applications, and to alter it and redistribute it | ||
| 10 | freely. | ||
| 11 | */ | ||
| 12 | |||
| 13 | /* Simple program to test the SDL controller routines */ | ||
| 14 | |||
| 15 | #define SDL_MAIN_USE_CALLBACKS | ||
| 16 | #include <SDL3/SDL.h> | ||
| 17 | #include <SDL3/SDL_main.h> | ||
| 18 | #include <SDL3/SDL_test.h> | ||
| 19 | #include <SDL3/SDL_test_font.h> | ||
| 20 | |||
| 21 | #ifdef SDL_PLATFORM_EMSCRIPTEN | ||
| 22 | #include <emscripten/emscripten.h> | ||
| 23 | #endif | ||
| 24 | |||
| 25 | #include "gamepadutils.h" | ||
| 26 | #include "testutils.h" | ||
| 27 | |||
| 28 | #if 0 | ||
| 29 | #define DEBUG_AXIS_MAPPING | ||
| 30 | #endif | ||
| 31 | |||
| 32 | #define TITLE_HEIGHT 48.0f | ||
| 33 | #define PANEL_SPACING 25.0f | ||
| 34 | #define PANEL_WIDTH 250.0f | ||
| 35 | #define MINIMUM_BUTTON_WIDTH 96.0f | ||
| 36 | #define BUTTON_MARGIN 16.0f | ||
| 37 | #define BUTTON_PADDING 12.0f | ||
| 38 | #define GAMEPAD_WIDTH 512.0f | ||
| 39 | #define GAMEPAD_HEIGHT 560.0f | ||
| 40 | |||
| 41 | #define SCREEN_WIDTH (PANEL_WIDTH + PANEL_SPACING + GAMEPAD_WIDTH + PANEL_SPACING + PANEL_WIDTH) | ||
| 42 | #define SCREEN_HEIGHT (TITLE_HEIGHT + GAMEPAD_HEIGHT) | ||
| 43 | |||
| 44 | typedef struct | ||
| 45 | { | ||
| 46 | bool m_bMoving; | ||
| 47 | int m_nLastValue; | ||
| 48 | int m_nStartingValue; | ||
| 49 | int m_nFarthestValue; | ||
| 50 | } AxisState; | ||
| 51 | |||
| 52 | typedef struct | ||
| 53 | { | ||
| 54 | SDL_JoystickID id; | ||
| 55 | |||
| 56 | SDL_Joystick *joystick; | ||
| 57 | int num_axes; | ||
| 58 | AxisState *axis_state; | ||
| 59 | |||
| 60 | SDL_Gamepad *gamepad; | ||
| 61 | char *mapping; | ||
| 62 | bool has_bindings; | ||
| 63 | |||
| 64 | int audio_route; | ||
| 65 | int trigger_effect; | ||
| 66 | } Controller; | ||
| 67 | |||
| 68 | static SDLTest_CommonState *state; | ||
| 69 | static SDL_Window *window = NULL; | ||
| 70 | static SDL_Renderer *screen = NULL; | ||
| 71 | static ControllerDisplayMode display_mode = CONTROLLER_MODE_TESTING; | ||
| 72 | static GamepadImage *image = NULL; | ||
| 73 | static GamepadDisplay *gamepad_elements = NULL; | ||
| 74 | static GamepadTypeDisplay *gamepad_type = NULL; | ||
| 75 | static JoystickDisplay *joystick_elements = NULL; | ||
| 76 | static GamepadButton *setup_mapping_button = NULL; | ||
| 77 | static GamepadButton *done_mapping_button = NULL; | ||
| 78 | static GamepadButton *cancel_button = NULL; | ||
| 79 | static GamepadButton *clear_button = NULL; | ||
| 80 | static GamepadButton *copy_button = NULL; | ||
| 81 | static GamepadButton *paste_button = NULL; | ||
| 82 | static char *backup_mapping = NULL; | ||
| 83 | static bool done = false; | ||
| 84 | static bool set_LED = false; | ||
| 85 | static int num_controllers = 0; | ||
| 86 | static Controller *controllers; | ||
| 87 | static Controller *controller; | ||
| 88 | static SDL_JoystickID mapping_controller = 0; | ||
| 89 | static int binding_element = SDL_GAMEPAD_ELEMENT_INVALID; | ||
| 90 | static int last_binding_element = SDL_GAMEPAD_ELEMENT_INVALID; | ||
| 91 | static bool binding_flow = false; | ||
| 92 | static int binding_flow_direction = 0; | ||
| 93 | static Uint64 binding_advance_time = 0; | ||
| 94 | static SDL_FRect title_area; | ||
| 95 | static bool title_highlighted; | ||
| 96 | static bool title_pressed; | ||
| 97 | static SDL_FRect type_area; | ||
| 98 | static bool type_highlighted; | ||
| 99 | static bool type_pressed; | ||
| 100 | static char *controller_name; | ||
| 101 | static SDL_Joystick *virtual_joystick = NULL; | ||
| 102 | static SDL_GamepadAxis virtual_axis_active = SDL_GAMEPAD_AXIS_INVALID; | ||
| 103 | static float virtual_axis_start_x; | ||
| 104 | static float virtual_axis_start_y; | ||
| 105 | static SDL_GamepadButton virtual_button_active = SDL_GAMEPAD_BUTTON_INVALID; | ||
| 106 | static bool virtual_touchpad_active = false; | ||
| 107 | static float virtual_touchpad_x; | ||
| 108 | static float virtual_touchpad_y; | ||
| 109 | |||
| 110 | static int s_arrBindingOrder[] = { | ||
| 111 | /* Standard sequence */ | ||
| 112 | SDL_GAMEPAD_BUTTON_SOUTH, | ||
| 113 | SDL_GAMEPAD_BUTTON_EAST, | ||
| 114 | SDL_GAMEPAD_BUTTON_WEST, | ||
| 115 | SDL_GAMEPAD_BUTTON_NORTH, | ||
| 116 | SDL_GAMEPAD_BUTTON_DPAD_LEFT, | ||
| 117 | SDL_GAMEPAD_BUTTON_DPAD_RIGHT, | ||
| 118 | SDL_GAMEPAD_BUTTON_DPAD_UP, | ||
| 119 | SDL_GAMEPAD_BUTTON_DPAD_DOWN, | ||
| 120 | SDL_GAMEPAD_ELEMENT_AXIS_LEFTX_NEGATIVE, | ||
| 121 | SDL_GAMEPAD_ELEMENT_AXIS_LEFTX_POSITIVE, | ||
| 122 | SDL_GAMEPAD_ELEMENT_AXIS_LEFTY_NEGATIVE, | ||
| 123 | SDL_GAMEPAD_ELEMENT_AXIS_LEFTY_POSITIVE, | ||
| 124 | SDL_GAMEPAD_BUTTON_LEFT_STICK, | ||
| 125 | SDL_GAMEPAD_ELEMENT_AXIS_RIGHTX_NEGATIVE, | ||
| 126 | SDL_GAMEPAD_ELEMENT_AXIS_RIGHTX_POSITIVE, | ||
| 127 | SDL_GAMEPAD_ELEMENT_AXIS_RIGHTY_NEGATIVE, | ||
| 128 | SDL_GAMEPAD_ELEMENT_AXIS_RIGHTY_POSITIVE, | ||
| 129 | SDL_GAMEPAD_BUTTON_RIGHT_STICK, | ||
| 130 | SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, | ||
| 131 | SDL_GAMEPAD_ELEMENT_AXIS_LEFT_TRIGGER, | ||
| 132 | SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, | ||
| 133 | SDL_GAMEPAD_ELEMENT_AXIS_RIGHT_TRIGGER, | ||
| 134 | SDL_GAMEPAD_BUTTON_BACK, | ||
| 135 | SDL_GAMEPAD_BUTTON_START, | ||
| 136 | SDL_GAMEPAD_BUTTON_GUIDE, | ||
| 137 | SDL_GAMEPAD_BUTTON_MISC1, | ||
| 138 | SDL_GAMEPAD_ELEMENT_INVALID, | ||
| 139 | |||
| 140 | /* Paddle sequence */ | ||
| 141 | SDL_GAMEPAD_BUTTON_RIGHT_PADDLE1, | ||
| 142 | SDL_GAMEPAD_BUTTON_LEFT_PADDLE1, | ||
| 143 | SDL_GAMEPAD_BUTTON_RIGHT_PADDLE2, | ||
| 144 | SDL_GAMEPAD_BUTTON_LEFT_PADDLE2, | ||
| 145 | SDL_GAMEPAD_ELEMENT_INVALID, | ||
| 146 | }; | ||
| 147 | |||
| 148 | |||
| 149 | static const char *GetSensorName(SDL_SensorType sensor) | ||
| 150 | { | ||
| 151 | switch (sensor) { | ||
| 152 | case SDL_SENSOR_ACCEL: | ||
| 153 | return "accelerometer"; | ||
| 154 | case SDL_SENSOR_GYRO: | ||
| 155 | return "gyro"; | ||
| 156 | case SDL_SENSOR_ACCEL_L: | ||
| 157 | return "accelerometer (L)"; | ||
| 158 | case SDL_SENSOR_GYRO_L: | ||
| 159 | return "gyro (L)"; | ||
| 160 | case SDL_SENSOR_ACCEL_R: | ||
| 161 | return "accelerometer (R)"; | ||
| 162 | case SDL_SENSOR_GYRO_R: | ||
| 163 | return "gyro (R)"; | ||
| 164 | default: | ||
| 165 | return "UNKNOWN"; | ||
| 166 | } | ||
| 167 | } | ||
| 168 | |||
| 169 | /* PS5 trigger effect documentation: | ||
| 170 | https://controllers.fandom.com/wiki/Sony_DualSense#FFB_Trigger_Modes | ||
| 171 | */ | ||
| 172 | typedef struct | ||
| 173 | { | ||
| 174 | Uint8 ucEnableBits1; /* 0 */ | ||
| 175 | Uint8 ucEnableBits2; /* 1 */ | ||
| 176 | Uint8 ucRumbleRight; /* 2 */ | ||
| 177 | Uint8 ucRumbleLeft; /* 3 */ | ||
| 178 | Uint8 ucHeadphoneVolume; /* 4 */ | ||
| 179 | Uint8 ucSpeakerVolume; /* 5 */ | ||
| 180 | Uint8 ucMicrophoneVolume; /* 6 */ | ||
| 181 | Uint8 ucAudioEnableBits; /* 7 */ | ||
| 182 | Uint8 ucMicLightMode; /* 8 */ | ||
| 183 | Uint8 ucAudioMuteBits; /* 9 */ | ||
| 184 | Uint8 rgucRightTriggerEffect[11]; /* 10 */ | ||
| 185 | Uint8 rgucLeftTriggerEffect[11]; /* 21 */ | ||
| 186 | Uint8 rgucUnknown1[6]; /* 32 */ | ||
| 187 | Uint8 ucLedFlags; /* 38 */ | ||
| 188 | Uint8 rgucUnknown2[2]; /* 39 */ | ||
| 189 | Uint8 ucLedAnim; /* 41 */ | ||
| 190 | Uint8 ucLedBrightness; /* 42 */ | ||
| 191 | Uint8 ucPadLights; /* 43 */ | ||
| 192 | Uint8 ucLedRed; /* 44 */ | ||
| 193 | Uint8 ucLedGreen; /* 45 */ | ||
| 194 | Uint8 ucLedBlue; /* 46 */ | ||
| 195 | } DS5EffectsState_t; | ||
| 196 | |||
| 197 | static void CyclePS5AudioRoute(Controller *device) | ||
| 198 | { | ||
| 199 | DS5EffectsState_t effects; | ||
| 200 | |||
| 201 | device->audio_route = (device->audio_route + 1) % 4; | ||
| 202 | |||
| 203 | SDL_zero(effects); | ||
| 204 | switch (device->audio_route) { | ||
| 205 | case 0: | ||
| 206 | /* Audio disabled */ | ||
| 207 | effects.ucEnableBits1 |= (0x80 | 0x20 | 0x10); /* Modify audio route and speaker / headphone volume */ | ||
| 208 | effects.ucSpeakerVolume = 0; /* Minimum volume */ | ||
| 209 | effects.ucHeadphoneVolume = 0; /* Minimum volume */ | ||
| 210 | effects.ucAudioEnableBits = 0x00; /* Output to headphones */ | ||
| 211 | break; | ||
| 212 | case 1: | ||
| 213 | /* Headphones */ | ||
| 214 | effects.ucEnableBits1 |= (0x80 | 0x10); /* Modify audio route and headphone volume */ | ||
| 215 | effects.ucHeadphoneVolume = 50; /* 50% volume - don't blast into the ears */ | ||
| 216 | effects.ucAudioEnableBits = 0x00; /* Output to headphones */ | ||
| 217 | break; | ||
| 218 | case 2: | ||
| 219 | /* Speaker */ | ||
| 220 | effects.ucEnableBits1 |= (0x80 | 0x20); /* Modify audio route and speaker volume */ | ||
| 221 | effects.ucSpeakerVolume = 100; /* Maximum volume */ | ||
| 222 | effects.ucAudioEnableBits = 0x30; /* Output to speaker */ | ||
| 223 | break; | ||
| 224 | case 3: | ||
| 225 | /* Both */ | ||
| 226 | effects.ucEnableBits1 |= (0x80 | 0x20 | 0x10); /* Modify audio route and speaker / headphone volume */ | ||
| 227 | effects.ucSpeakerVolume = 100; /* Maximum volume */ | ||
| 228 | effects.ucHeadphoneVolume = 50; /* 50% volume - don't blast into the ears */ | ||
| 229 | effects.ucAudioEnableBits = 0x20; /* Output to both speaker and headphones */ | ||
| 230 | break; | ||
| 231 | } | ||
| 232 | SDL_SendGamepadEffect(device->gamepad, &effects, sizeof(effects)); | ||
| 233 | } | ||
| 234 | |||
| 235 | static void CyclePS5TriggerEffect(Controller *device) | ||
| 236 | { | ||
| 237 | DS5EffectsState_t effects; | ||
| 238 | |||
| 239 | Uint8 trigger_effects[3][11] = { | ||
| 240 | /* Clear trigger effect */ | ||
| 241 | { 0x05, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, | ||
| 242 | /* Constant resistance across entire trigger pull */ | ||
| 243 | { 0x01, 0, 110, 0, 0, 0, 0, 0, 0, 0, 0 }, | ||
| 244 | /* Resistance and vibration when trigger is pulled */ | ||
| 245 | { 0x06, 15, 63, 128, 0, 0, 0, 0, 0, 0, 0 }, | ||
| 246 | }; | ||
| 247 | |||
| 248 | device->trigger_effect = (device->trigger_effect + 1) % SDL_arraysize(trigger_effects); | ||
| 249 | |||
| 250 | SDL_zero(effects); | ||
| 251 | effects.ucEnableBits1 |= (0x04 | 0x08); /* Modify right and left trigger effect respectively */ | ||
| 252 | SDL_memcpy(effects.rgucRightTriggerEffect, trigger_effects[device->trigger_effect], sizeof(trigger_effects[0])); | ||
| 253 | SDL_memcpy(effects.rgucLeftTriggerEffect, trigger_effects[device->trigger_effect], sizeof(trigger_effects[0])); | ||
| 254 | SDL_SendGamepadEffect(device->gamepad, &effects, sizeof(effects)); | ||
| 255 | } | ||
| 256 | |||
| 257 | static void ClearButtonHighlights(void) | ||
| 258 | { | ||
| 259 | title_highlighted = false; | ||
| 260 | title_pressed = false; | ||
| 261 | |||
| 262 | type_highlighted = false; | ||
| 263 | type_pressed = false; | ||
| 264 | |||
| 265 | ClearGamepadImage(image); | ||
| 266 | SetGamepadDisplayHighlight(gamepad_elements, SDL_GAMEPAD_ELEMENT_INVALID, false); | ||
| 267 | SetGamepadTypeDisplayHighlight(gamepad_type, SDL_GAMEPAD_TYPE_UNSELECTED, false); | ||
| 268 | SetGamepadButtonHighlight(setup_mapping_button, false, false); | ||
| 269 | SetGamepadButtonHighlight(done_mapping_button, false, false); | ||
| 270 | SetGamepadButtonHighlight(cancel_button, false, false); | ||
| 271 | SetGamepadButtonHighlight(clear_button, false, false); | ||
| 272 | SetGamepadButtonHighlight(copy_button, false, false); | ||
| 273 | SetGamepadButtonHighlight(paste_button, false, false); | ||
| 274 | } | ||
| 275 | |||
| 276 | static void UpdateButtonHighlights(float x, float y, bool button_down) | ||
| 277 | { | ||
| 278 | ClearButtonHighlights(); | ||
| 279 | |||
| 280 | if (display_mode == CONTROLLER_MODE_TESTING) { | ||
| 281 | SetGamepadButtonHighlight(setup_mapping_button, GamepadButtonContains(setup_mapping_button, x, y), button_down); | ||
| 282 | } else if (display_mode == CONTROLLER_MODE_BINDING) { | ||
| 283 | SDL_FPoint point; | ||
| 284 | int gamepad_highlight_element = SDL_GAMEPAD_ELEMENT_INVALID; | ||
| 285 | char *joystick_highlight_element; | ||
| 286 | |||
| 287 | point.x = x; | ||
| 288 | point.y = y; | ||
| 289 | if (SDL_PointInRectFloat(&point, &title_area)) { | ||
| 290 | title_highlighted = true; | ||
| 291 | title_pressed = button_down; | ||
| 292 | } else { | ||
| 293 | title_highlighted = false; | ||
| 294 | title_pressed = false; | ||
| 295 | } | ||
| 296 | |||
| 297 | if (SDL_PointInRectFloat(&point, &type_area)) { | ||
| 298 | type_highlighted = true; | ||
| 299 | type_pressed = button_down; | ||
| 300 | } else { | ||
| 301 | type_highlighted = false; | ||
| 302 | type_pressed = false; | ||
| 303 | } | ||
| 304 | |||
| 305 | if (controller->joystick != virtual_joystick) { | ||
| 306 | gamepad_highlight_element = GetGamepadImageElementAt(image, x, y); | ||
| 307 | } | ||
| 308 | if (gamepad_highlight_element == SDL_GAMEPAD_ELEMENT_INVALID) { | ||
| 309 | gamepad_highlight_element = GetGamepadDisplayElementAt(gamepad_elements, controller->gamepad, x, y); | ||
| 310 | } | ||
| 311 | SetGamepadDisplayHighlight(gamepad_elements, gamepad_highlight_element, button_down); | ||
| 312 | |||
| 313 | if (binding_element == SDL_GAMEPAD_ELEMENT_TYPE) { | ||
| 314 | int gamepad_highlight_type = GetGamepadTypeDisplayAt(gamepad_type, x, y); | ||
| 315 | SetGamepadTypeDisplayHighlight(gamepad_type, gamepad_highlight_type, button_down); | ||
| 316 | } | ||
| 317 | |||
| 318 | joystick_highlight_element = GetJoystickDisplayElementAt(joystick_elements, controller->joystick, x, y); | ||
| 319 | SetJoystickDisplayHighlight(joystick_elements, joystick_highlight_element, button_down); | ||
| 320 | SDL_free(joystick_highlight_element); | ||
| 321 | |||
| 322 | SetGamepadButtonHighlight(done_mapping_button, GamepadButtonContains(done_mapping_button, x, y), button_down); | ||
| 323 | SetGamepadButtonHighlight(cancel_button, GamepadButtonContains(cancel_button, x, y), button_down); | ||
| 324 | SetGamepadButtonHighlight(clear_button, GamepadButtonContains(clear_button, x, y), button_down); | ||
| 325 | SetGamepadButtonHighlight(copy_button, GamepadButtonContains(copy_button, x, y), button_down); | ||
| 326 | SetGamepadButtonHighlight(paste_button, GamepadButtonContains(paste_button, x, y), button_down); | ||
| 327 | } | ||
| 328 | } | ||
| 329 | |||
| 330 | static int StandardizeAxisValue(int nValue) | ||
| 331 | { | ||
| 332 | if (nValue > SDL_JOYSTICK_AXIS_MAX / 2) { | ||
| 333 | return SDL_JOYSTICK_AXIS_MAX; | ||
| 334 | } else if (nValue < SDL_JOYSTICK_AXIS_MIN / 2) { | ||
| 335 | return SDL_JOYSTICK_AXIS_MIN; | ||
| 336 | } else { | ||
| 337 | return 0; | ||
| 338 | } | ||
| 339 | } | ||
| 340 | |||
| 341 | static void RefreshControllerName(void) | ||
| 342 | { | ||
| 343 | const char *name = NULL; | ||
| 344 | |||
| 345 | SDL_free(controller_name); | ||
| 346 | controller_name = NULL; | ||
| 347 | |||
| 348 | if (controller) { | ||
| 349 | if (controller->gamepad) { | ||
| 350 | name = SDL_GetGamepadName(controller->gamepad); | ||
| 351 | } else { | ||
| 352 | name = SDL_GetJoystickName(controller->joystick); | ||
| 353 | } | ||
| 354 | } | ||
| 355 | |||
| 356 | if (name) { | ||
| 357 | controller_name = SDL_strdup(name); | ||
| 358 | } else { | ||
| 359 | controller_name = SDL_strdup(""); | ||
| 360 | } | ||
| 361 | } | ||
| 362 | |||
| 363 | static void SetAndFreeGamepadMapping(char *mapping) | ||
| 364 | { | ||
| 365 | SDL_SetGamepadMapping(controller->id, mapping); | ||
| 366 | SDL_free(mapping); | ||
| 367 | } | ||
| 368 | |||
| 369 | static void SetCurrentBindingElement(int element, bool flow) | ||
| 370 | { | ||
| 371 | int i; | ||
| 372 | |||
| 373 | if (binding_element == SDL_GAMEPAD_ELEMENT_NAME) { | ||
| 374 | RefreshControllerName(); | ||
| 375 | } | ||
| 376 | |||
| 377 | if (element == SDL_GAMEPAD_ELEMENT_INVALID) { | ||
| 378 | binding_flow_direction = 0; | ||
| 379 | last_binding_element = SDL_GAMEPAD_ELEMENT_INVALID; | ||
| 380 | } else { | ||
| 381 | last_binding_element = binding_element; | ||
| 382 | } | ||
| 383 | binding_element = element; | ||
| 384 | binding_flow = flow || (element == SDL_GAMEPAD_BUTTON_SOUTH); | ||
| 385 | binding_advance_time = 0; | ||
| 386 | |||
| 387 | for (i = 0; i < controller->num_axes; ++i) { | ||
| 388 | controller->axis_state[i].m_nFarthestValue = controller->axis_state[i].m_nStartingValue; | ||
| 389 | } | ||
| 390 | |||
| 391 | SetGamepadDisplaySelected(gamepad_elements, element); | ||
| 392 | } | ||
| 393 | |||
| 394 | static void SetNextBindingElement(void) | ||
| 395 | { | ||
| 396 | int i; | ||
| 397 | |||
| 398 | if (binding_element == SDL_GAMEPAD_ELEMENT_INVALID) { | ||
| 399 | return; | ||
| 400 | } | ||
| 401 | |||
| 402 | for (i = 0; i < SDL_arraysize(s_arrBindingOrder); ++i) { | ||
| 403 | if (binding_element == s_arrBindingOrder[i]) { | ||
| 404 | binding_flow_direction = 1; | ||
| 405 | SetCurrentBindingElement(s_arrBindingOrder[i + 1], true); | ||
| 406 | return; | ||
| 407 | } | ||
| 408 | } | ||
| 409 | SetCurrentBindingElement(SDL_GAMEPAD_ELEMENT_INVALID, false); | ||
| 410 | } | ||
| 411 | |||
| 412 | static void SetPrevBindingElement(void) | ||
| 413 | { | ||
| 414 | int i; | ||
| 415 | |||
| 416 | if (binding_element == SDL_GAMEPAD_ELEMENT_INVALID) { | ||
| 417 | return; | ||
| 418 | } | ||
| 419 | |||
| 420 | for (i = 1; i < SDL_arraysize(s_arrBindingOrder); ++i) { | ||
| 421 | if (binding_element == s_arrBindingOrder[i]) { | ||
| 422 | binding_flow_direction = -1; | ||
| 423 | SetCurrentBindingElement(s_arrBindingOrder[i - 1], true); | ||
| 424 | return; | ||
| 425 | } | ||
| 426 | } | ||
| 427 | SetCurrentBindingElement(SDL_GAMEPAD_ELEMENT_INVALID, false); | ||
| 428 | } | ||
| 429 | |||
| 430 | static void StopBinding(void) | ||
| 431 | { | ||
| 432 | SetCurrentBindingElement(SDL_GAMEPAD_ELEMENT_INVALID, false); | ||
| 433 | } | ||
| 434 | |||
| 435 | typedef struct | ||
| 436 | { | ||
| 437 | int axis; | ||
| 438 | int direction; | ||
| 439 | } AxisInfo; | ||
| 440 | |||
| 441 | static bool ParseAxisInfo(const char *description, AxisInfo *info) | ||
| 442 | { | ||
| 443 | if (!description) { | ||
| 444 | return false; | ||
| 445 | } | ||
| 446 | |||
| 447 | if (*description == '-') { | ||
| 448 | info->direction = -1; | ||
| 449 | ++description; | ||
| 450 | } else if (*description == '+') { | ||
| 451 | info->direction = 1; | ||
| 452 | ++description; | ||
| 453 | } else { | ||
| 454 | info->direction = 0; | ||
| 455 | } | ||
| 456 | |||
| 457 | if (description[0] == 'a' && SDL_isdigit(description[1])) { | ||
| 458 | ++description; | ||
| 459 | info->axis = SDL_atoi(description); | ||
| 460 | return true; | ||
| 461 | } | ||
| 462 | return false; | ||
| 463 | } | ||
| 464 | |||
| 465 | static void CommitBindingElement(const char *binding, bool force) | ||
| 466 | { | ||
| 467 | char *mapping; | ||
| 468 | int direction = 1; | ||
| 469 | bool ignore_binding = false; | ||
| 470 | |||
| 471 | if (binding_element == SDL_GAMEPAD_ELEMENT_INVALID) { | ||
| 472 | return; | ||
| 473 | } | ||
| 474 | |||
| 475 | if (controller->mapping) { | ||
| 476 | mapping = SDL_strdup(controller->mapping); | ||
| 477 | } else { | ||
| 478 | mapping = NULL; | ||
| 479 | } | ||
| 480 | |||
| 481 | /* If the controller generates multiple events for a single element, pick the best one */ | ||
| 482 | if (!force && binding_advance_time) { | ||
| 483 | char *current = GetElementBinding(mapping, binding_element); | ||
| 484 | bool native_button = (binding_element < SDL_GAMEPAD_BUTTON_COUNT); | ||
| 485 | bool native_axis = (binding_element >= SDL_GAMEPAD_BUTTON_COUNT && | ||
| 486 | binding_element <= SDL_GAMEPAD_ELEMENT_AXIS_MAX); | ||
| 487 | bool native_trigger = (binding_element == SDL_GAMEPAD_ELEMENT_AXIS_LEFT_TRIGGER || | ||
| 488 | binding_element == SDL_GAMEPAD_ELEMENT_AXIS_RIGHT_TRIGGER); | ||
| 489 | bool native_dpad = (binding_element == SDL_GAMEPAD_BUTTON_DPAD_UP || | ||
| 490 | binding_element == SDL_GAMEPAD_BUTTON_DPAD_DOWN || | ||
| 491 | binding_element == SDL_GAMEPAD_BUTTON_DPAD_LEFT || | ||
| 492 | binding_element == SDL_GAMEPAD_BUTTON_DPAD_RIGHT); | ||
| 493 | |||
| 494 | if (native_button) { | ||
| 495 | bool current_button = (current && *current == 'b'); | ||
| 496 | bool proposed_button = (binding && *binding == 'b'); | ||
| 497 | if (current_button && !proposed_button) { | ||
| 498 | ignore_binding = true; | ||
| 499 | } | ||
| 500 | /* Use the lower index button (we map from lower to higher button index) */ | ||
| 501 | if (current_button && proposed_button && current[1] < binding[1]) { | ||
| 502 | ignore_binding = true; | ||
| 503 | } | ||
| 504 | } | ||
| 505 | if (native_axis) { | ||
| 506 | AxisInfo current_axis_info = { 0, 0 }; | ||
| 507 | AxisInfo proposed_axis_info = { 0, 0 }; | ||
| 508 | bool current_axis = ParseAxisInfo(current, ¤t_axis_info); | ||
| 509 | bool proposed_axis = ParseAxisInfo(binding, &proposed_axis_info); | ||
| 510 | |||
| 511 | if (current_axis) { | ||
| 512 | /* Ignore this unless the proposed binding extends the existing axis */ | ||
| 513 | ignore_binding = true; | ||
| 514 | |||
| 515 | if (native_trigger && | ||
| 516 | ((*current == '-' && *binding == '+' && | ||
| 517 | SDL_strcmp(current + 1, binding + 1) == 0) || | ||
| 518 | (*current == '+' && *binding == '-' && | ||
| 519 | SDL_strcmp(current + 1, binding + 1) == 0))) { | ||
| 520 | /* Merge two half axes into a whole axis for a trigger */ | ||
| 521 | ++binding; | ||
| 522 | ignore_binding = false; | ||
| 523 | } | ||
| 524 | |||
| 525 | /* Use the lower index axis (we map from lower to higher axis index) */ | ||
| 526 | if (proposed_axis && proposed_axis_info.axis < current_axis_info.axis) { | ||
| 527 | ignore_binding = false; | ||
| 528 | } | ||
| 529 | } | ||
| 530 | } | ||
| 531 | if (native_dpad) { | ||
| 532 | bool current_hat = (current && *current == 'h'); | ||
| 533 | bool proposed_hat = (binding && *binding == 'h'); | ||
| 534 | if (current_hat && !proposed_hat) { | ||
| 535 | ignore_binding = true; | ||
| 536 | } | ||
| 537 | /* Use the lower index hat (we map from lower to higher hat index) */ | ||
| 538 | if (current_hat && proposed_hat && current[1] < binding[1]) { | ||
| 539 | ignore_binding = true; | ||
| 540 | } | ||
| 541 | } | ||
| 542 | SDL_free(current); | ||
| 543 | } | ||
| 544 | |||
| 545 | if (!ignore_binding && binding_flow && !force) { | ||
| 546 | int existing = GetElementForBinding(mapping, binding); | ||
| 547 | if (existing != SDL_GAMEPAD_ELEMENT_INVALID) { | ||
| 548 | SDL_GamepadButton action_forward = SDL_GAMEPAD_BUTTON_SOUTH; | ||
| 549 | SDL_GamepadButton action_backward = SDL_GAMEPAD_BUTTON_EAST; | ||
| 550 | SDL_GamepadButton action_delete = SDL_GAMEPAD_BUTTON_WEST; | ||
| 551 | if (binding_element == action_forward) { | ||
| 552 | /* Bind it! */ | ||
| 553 | } else if (binding_element == action_backward) { | ||
| 554 | if (existing == action_forward) { | ||
| 555 | bool bound_backward = MappingHasElement(controller->mapping, action_backward); | ||
| 556 | if (bound_backward) { | ||
| 557 | /* Just move on to the next one */ | ||
| 558 | ignore_binding = true; | ||
| 559 | SetNextBindingElement(); | ||
| 560 | } else { | ||
| 561 | /* You can't skip the backward action, go back and start over */ | ||
| 562 | ignore_binding = true; | ||
| 563 | SetPrevBindingElement(); | ||
| 564 | } | ||
| 565 | } else if (existing == action_backward && binding_flow_direction == -1) { | ||
| 566 | /* Keep going backwards */ | ||
| 567 | ignore_binding = true; | ||
| 568 | SetPrevBindingElement(); | ||
| 569 | } else { | ||
| 570 | /* Bind it! */ | ||
| 571 | } | ||
| 572 | } else if (existing == action_forward) { | ||
| 573 | /* Just move on to the next one */ | ||
| 574 | ignore_binding = true; | ||
| 575 | SetNextBindingElement(); | ||
| 576 | } else if (existing == action_backward) { | ||
| 577 | ignore_binding = true; | ||
| 578 | SetPrevBindingElement(); | ||
| 579 | } else if (existing == binding_element) { | ||
| 580 | /* We're rebinding the same thing, just move to the next one */ | ||
| 581 | ignore_binding = true; | ||
| 582 | SetNextBindingElement(); | ||
| 583 | } else if (existing == action_delete) { | ||
| 584 | /* Clear the current binding and move to the next one */ | ||
| 585 | binding = NULL; | ||
| 586 | direction = 1; | ||
| 587 | force = true; | ||
| 588 | } else if (binding_element != action_forward && | ||
| 589 | binding_element != action_backward) { | ||
| 590 | /* Actually, we'll just clear the existing binding */ | ||
| 591 | /*ignore_binding = true;*/ | ||
| 592 | } | ||
| 593 | } | ||
| 594 | } | ||
| 595 | |||
| 596 | if (ignore_binding) { | ||
| 597 | SDL_free(mapping); | ||
| 598 | return; | ||
| 599 | } | ||
| 600 | |||
| 601 | mapping = ClearMappingBinding(mapping, binding); | ||
| 602 | mapping = SetElementBinding(mapping, binding_element, binding); | ||
| 603 | SetAndFreeGamepadMapping(mapping); | ||
| 604 | |||
| 605 | if (force) { | ||
| 606 | if (binding_flow) { | ||
| 607 | if (direction > 0) { | ||
| 608 | SetNextBindingElement(); | ||
| 609 | } else if (direction < 0) { | ||
| 610 | SetPrevBindingElement(); | ||
| 611 | } | ||
| 612 | } else { | ||
| 613 | StopBinding(); | ||
| 614 | } | ||
| 615 | } else { | ||
| 616 | /* Wait to see if any more bindings come in */ | ||
| 617 | binding_advance_time = SDL_GetTicks() + 30; | ||
| 618 | } | ||
| 619 | } | ||
| 620 | |||
| 621 | static void ClearBinding(void) | ||
| 622 | { | ||
| 623 | CommitBindingElement(NULL, true); | ||
| 624 | } | ||
| 625 | |||
| 626 | static void SetDisplayMode(ControllerDisplayMode mode) | ||
| 627 | { | ||
| 628 | float x, y; | ||
| 629 | SDL_MouseButtonFlags button_state; | ||
| 630 | |||
| 631 | if (mode == CONTROLLER_MODE_BINDING) { | ||
| 632 | /* Make a backup of the current mapping */ | ||
| 633 | if (controller->mapping) { | ||
| 634 | backup_mapping = SDL_strdup(controller->mapping); | ||
| 635 | } | ||
| 636 | mapping_controller = controller->id; | ||
| 637 | if (MappingHasBindings(backup_mapping)) { | ||
| 638 | SetCurrentBindingElement(SDL_GAMEPAD_ELEMENT_INVALID, false); | ||
| 639 | } else { | ||
| 640 | SetCurrentBindingElement(SDL_GAMEPAD_BUTTON_SOUTH, true); | ||
| 641 | } | ||
| 642 | } else { | ||
| 643 | if (backup_mapping) { | ||
| 644 | SDL_free(backup_mapping); | ||
| 645 | backup_mapping = NULL; | ||
| 646 | } | ||
| 647 | mapping_controller = 0; | ||
| 648 | StopBinding(); | ||
| 649 | } | ||
| 650 | |||
| 651 | display_mode = mode; | ||
| 652 | SetGamepadImageDisplayMode(image, mode); | ||
| 653 | SetGamepadDisplayDisplayMode(gamepad_elements, mode); | ||
| 654 | |||
| 655 | button_state = SDL_GetMouseState(&x, &y); | ||
| 656 | SDL_RenderCoordinatesFromWindow(screen, x, y, &x, &y); | ||
| 657 | UpdateButtonHighlights(x, y, button_state ? true : false); | ||
| 658 | } | ||
| 659 | |||
| 660 | static void CancelMapping(void) | ||
| 661 | { | ||
| 662 | SetAndFreeGamepadMapping(backup_mapping); | ||
| 663 | backup_mapping = NULL; | ||
| 664 | |||
| 665 | SetDisplayMode(CONTROLLER_MODE_TESTING); | ||
| 666 | } | ||
| 667 | |||
| 668 | static void ClearMapping(void) | ||
| 669 | { | ||
| 670 | SetAndFreeGamepadMapping(NULL); | ||
| 671 | SetCurrentBindingElement(SDL_GAMEPAD_ELEMENT_INVALID, false); | ||
| 672 | } | ||
| 673 | |||
| 674 | static void CopyMapping(void) | ||
| 675 | { | ||
| 676 | if (controller && controller->mapping) { | ||
| 677 | SDL_SetClipboardText(controller->mapping); | ||
| 678 | } | ||
| 679 | } | ||
| 680 | |||
| 681 | static void PasteMapping(void) | ||
| 682 | { | ||
| 683 | if (controller) { | ||
| 684 | char *mapping = SDL_GetClipboardText(); | ||
| 685 | if (MappingHasBindings(mapping)) { | ||
| 686 | StopBinding(); | ||
| 687 | SDL_SetGamepadMapping(controller->id, mapping); | ||
| 688 | RefreshControllerName(); | ||
| 689 | } else { | ||
| 690 | /* Not a valid mapping, ignore it */ | ||
| 691 | } | ||
| 692 | SDL_free(mapping); | ||
| 693 | } | ||
| 694 | } | ||
| 695 | |||
| 696 | static void CommitControllerName(void) | ||
| 697 | { | ||
| 698 | char *mapping = NULL; | ||
| 699 | |||
| 700 | if (controller->mapping) { | ||
| 701 | mapping = SDL_strdup(controller->mapping); | ||
| 702 | } else { | ||
| 703 | mapping = NULL; | ||
| 704 | } | ||
| 705 | mapping = SetMappingName(mapping, controller_name); | ||
| 706 | SetAndFreeGamepadMapping(mapping); | ||
| 707 | } | ||
| 708 | |||
| 709 | static void AddControllerNameText(const char *text) | ||
| 710 | { | ||
| 711 | size_t current_length = (controller_name ? SDL_strlen(controller_name) : 0); | ||
| 712 | size_t text_length = SDL_strlen(text); | ||
| 713 | size_t size = current_length + text_length + 1; | ||
| 714 | char *name = (char *)SDL_realloc(controller_name, size); | ||
| 715 | if (name) { | ||
| 716 | SDL_memcpy(&name[current_length], text, text_length + 1); | ||
| 717 | controller_name = name; | ||
| 718 | } | ||
| 719 | CommitControllerName(); | ||
| 720 | } | ||
| 721 | |||
| 722 | static void BackspaceControllerName(void) | ||
| 723 | { | ||
| 724 | size_t length = (controller_name ? SDL_strlen(controller_name) : 0); | ||
| 725 | if (length > 0) { | ||
| 726 | controller_name[length - 1] = '\0'; | ||
| 727 | } | ||
| 728 | CommitControllerName(); | ||
| 729 | } | ||
| 730 | |||
| 731 | static void ClearControllerName(void) | ||
| 732 | { | ||
| 733 | if (controller_name) { | ||
| 734 | *controller_name = '\0'; | ||
| 735 | } | ||
| 736 | CommitControllerName(); | ||
| 737 | } | ||
| 738 | |||
| 739 | static void CopyControllerName(void) | ||
| 740 | { | ||
| 741 | SDL_SetClipboardText(controller_name); | ||
| 742 | } | ||
| 743 | |||
| 744 | static void PasteControllerName(void) | ||
| 745 | { | ||
| 746 | SDL_free(controller_name); | ||
| 747 | controller_name = SDL_GetClipboardText(); | ||
| 748 | CommitControllerName(); | ||
| 749 | } | ||
| 750 | |||
| 751 | static void CommitGamepadType(SDL_GamepadType type) | ||
| 752 | { | ||
| 753 | char *mapping = NULL; | ||
| 754 | |||
| 755 | if (controller->mapping) { | ||
| 756 | mapping = SDL_strdup(controller->mapping); | ||
| 757 | } else { | ||
| 758 | mapping = NULL; | ||
| 759 | } | ||
| 760 | mapping = SetMappingType(mapping, type); | ||
| 761 | SetAndFreeGamepadMapping(mapping); | ||
| 762 | } | ||
| 763 | |||
| 764 | static const char *GetBindingInstruction(void) | ||
| 765 | { | ||
| 766 | switch (binding_element) { | ||
| 767 | case SDL_GAMEPAD_ELEMENT_INVALID: | ||
| 768 | return "Select an element to bind from the list on the left"; | ||
| 769 | case SDL_GAMEPAD_BUTTON_SOUTH: | ||
| 770 | case SDL_GAMEPAD_BUTTON_EAST: | ||
| 771 | case SDL_GAMEPAD_BUTTON_WEST: | ||
| 772 | case SDL_GAMEPAD_BUTTON_NORTH: | ||
| 773 | switch (SDL_GetGamepadButtonLabelForType(GetGamepadImageType(image), (SDL_GamepadButton)binding_element)) { | ||
| 774 | case SDL_GAMEPAD_BUTTON_LABEL_A: | ||
| 775 | return "Press the A button"; | ||
| 776 | case SDL_GAMEPAD_BUTTON_LABEL_B: | ||
| 777 | return "Press the B button"; | ||
| 778 | case SDL_GAMEPAD_BUTTON_LABEL_X: | ||
| 779 | return "Press the X button"; | ||
| 780 | case SDL_GAMEPAD_BUTTON_LABEL_Y: | ||
| 781 | return "Press the Y button"; | ||
| 782 | case SDL_GAMEPAD_BUTTON_LABEL_CROSS: | ||
| 783 | return "Press the Cross (X) button"; | ||
| 784 | case SDL_GAMEPAD_BUTTON_LABEL_CIRCLE: | ||
| 785 | return "Press the Circle button"; | ||
| 786 | case SDL_GAMEPAD_BUTTON_LABEL_SQUARE: | ||
| 787 | return "Press the Square button"; | ||
| 788 | case SDL_GAMEPAD_BUTTON_LABEL_TRIANGLE: | ||
| 789 | return "Press the Triangle button"; | ||
| 790 | default: | ||
| 791 | return ""; | ||
| 792 | } | ||
| 793 | break; | ||
| 794 | case SDL_GAMEPAD_BUTTON_BACK: | ||
| 795 | return "Press the left center button (Back/View/Share)"; | ||
| 796 | case SDL_GAMEPAD_BUTTON_GUIDE: | ||
| 797 | return "Press the center button (Home/Guide)"; | ||
| 798 | case SDL_GAMEPAD_BUTTON_START: | ||
| 799 | return "Press the right center button (Start/Menu/Options)"; | ||
| 800 | case SDL_GAMEPAD_BUTTON_LEFT_STICK: | ||
| 801 | return "Press the left thumbstick button (LSB/L3)"; | ||
| 802 | case SDL_GAMEPAD_BUTTON_RIGHT_STICK: | ||
| 803 | return "Press the right thumbstick button (RSB/R3)"; | ||
| 804 | case SDL_GAMEPAD_BUTTON_LEFT_SHOULDER: | ||
| 805 | return "Press the left shoulder button (LB/L1)"; | ||
| 806 | case SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER: | ||
| 807 | return "Press the right shoulder button (RB/R1)"; | ||
| 808 | case SDL_GAMEPAD_BUTTON_DPAD_UP: | ||
| 809 | return "Press the D-Pad up"; | ||
| 810 | case SDL_GAMEPAD_BUTTON_DPAD_DOWN: | ||
| 811 | return "Press the D-Pad down"; | ||
| 812 | case SDL_GAMEPAD_BUTTON_DPAD_LEFT: | ||
| 813 | return "Press the D-Pad left"; | ||
| 814 | case SDL_GAMEPAD_BUTTON_DPAD_RIGHT: | ||
| 815 | return "Press the D-Pad right"; | ||
| 816 | case SDL_GAMEPAD_BUTTON_MISC1: | ||
| 817 | return "Press the bottom center button (Share/Capture)"; | ||
| 818 | case SDL_GAMEPAD_BUTTON_RIGHT_PADDLE1: | ||
| 819 | return "Press the upper paddle under your right hand"; | ||
| 820 | case SDL_GAMEPAD_BUTTON_LEFT_PADDLE1: | ||
| 821 | return "Press the upper paddle under your left hand"; | ||
| 822 | case SDL_GAMEPAD_BUTTON_RIGHT_PADDLE2: | ||
| 823 | return "Press the lower paddle under your right hand"; | ||
| 824 | case SDL_GAMEPAD_BUTTON_LEFT_PADDLE2: | ||
| 825 | return "Press the lower paddle under your left hand"; | ||
| 826 | case SDL_GAMEPAD_BUTTON_TOUCHPAD: | ||
| 827 | return "Press down on the touchpad"; | ||
| 828 | case SDL_GAMEPAD_BUTTON_MISC2: | ||
| 829 | case SDL_GAMEPAD_BUTTON_MISC3: | ||
| 830 | case SDL_GAMEPAD_BUTTON_MISC4: | ||
| 831 | case SDL_GAMEPAD_BUTTON_MISC5: | ||
| 832 | case SDL_GAMEPAD_BUTTON_MISC6: | ||
| 833 | return "Press any additional button not already bound"; | ||
| 834 | case SDL_GAMEPAD_ELEMENT_AXIS_LEFTX_NEGATIVE: | ||
| 835 | return "Move the left thumbstick to the left"; | ||
| 836 | case SDL_GAMEPAD_ELEMENT_AXIS_LEFTX_POSITIVE: | ||
| 837 | return "Move the left thumbstick to the right"; | ||
| 838 | case SDL_GAMEPAD_ELEMENT_AXIS_LEFTY_NEGATIVE: | ||
| 839 | return "Move the left thumbstick up"; | ||
| 840 | case SDL_GAMEPAD_ELEMENT_AXIS_LEFTY_POSITIVE: | ||
| 841 | return "Move the left thumbstick down"; | ||
| 842 | case SDL_GAMEPAD_ELEMENT_AXIS_RIGHTX_NEGATIVE: | ||
| 843 | return "Move the right thumbstick to the left"; | ||
| 844 | case SDL_GAMEPAD_ELEMENT_AXIS_RIGHTX_POSITIVE: | ||
| 845 | return "Move the right thumbstick to the right"; | ||
| 846 | case SDL_GAMEPAD_ELEMENT_AXIS_RIGHTY_NEGATIVE: | ||
| 847 | return "Move the right thumbstick up"; | ||
| 848 | case SDL_GAMEPAD_ELEMENT_AXIS_RIGHTY_POSITIVE: | ||
| 849 | return "Move the right thumbstick down"; | ||
| 850 | case SDL_GAMEPAD_ELEMENT_AXIS_LEFT_TRIGGER: | ||
| 851 | return "Pull the left trigger (LT/L2)"; | ||
| 852 | case SDL_GAMEPAD_ELEMENT_AXIS_RIGHT_TRIGGER: | ||
| 853 | return "Pull the right trigger (RT/R2)"; | ||
| 854 | case SDL_GAMEPAD_ELEMENT_NAME: | ||
| 855 | return "Type the name of your controller"; | ||
| 856 | case SDL_GAMEPAD_ELEMENT_TYPE: | ||
| 857 | return "Select the type of your controller"; | ||
| 858 | default: | ||
| 859 | return ""; | ||
| 860 | } | ||
| 861 | } | ||
| 862 | |||
| 863 | static int FindController(SDL_JoystickID id) | ||
| 864 | { | ||
| 865 | int i; | ||
| 866 | |||
| 867 | for (i = 0; i < num_controllers; ++i) { | ||
| 868 | if (id == controllers[i].id) { | ||
| 869 | return i; | ||
| 870 | } | ||
| 871 | } | ||
| 872 | return -1; | ||
| 873 | } | ||
| 874 | |||
| 875 | static void SetController(SDL_JoystickID id) | ||
| 876 | { | ||
| 877 | int i = FindController(id); | ||
| 878 | |||
| 879 | if (i < 0 && num_controllers > 0) { | ||
| 880 | i = 0; | ||
| 881 | } | ||
| 882 | |||
| 883 | if (i >= 0) { | ||
| 884 | controller = &controllers[i]; | ||
| 885 | } else { | ||
| 886 | controller = NULL; | ||
| 887 | } | ||
| 888 | |||
| 889 | RefreshControllerName(); | ||
| 890 | } | ||
| 891 | |||
| 892 | static void AddController(SDL_JoystickID id, bool verbose) | ||
| 893 | { | ||
| 894 | Controller *new_controllers; | ||
| 895 | Controller *new_controller; | ||
| 896 | SDL_Joystick *joystick; | ||
| 897 | |||
| 898 | if (FindController(id) >= 0) { | ||
| 899 | /* We already have this controller */ | ||
| 900 | return; | ||
| 901 | } | ||
| 902 | |||
| 903 | new_controllers = (Controller *)SDL_realloc(controllers, (num_controllers + 1) * sizeof(*controllers)); | ||
| 904 | if (!new_controllers) { | ||
| 905 | return; | ||
| 906 | } | ||
| 907 | |||
| 908 | controller = NULL; | ||
| 909 | controllers = new_controllers; | ||
| 910 | new_controller = &new_controllers[num_controllers++]; | ||
| 911 | SDL_zerop(new_controller); | ||
| 912 | new_controller->id = id; | ||
| 913 | |||
| 914 | new_controller->joystick = SDL_OpenJoystick(id); | ||
| 915 | if (new_controller->joystick) { | ||
| 916 | new_controller->num_axes = SDL_GetNumJoystickAxes(new_controller->joystick); | ||
| 917 | new_controller->axis_state = (AxisState *)SDL_calloc(new_controller->num_axes, sizeof(*new_controller->axis_state)); | ||
| 918 | } | ||
| 919 | |||
| 920 | joystick = new_controller->joystick; | ||
| 921 | if (joystick) { | ||
| 922 | if (verbose && !SDL_IsGamepad(id)) { | ||
| 923 | const char *name = SDL_GetJoystickName(joystick); | ||
| 924 | const char *path = SDL_GetJoystickPath(joystick); | ||
| 925 | char guid[33]; | ||
| 926 | SDL_Log("Opened joystick %s%s%s", name, path ? ", " : "", path ? path : ""); | ||
| 927 | SDL_GUIDToString(SDL_GetJoystickGUID(joystick), guid, sizeof(guid)); | ||
| 928 | SDL_Log("No gamepad mapping for %s", guid); | ||
| 929 | } | ||
| 930 | } else { | ||
| 931 | SDL_Log("Couldn't open joystick: %s", SDL_GetError()); | ||
| 932 | } | ||
| 933 | |||
| 934 | if (mapping_controller) { | ||
| 935 | SetController(mapping_controller); | ||
| 936 | } else { | ||
| 937 | SetController(id); | ||
| 938 | } | ||
| 939 | } | ||
| 940 | |||
| 941 | static void DelController(SDL_JoystickID id) | ||
| 942 | { | ||
| 943 | int i = FindController(id); | ||
| 944 | |||
| 945 | if (i < 0) { | ||
| 946 | return; | ||
| 947 | } | ||
| 948 | |||
| 949 | if (display_mode == CONTROLLER_MODE_BINDING && id == controller->id) { | ||
| 950 | SetDisplayMode(CONTROLLER_MODE_TESTING); | ||
| 951 | } | ||
| 952 | |||
| 953 | /* Reset trigger state */ | ||
| 954 | if (controllers[i].trigger_effect != 0) { | ||
| 955 | controllers[i].trigger_effect = -1; | ||
| 956 | CyclePS5TriggerEffect(&controllers[i]); | ||
| 957 | } | ||
| 958 | SDL_assert(controllers[i].gamepad == NULL); | ||
| 959 | if (controllers[i].axis_state) { | ||
| 960 | SDL_free(controllers[i].axis_state); | ||
| 961 | } | ||
| 962 | if (controllers[i].joystick) { | ||
| 963 | SDL_CloseJoystick(controllers[i].joystick); | ||
| 964 | } | ||
| 965 | |||
| 966 | --num_controllers; | ||
| 967 | if (i < num_controllers) { | ||
| 968 | SDL_memcpy(&controllers[i], &controllers[i + 1], (num_controllers - i) * sizeof(*controllers)); | ||
| 969 | } | ||
| 970 | |||
| 971 | if (mapping_controller) { | ||
| 972 | SetController(mapping_controller); | ||
| 973 | } else { | ||
| 974 | SetController(id); | ||
| 975 | } | ||
| 976 | } | ||
| 977 | |||
| 978 | static void HandleGamepadRemapped(SDL_JoystickID id) | ||
| 979 | { | ||
| 980 | char *mapping; | ||
| 981 | int i = FindController(id); | ||
| 982 | |||
| 983 | SDL_assert(i >= 0); | ||
| 984 | if (i < 0) { | ||
| 985 | return; | ||
| 986 | } | ||
| 987 | |||
| 988 | if (!controllers[i].gamepad) { | ||
| 989 | /* Failed to open this controller */ | ||
| 990 | return; | ||
| 991 | } | ||
| 992 | |||
| 993 | /* Get the current mapping */ | ||
| 994 | mapping = SDL_GetGamepadMapping(controllers[i].gamepad); | ||
| 995 | |||
| 996 | /* Make sure the mapping has a valid name */ | ||
| 997 | if (mapping && !MappingHasName(mapping)) { | ||
| 998 | mapping = SetMappingName(mapping, SDL_GetJoystickName(controllers[i].joystick)); | ||
| 999 | } | ||
| 1000 | |||
| 1001 | SDL_free(controllers[i].mapping); | ||
| 1002 | controllers[i].mapping = mapping; | ||
| 1003 | controllers[i].has_bindings = MappingHasBindings(mapping); | ||
| 1004 | } | ||
| 1005 | |||
| 1006 | static void HandleGamepadAdded(SDL_JoystickID id, bool verbose) | ||
| 1007 | { | ||
| 1008 | SDL_Gamepad *gamepad; | ||
| 1009 | Uint16 firmware_version; | ||
| 1010 | SDL_SensorType sensors[] = { | ||
| 1011 | SDL_SENSOR_ACCEL, | ||
| 1012 | SDL_SENSOR_GYRO, | ||
| 1013 | SDL_SENSOR_ACCEL_L, | ||
| 1014 | SDL_SENSOR_GYRO_L, | ||
| 1015 | SDL_SENSOR_ACCEL_R, | ||
| 1016 | SDL_SENSOR_GYRO_R | ||
| 1017 | }; | ||
| 1018 | int i; | ||
| 1019 | |||
| 1020 | i = FindController(id); | ||
| 1021 | if (i < 0) { | ||
| 1022 | return; | ||
| 1023 | } | ||
| 1024 | SDL_Log("Gamepad %" SDL_PRIu32 " added", id); | ||
| 1025 | |||
| 1026 | SDL_assert(!controllers[i].gamepad); | ||
| 1027 | controllers[i].gamepad = SDL_OpenGamepad(id); | ||
| 1028 | |||
| 1029 | gamepad = controllers[i].gamepad; | ||
| 1030 | if (gamepad) { | ||
| 1031 | if (verbose) { | ||
| 1032 | SDL_PropertiesID props = SDL_GetGamepadProperties(gamepad); | ||
| 1033 | const char *name = SDL_GetGamepadName(gamepad); | ||
| 1034 | const char *path = SDL_GetGamepadPath(gamepad); | ||
| 1035 | SDL_GUID guid = SDL_GetGamepadGUIDForID(id); | ||
| 1036 | char guid_string[33]; | ||
| 1037 | SDL_GUIDToString(guid, guid_string, sizeof(guid_string)); | ||
| 1038 | SDL_Log("Opened gamepad %s, guid %s%s%s", name, guid_string, path ? ", " : "", path ? path : ""); | ||
| 1039 | |||
| 1040 | firmware_version = SDL_GetGamepadFirmwareVersion(gamepad); | ||
| 1041 | if (firmware_version) { | ||
| 1042 | SDL_Log("Firmware version: 0x%x (%d)", firmware_version, firmware_version); | ||
| 1043 | } | ||
| 1044 | |||
| 1045 | if (SDL_GetBooleanProperty(props, SDL_PROP_GAMEPAD_CAP_PLAYER_LED_BOOLEAN, false)) { | ||
| 1046 | SDL_Log("Has player LED"); | ||
| 1047 | } | ||
| 1048 | |||
| 1049 | if (SDL_GetBooleanProperty(props, SDL_PROP_GAMEPAD_CAP_RUMBLE_BOOLEAN, false)) { | ||
| 1050 | SDL_Log("Rumble supported"); | ||
| 1051 | } | ||
| 1052 | |||
| 1053 | if (SDL_GetBooleanProperty(props, SDL_PROP_GAMEPAD_CAP_TRIGGER_RUMBLE_BOOLEAN, false)) { | ||
| 1054 | SDL_Log("Trigger rumble supported"); | ||
| 1055 | } | ||
| 1056 | |||
| 1057 | if (SDL_GetGamepadPlayerIndex(gamepad) >= 0) { | ||
| 1058 | SDL_Log("Player index: %d", SDL_GetGamepadPlayerIndex(gamepad)); | ||
| 1059 | } | ||
| 1060 | |||
| 1061 | switch (SDL_GetJoystickTypeForID(id)) { | ||
| 1062 | case SDL_JOYSTICK_TYPE_WHEEL: | ||
| 1063 | SDL_Log("Controller is a wheel"); | ||
| 1064 | break; | ||
| 1065 | case SDL_JOYSTICK_TYPE_ARCADE_STICK: | ||
| 1066 | SDL_Log("Controller is an arcade stick"); | ||
| 1067 | break; | ||
| 1068 | case SDL_JOYSTICK_TYPE_FLIGHT_STICK: | ||
| 1069 | SDL_Log("Controller is a flight stick"); | ||
| 1070 | break; | ||
| 1071 | case SDL_JOYSTICK_TYPE_DANCE_PAD: | ||
| 1072 | SDL_Log("Controller is a dance pad"); | ||
| 1073 | break; | ||
| 1074 | case SDL_JOYSTICK_TYPE_GUITAR: | ||
| 1075 | SDL_Log("Controller is a guitar"); | ||
| 1076 | break; | ||
| 1077 | case SDL_JOYSTICK_TYPE_DRUM_KIT: | ||
| 1078 | SDL_Log("Controller is a drum kit"); | ||
| 1079 | break; | ||
| 1080 | case SDL_JOYSTICK_TYPE_ARCADE_PAD: | ||
| 1081 | SDL_Log("Controller is an arcade pad"); | ||
| 1082 | break; | ||
| 1083 | case SDL_JOYSTICK_TYPE_THROTTLE: | ||
| 1084 | SDL_Log("Controller is a throttle"); | ||
| 1085 | break; | ||
| 1086 | default: | ||
| 1087 | break; | ||
| 1088 | } | ||
| 1089 | } | ||
| 1090 | |||
| 1091 | for (i = 0; i < SDL_arraysize(sensors); ++i) { | ||
| 1092 | SDL_SensorType sensor = sensors[i]; | ||
| 1093 | |||
| 1094 | if (SDL_GamepadHasSensor(gamepad, sensor)) { | ||
| 1095 | if (verbose) { | ||
| 1096 | SDL_Log("Enabling %s at %.2f Hz", GetSensorName(sensor), SDL_GetGamepadSensorDataRate(gamepad, sensor)); | ||
| 1097 | } | ||
| 1098 | SDL_SetGamepadSensorEnabled(gamepad, sensor, true); | ||
| 1099 | } | ||
| 1100 | } | ||
| 1101 | |||
| 1102 | if (verbose) { | ||
| 1103 | char *mapping = SDL_GetGamepadMapping(gamepad); | ||
| 1104 | if (mapping) { | ||
| 1105 | SDL_Log("Mapping: %s", mapping); | ||
| 1106 | SDL_free(mapping); | ||
| 1107 | } | ||
| 1108 | } | ||
| 1109 | } else { | ||
| 1110 | SDL_Log("Couldn't open gamepad: %s", SDL_GetError()); | ||
| 1111 | } | ||
| 1112 | |||
| 1113 | HandleGamepadRemapped(id); | ||
| 1114 | SetController(id); | ||
| 1115 | } | ||
| 1116 | |||
| 1117 | static void HandleGamepadRemoved(SDL_JoystickID id) | ||
| 1118 | { | ||
| 1119 | int i = FindController(id); | ||
| 1120 | |||
| 1121 | SDL_assert(i >= 0); | ||
| 1122 | if (i < 0) { | ||
| 1123 | return; | ||
| 1124 | } | ||
| 1125 | SDL_Log("Gamepad %" SDL_PRIu32 " removed", id); | ||
| 1126 | |||
| 1127 | if (controllers[i].mapping) { | ||
| 1128 | SDL_free(controllers[i].mapping); | ||
| 1129 | controllers[i].mapping = NULL; | ||
| 1130 | } | ||
| 1131 | if (controllers[i].gamepad) { | ||
| 1132 | SDL_CloseGamepad(controllers[i].gamepad); | ||
| 1133 | controllers[i].gamepad = NULL; | ||
| 1134 | } | ||
| 1135 | } | ||
| 1136 | |||
| 1137 | static Uint16 ConvertAxisToRumble(Sint16 axisval) | ||
| 1138 | { | ||
| 1139 | /* Only start rumbling if the axis is past the halfway point */ | ||
| 1140 | const Sint16 half_axis = (Sint16)SDL_ceil(SDL_JOYSTICK_AXIS_MAX / 2.0f); | ||
| 1141 | if (axisval > half_axis) { | ||
| 1142 | return (Uint16)(axisval - half_axis) * 4; | ||
| 1143 | } else { | ||
| 1144 | return 0; | ||
| 1145 | } | ||
| 1146 | } | ||
| 1147 | |||
| 1148 | static bool ShowingFront(void) | ||
| 1149 | { | ||
| 1150 | bool showing_front = true; | ||
| 1151 | int i; | ||
| 1152 | |||
| 1153 | /* Show the back of the gamepad if the paddles are being held or bound */ | ||
| 1154 | for (i = SDL_GAMEPAD_BUTTON_RIGHT_PADDLE1; i <= SDL_GAMEPAD_BUTTON_LEFT_PADDLE2; ++i) { | ||
| 1155 | if (SDL_GetGamepadButton(controller->gamepad, (SDL_GamepadButton)i) || | ||
| 1156 | binding_element == i) { | ||
| 1157 | showing_front = false; | ||
| 1158 | break; | ||
| 1159 | } | ||
| 1160 | } | ||
| 1161 | if ((SDL_GetModState() & SDL_KMOD_SHIFT) && binding_element != SDL_GAMEPAD_ELEMENT_NAME) { | ||
| 1162 | showing_front = false; | ||
| 1163 | } | ||
| 1164 | return showing_front; | ||
| 1165 | } | ||
| 1166 | |||
| 1167 | static void SDLCALL VirtualGamepadSetPlayerIndex(void *userdata, int player_index) | ||
| 1168 | { | ||
| 1169 | SDL_Log("Virtual Gamepad: player index set to %d", player_index); | ||
| 1170 | } | ||
| 1171 | |||
| 1172 | static bool SDLCALL VirtualGamepadRumble(void *userdata, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble) | ||
| 1173 | { | ||
| 1174 | SDL_Log("Virtual Gamepad: rumble set to %d/%d", low_frequency_rumble, high_frequency_rumble); | ||
| 1175 | return true; | ||
| 1176 | } | ||
| 1177 | |||
| 1178 | static bool SDLCALL VirtualGamepadRumbleTriggers(void *userdata, Uint16 left_rumble, Uint16 right_rumble) | ||
| 1179 | { | ||
| 1180 | SDL_Log("Virtual Gamepad: trigger rumble set to %d/%d", left_rumble, right_rumble); | ||
| 1181 | return true; | ||
| 1182 | } | ||
| 1183 | |||
| 1184 | static bool SDLCALL VirtualGamepadSetLED(void *userdata, Uint8 red, Uint8 green, Uint8 blue) | ||
| 1185 | { | ||
| 1186 | SDL_Log("Virtual Gamepad: LED set to RGB %d,%d,%d", red, green, blue); | ||
| 1187 | return true; | ||
| 1188 | } | ||
| 1189 | |||
| 1190 | static void OpenVirtualGamepad(void) | ||
| 1191 | { | ||
| 1192 | SDL_VirtualJoystickTouchpadDesc virtual_touchpad = { 1, { 0, 0, 0 } }; | ||
| 1193 | SDL_VirtualJoystickSensorDesc virtual_sensor = { SDL_SENSOR_ACCEL, 0.0f }; | ||
| 1194 | SDL_VirtualJoystickDesc desc; | ||
| 1195 | SDL_JoystickID virtual_id; | ||
| 1196 | |||
| 1197 | if (virtual_joystick) { | ||
| 1198 | return; | ||
| 1199 | } | ||
| 1200 | |||
| 1201 | SDL_INIT_INTERFACE(&desc); | ||
| 1202 | desc.type = SDL_JOYSTICK_TYPE_GAMEPAD; | ||
| 1203 | desc.naxes = SDL_GAMEPAD_AXIS_COUNT; | ||
| 1204 | desc.nbuttons = SDL_GAMEPAD_BUTTON_COUNT; | ||
| 1205 | desc.ntouchpads = 1; | ||
| 1206 | desc.touchpads = &virtual_touchpad; | ||
| 1207 | desc.nsensors = 1; | ||
| 1208 | desc.sensors = &virtual_sensor; | ||
| 1209 | desc.SetPlayerIndex = VirtualGamepadSetPlayerIndex; | ||
| 1210 | desc.Rumble = VirtualGamepadRumble; | ||
| 1211 | desc.RumbleTriggers = VirtualGamepadRumbleTriggers; | ||
| 1212 | desc.SetLED = VirtualGamepadSetLED; | ||
| 1213 | |||
| 1214 | virtual_id = SDL_AttachVirtualJoystick(&desc); | ||
| 1215 | if (virtual_id == 0) { | ||
| 1216 | SDL_Log("Couldn't attach virtual device: %s", SDL_GetError()); | ||
| 1217 | } else { | ||
| 1218 | virtual_joystick = SDL_OpenJoystick(virtual_id); | ||
| 1219 | if (!virtual_joystick) { | ||
| 1220 | SDL_Log("Couldn't open virtual device: %s", SDL_GetError()); | ||
| 1221 | } | ||
| 1222 | } | ||
| 1223 | } | ||
| 1224 | |||
| 1225 | static void CloseVirtualGamepad(void) | ||
| 1226 | { | ||
| 1227 | int i; | ||
| 1228 | SDL_JoystickID *joysticks = SDL_GetJoysticks(NULL); | ||
| 1229 | if (joysticks) { | ||
| 1230 | for (i = 0; joysticks[i]; ++i) { | ||
| 1231 | SDL_JoystickID instance_id = joysticks[i]; | ||
| 1232 | if (SDL_IsJoystickVirtual(instance_id)) { | ||
| 1233 | SDL_DetachVirtualJoystick(instance_id); | ||
| 1234 | } | ||
| 1235 | } | ||
| 1236 | SDL_free(joysticks); | ||
| 1237 | } | ||
| 1238 | |||
| 1239 | if (virtual_joystick) { | ||
| 1240 | SDL_CloseJoystick(virtual_joystick); | ||
| 1241 | virtual_joystick = NULL; | ||
| 1242 | } | ||
| 1243 | } | ||
| 1244 | |||
| 1245 | static void VirtualGamepadMouseMotion(float x, float y) | ||
| 1246 | { | ||
| 1247 | if (virtual_button_active != SDL_GAMEPAD_BUTTON_INVALID) { | ||
| 1248 | if (virtual_axis_active != SDL_GAMEPAD_AXIS_INVALID) { | ||
| 1249 | const float MOVING_DISTANCE = 2.0f; | ||
| 1250 | if (SDL_fabs(x - virtual_axis_start_x) >= MOVING_DISTANCE || | ||
| 1251 | SDL_fabs(y - virtual_axis_start_y) >= MOVING_DISTANCE) { | ||
| 1252 | SDL_SetJoystickVirtualButton(virtual_joystick, virtual_button_active, false); | ||
| 1253 | virtual_button_active = SDL_GAMEPAD_BUTTON_INVALID; | ||
| 1254 | } | ||
| 1255 | } | ||
| 1256 | } | ||
| 1257 | |||
| 1258 | if (virtual_axis_active != SDL_GAMEPAD_AXIS_INVALID) { | ||
| 1259 | if (virtual_axis_active == SDL_GAMEPAD_AXIS_LEFT_TRIGGER || | ||
| 1260 | virtual_axis_active == SDL_GAMEPAD_AXIS_RIGHT_TRIGGER) { | ||
| 1261 | int range = (SDL_JOYSTICK_AXIS_MAX - SDL_JOYSTICK_AXIS_MIN); | ||
| 1262 | float distance = SDL_clamp((y - virtual_axis_start_y) / GetGamepadImageAxisHeight(image), 0.0f, 1.0f); | ||
| 1263 | Sint16 value = (Sint16)(SDL_JOYSTICK_AXIS_MIN + (distance * range)); | ||
| 1264 | SDL_SetJoystickVirtualAxis(virtual_joystick, virtual_axis_active, value); | ||
| 1265 | } else { | ||
| 1266 | float distanceX = SDL_clamp((x - virtual_axis_start_x) / GetGamepadImageAxisWidth(image), -1.0f, 1.0f); | ||
| 1267 | float distanceY = SDL_clamp((y - virtual_axis_start_y) / GetGamepadImageAxisHeight(image), -1.0f, 1.0f); | ||
| 1268 | Sint16 valueX, valueY; | ||
| 1269 | |||
| 1270 | if (distanceX >= 0) { | ||
| 1271 | valueX = (Sint16)(distanceX * SDL_JOYSTICK_AXIS_MAX); | ||
| 1272 | } else { | ||
| 1273 | valueX = (Sint16)(distanceX * -SDL_JOYSTICK_AXIS_MIN); | ||
| 1274 | } | ||
| 1275 | if (distanceY >= 0) { | ||
| 1276 | valueY = (Sint16)(distanceY * SDL_JOYSTICK_AXIS_MAX); | ||
| 1277 | } else { | ||
| 1278 | valueY = (Sint16)(distanceY * -SDL_JOYSTICK_AXIS_MIN); | ||
| 1279 | } | ||
| 1280 | SDL_SetJoystickVirtualAxis(virtual_joystick, virtual_axis_active, valueX); | ||
| 1281 | SDL_SetJoystickVirtualAxis(virtual_joystick, virtual_axis_active + 1, valueY); | ||
| 1282 | } | ||
| 1283 | } | ||
| 1284 | |||
| 1285 | if (virtual_touchpad_active) { | ||
| 1286 | SDL_FRect touchpad; | ||
| 1287 | GetGamepadTouchpadArea(image, &touchpad); | ||
| 1288 | virtual_touchpad_x = (x - touchpad.x) / touchpad.w; | ||
| 1289 | virtual_touchpad_y = (y - touchpad.y) / touchpad.h; | ||
| 1290 | SDL_SetJoystickVirtualTouchpad(virtual_joystick, 0, 0, true, virtual_touchpad_x, virtual_touchpad_y, 1.0f); | ||
| 1291 | } | ||
| 1292 | } | ||
| 1293 | |||
| 1294 | static void VirtualGamepadMouseDown(float x, float y) | ||
| 1295 | { | ||
| 1296 | int element = GetGamepadImageElementAt(image, x, y); | ||
| 1297 | |||
| 1298 | if (element == SDL_GAMEPAD_ELEMENT_INVALID) { | ||
| 1299 | SDL_FPoint point = { x, y }; | ||
| 1300 | SDL_FRect touchpad; | ||
| 1301 | GetGamepadTouchpadArea(image, &touchpad); | ||
| 1302 | if (SDL_PointInRectFloat(&point, &touchpad)) { | ||
| 1303 | virtual_touchpad_active = true; | ||
| 1304 | virtual_touchpad_x = (x - touchpad.x) / touchpad.w; | ||
| 1305 | virtual_touchpad_y = (y - touchpad.y) / touchpad.h; | ||
| 1306 | SDL_SetJoystickVirtualTouchpad(virtual_joystick, 0, 0, true, virtual_touchpad_x, virtual_touchpad_y, 1.0f); | ||
| 1307 | } | ||
| 1308 | return; | ||
| 1309 | } | ||
| 1310 | |||
| 1311 | if (element < SDL_GAMEPAD_BUTTON_COUNT) { | ||
| 1312 | virtual_button_active = (SDL_GamepadButton)element; | ||
| 1313 | SDL_SetJoystickVirtualButton(virtual_joystick, virtual_button_active, true); | ||
| 1314 | } else { | ||
| 1315 | switch (element) { | ||
| 1316 | case SDL_GAMEPAD_ELEMENT_AXIS_LEFTX_NEGATIVE: | ||
| 1317 | case SDL_GAMEPAD_ELEMENT_AXIS_LEFTX_POSITIVE: | ||
| 1318 | case SDL_GAMEPAD_ELEMENT_AXIS_LEFTY_NEGATIVE: | ||
| 1319 | case SDL_GAMEPAD_ELEMENT_AXIS_LEFTY_POSITIVE: | ||
| 1320 | virtual_axis_active = SDL_GAMEPAD_AXIS_LEFTX; | ||
| 1321 | break; | ||
| 1322 | case SDL_GAMEPAD_ELEMENT_AXIS_RIGHTX_NEGATIVE: | ||
| 1323 | case SDL_GAMEPAD_ELEMENT_AXIS_RIGHTX_POSITIVE: | ||
| 1324 | case SDL_GAMEPAD_ELEMENT_AXIS_RIGHTY_NEGATIVE: | ||
| 1325 | case SDL_GAMEPAD_ELEMENT_AXIS_RIGHTY_POSITIVE: | ||
| 1326 | virtual_axis_active = SDL_GAMEPAD_AXIS_RIGHTX; | ||
| 1327 | break; | ||
| 1328 | case SDL_GAMEPAD_ELEMENT_AXIS_LEFT_TRIGGER: | ||
| 1329 | virtual_axis_active = SDL_GAMEPAD_AXIS_LEFT_TRIGGER; | ||
| 1330 | break; | ||
| 1331 | case SDL_GAMEPAD_ELEMENT_AXIS_RIGHT_TRIGGER: | ||
| 1332 | virtual_axis_active = SDL_GAMEPAD_AXIS_RIGHT_TRIGGER; | ||
| 1333 | break; | ||
| 1334 | } | ||
| 1335 | virtual_axis_start_x = x; | ||
| 1336 | virtual_axis_start_y = y; | ||
| 1337 | } | ||
| 1338 | } | ||
| 1339 | |||
| 1340 | static void VirtualGamepadMouseUp(float x, float y) | ||
| 1341 | { | ||
| 1342 | if (virtual_button_active != SDL_GAMEPAD_BUTTON_INVALID) { | ||
| 1343 | SDL_SetJoystickVirtualButton(virtual_joystick, virtual_button_active, false); | ||
| 1344 | virtual_button_active = SDL_GAMEPAD_BUTTON_INVALID; | ||
| 1345 | } | ||
| 1346 | |||
| 1347 | if (virtual_axis_active != SDL_GAMEPAD_AXIS_INVALID) { | ||
| 1348 | if (virtual_axis_active == SDL_GAMEPAD_AXIS_LEFT_TRIGGER || | ||
| 1349 | virtual_axis_active == SDL_GAMEPAD_AXIS_RIGHT_TRIGGER) { | ||
| 1350 | SDL_SetJoystickVirtualAxis(virtual_joystick, virtual_axis_active, SDL_JOYSTICK_AXIS_MIN); | ||
| 1351 | } else { | ||
| 1352 | SDL_SetJoystickVirtualAxis(virtual_joystick, virtual_axis_active, 0); | ||
| 1353 | SDL_SetJoystickVirtualAxis(virtual_joystick, virtual_axis_active + 1, 0); | ||
| 1354 | } | ||
| 1355 | virtual_axis_active = SDL_GAMEPAD_AXIS_INVALID; | ||
| 1356 | } | ||
| 1357 | |||
| 1358 | if (virtual_touchpad_active) { | ||
| 1359 | SDL_SetJoystickVirtualTouchpad(virtual_joystick, 0, 0, false, virtual_touchpad_x, virtual_touchpad_y, 0.0f); | ||
| 1360 | virtual_touchpad_active = false; | ||
| 1361 | } | ||
| 1362 | } | ||
| 1363 | |||
| 1364 | static void DrawGamepadWaiting(SDL_Renderer *renderer) | ||
| 1365 | { | ||
| 1366 | const char *text = "Waiting for gamepad, press A to add a virtual controller"; | ||
| 1367 | float x, y; | ||
| 1368 | |||
| 1369 | x = SCREEN_WIDTH / 2 - (FONT_CHARACTER_SIZE * SDL_strlen(text)) / 2; | ||
| 1370 | y = TITLE_HEIGHT / 2 - FONT_CHARACTER_SIZE / 2; | ||
| 1371 | SDLTest_DrawString(renderer, x, y, text); | ||
| 1372 | } | ||
| 1373 | |||
| 1374 | static void DrawGamepadInfo(SDL_Renderer *renderer) | ||
| 1375 | { | ||
| 1376 | const char *type; | ||
| 1377 | const char *serial; | ||
| 1378 | char text[128]; | ||
| 1379 | float x, y; | ||
| 1380 | |||
| 1381 | if (title_highlighted) { | ||
| 1382 | Uint8 r, g, b, a; | ||
| 1383 | |||
| 1384 | SDL_GetRenderDrawColor(renderer, &r, &g, &b, &a); | ||
| 1385 | |||
| 1386 | if (title_pressed) { | ||
| 1387 | SDL_SetRenderDrawColor(renderer, PRESSED_COLOR); | ||
| 1388 | } else { | ||
| 1389 | SDL_SetRenderDrawColor(renderer, HIGHLIGHT_COLOR); | ||
| 1390 | } | ||
| 1391 | SDL_RenderFillRect(renderer, &title_area); | ||
| 1392 | |||
| 1393 | SDL_SetRenderDrawColor(renderer, r, g, b, a); | ||
| 1394 | } | ||
| 1395 | |||
| 1396 | if (type_highlighted) { | ||
| 1397 | Uint8 r, g, b, a; | ||
| 1398 | |||
| 1399 | SDL_GetRenderDrawColor(renderer, &r, &g, &b, &a); | ||
| 1400 | |||
| 1401 | if (type_pressed) { | ||
| 1402 | SDL_SetRenderDrawColor(renderer, PRESSED_COLOR); | ||
| 1403 | } else { | ||
| 1404 | SDL_SetRenderDrawColor(renderer, HIGHLIGHT_COLOR); | ||
| 1405 | } | ||
| 1406 | SDL_RenderFillRect(renderer, &type_area); | ||
| 1407 | |||
| 1408 | SDL_SetRenderDrawColor(renderer, r, g, b, a); | ||
| 1409 | } | ||
| 1410 | |||
| 1411 | if (controller->joystick) { | ||
| 1412 | SDL_snprintf(text, sizeof(text), "(%" SDL_PRIu32 ")", SDL_GetJoystickID(controller->joystick)); | ||
| 1413 | x = SCREEN_WIDTH - (FONT_CHARACTER_SIZE * SDL_strlen(text)) - 8.0f; | ||
| 1414 | y = 8.0f; | ||
| 1415 | SDLTest_DrawString(renderer, x, y, text); | ||
| 1416 | } | ||
| 1417 | |||
| 1418 | if (controller_name && *controller_name) { | ||
| 1419 | x = title_area.x + title_area.w / 2 - (FONT_CHARACTER_SIZE * SDL_strlen(controller_name)) / 2; | ||
| 1420 | y = title_area.y + title_area.h / 2 - FONT_CHARACTER_SIZE / 2; | ||
| 1421 | SDLTest_DrawString(renderer, x, y, controller_name); | ||
| 1422 | } | ||
| 1423 | |||
| 1424 | if (SDL_IsJoystickVirtual(controller->id)) { | ||
| 1425 | SDL_strlcpy(text, "Click on the gamepad image below to generate input", sizeof(text)); | ||
| 1426 | x = SCREEN_WIDTH / 2 - (FONT_CHARACTER_SIZE * SDL_strlen(text)) / 2; | ||
| 1427 | y = TITLE_HEIGHT / 2 - FONT_CHARACTER_SIZE / 2 + FONT_LINE_HEIGHT + 2.0f; | ||
| 1428 | SDLTest_DrawString(renderer, x, y, text); | ||
| 1429 | } | ||
| 1430 | |||
| 1431 | type = GetGamepadTypeString(SDL_GetGamepadType(controller->gamepad)); | ||
| 1432 | x = type_area.x + type_area.w / 2 - (FONT_CHARACTER_SIZE * SDL_strlen(type)) / 2; | ||
| 1433 | y = type_area.y + type_area.h / 2 - FONT_CHARACTER_SIZE / 2; | ||
| 1434 | SDLTest_DrawString(renderer, x, y, type); | ||
| 1435 | |||
| 1436 | if (display_mode == CONTROLLER_MODE_TESTING) { | ||
| 1437 | Uint64 steam_handle = SDL_GetGamepadSteamHandle(controller->gamepad); | ||
| 1438 | if (steam_handle) { | ||
| 1439 | SDL_snprintf(text, SDL_arraysize(text), "Steam: 0x%.16" SDL_PRIx64, steam_handle); | ||
| 1440 | y = SCREEN_HEIGHT - 2 * (8.0f + FONT_LINE_HEIGHT); | ||
| 1441 | x = SCREEN_WIDTH - 8.0f - (FONT_CHARACTER_SIZE * SDL_strlen(text)); | ||
| 1442 | SDLTest_DrawString(renderer, x, y, text); | ||
| 1443 | } | ||
| 1444 | |||
| 1445 | SDL_snprintf(text, SDL_arraysize(text), "VID: 0x%.4x PID: 0x%.4x", | ||
| 1446 | SDL_GetJoystickVendor(controller->joystick), | ||
| 1447 | SDL_GetJoystickProduct(controller->joystick)); | ||
| 1448 | y = SCREEN_HEIGHT - 8.0f - FONT_LINE_HEIGHT; | ||
| 1449 | x = SCREEN_WIDTH - 8.0f - (FONT_CHARACTER_SIZE * SDL_strlen(text)); | ||
| 1450 | SDLTest_DrawString(renderer, x, y, text); | ||
| 1451 | |||
| 1452 | serial = SDL_GetJoystickSerial(controller->joystick); | ||
| 1453 | if (serial && *serial) { | ||
| 1454 | SDL_snprintf(text, SDL_arraysize(text), "Serial: %s", serial); | ||
| 1455 | x = SCREEN_WIDTH / 2 - (FONT_CHARACTER_SIZE * SDL_strlen(text)) / 2; | ||
| 1456 | y = SCREEN_HEIGHT - 8.0f - FONT_LINE_HEIGHT; | ||
| 1457 | SDLTest_DrawString(renderer, x, y, text); | ||
| 1458 | } | ||
| 1459 | } | ||
| 1460 | } | ||
| 1461 | |||
| 1462 | static const char *GetButtonLabel(SDL_GamepadType type, SDL_GamepadButton button) | ||
| 1463 | { | ||
| 1464 | switch (SDL_GetGamepadButtonLabelForType(type, button)) { | ||
| 1465 | case SDL_GAMEPAD_BUTTON_LABEL_A: | ||
| 1466 | return "A"; | ||
| 1467 | case SDL_GAMEPAD_BUTTON_LABEL_B: | ||
| 1468 | return "B"; | ||
| 1469 | case SDL_GAMEPAD_BUTTON_LABEL_X: | ||
| 1470 | return "X"; | ||
| 1471 | case SDL_GAMEPAD_BUTTON_LABEL_Y: | ||
| 1472 | return "Y"; | ||
| 1473 | case SDL_GAMEPAD_BUTTON_LABEL_CROSS: | ||
| 1474 | return "Cross (X)"; | ||
| 1475 | case SDL_GAMEPAD_BUTTON_LABEL_CIRCLE: | ||
| 1476 | return "Circle"; | ||
| 1477 | case SDL_GAMEPAD_BUTTON_LABEL_SQUARE: | ||
| 1478 | return "Square"; | ||
| 1479 | case SDL_GAMEPAD_BUTTON_LABEL_TRIANGLE: | ||
| 1480 | return "Triangle"; | ||
| 1481 | default: | ||
| 1482 | return "UNKNOWN"; | ||
| 1483 | } | ||
| 1484 | } | ||
| 1485 | |||
| 1486 | static void DrawBindingTips(SDL_Renderer *renderer) | ||
| 1487 | { | ||
| 1488 | const char *text; | ||
| 1489 | SDL_FRect image_area, button_area; | ||
| 1490 | float x, y; | ||
| 1491 | |||
| 1492 | GetGamepadImageArea(image, &image_area); | ||
| 1493 | GetGamepadButtonArea(done_mapping_button, &button_area); | ||
| 1494 | x = image_area.x + image_area.w / 2; | ||
| 1495 | y = image_area.y + image_area.h; | ||
| 1496 | y += (button_area.y - y - FONT_CHARACTER_SIZE) / 2; | ||
| 1497 | |||
| 1498 | text = GetBindingInstruction(); | ||
| 1499 | |||
| 1500 | if (binding_element == SDL_GAMEPAD_ELEMENT_INVALID) { | ||
| 1501 | SDLTest_DrawString(renderer, x - (FONT_CHARACTER_SIZE * SDL_strlen(text)) / 2, y, text); | ||
| 1502 | } else { | ||
| 1503 | Uint8 r, g, b, a; | ||
| 1504 | SDL_FRect rect; | ||
| 1505 | SDL_GamepadButton action_forward = SDL_GAMEPAD_BUTTON_SOUTH; | ||
| 1506 | bool bound_forward = MappingHasElement(controller->mapping, action_forward); | ||
| 1507 | SDL_GamepadButton action_backward = SDL_GAMEPAD_BUTTON_EAST; | ||
| 1508 | bool bound_backward = MappingHasElement(controller->mapping, action_backward); | ||
| 1509 | SDL_GamepadButton action_delete = SDL_GAMEPAD_BUTTON_WEST; | ||
| 1510 | bool bound_delete = MappingHasElement(controller->mapping, action_delete); | ||
| 1511 | |||
| 1512 | y -= (FONT_CHARACTER_SIZE + BUTTON_MARGIN) / 2; | ||
| 1513 | |||
| 1514 | rect.w = 2.0f + (FONT_CHARACTER_SIZE * SDL_strlen(text)) + 2.0f; | ||
| 1515 | rect.h = 2.0f + FONT_CHARACTER_SIZE + 2.0f; | ||
| 1516 | rect.x = x - rect.w / 2; | ||
| 1517 | rect.y = y - 2.0f; | ||
| 1518 | |||
| 1519 | SDL_GetRenderDrawColor(renderer, &r, &g, &b, &a); | ||
| 1520 | SDL_SetRenderDrawColor(renderer, SELECTED_COLOR); | ||
| 1521 | SDL_RenderFillRect(renderer, &rect); | ||
| 1522 | SDL_SetRenderDrawColor(renderer, r, g, b, a); | ||
| 1523 | SDLTest_DrawString(renderer, x - (FONT_CHARACTER_SIZE * SDL_strlen(text)) / 2, y, text); | ||
| 1524 | |||
| 1525 | y += (FONT_CHARACTER_SIZE + BUTTON_MARGIN); | ||
| 1526 | |||
| 1527 | if (binding_element == SDL_GAMEPAD_ELEMENT_NAME) { | ||
| 1528 | text = "(press RETURN to complete)"; | ||
| 1529 | } else if (binding_element == SDL_GAMEPAD_ELEMENT_TYPE || | ||
| 1530 | binding_element == action_forward || | ||
| 1531 | binding_element == action_backward) { | ||
| 1532 | text = "(press ESC to cancel)"; | ||
| 1533 | } else { | ||
| 1534 | static char dynamic_text[128]; | ||
| 1535 | SDL_GamepadType type = GetGamepadImageType(image); | ||
| 1536 | if (binding_flow && bound_forward && bound_backward) { | ||
| 1537 | if (binding_element != action_delete && bound_delete) { | ||
| 1538 | 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)); | ||
| 1539 | } else { | ||
| 1540 | 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)); | ||
| 1541 | } | ||
| 1542 | text = dynamic_text; | ||
| 1543 | } else { | ||
| 1544 | text = "(press SPACE to delete and ESC to cancel)"; | ||
| 1545 | } | ||
| 1546 | } | ||
| 1547 | SDLTest_DrawString(renderer, x - (FONT_CHARACTER_SIZE * SDL_strlen(text)) / 2, y, text); | ||
| 1548 | } | ||
| 1549 | } | ||
| 1550 | |||
| 1551 | static void UpdateGamepadEffects(void) | ||
| 1552 | { | ||
| 1553 | if (display_mode != CONTROLLER_MODE_TESTING || !controller->gamepad) { | ||
| 1554 | return; | ||
| 1555 | } | ||
| 1556 | |||
| 1557 | /* Update LED based on left thumbstick position */ | ||
| 1558 | { | ||
| 1559 | Sint16 x = SDL_GetGamepadAxis(controller->gamepad, SDL_GAMEPAD_AXIS_LEFTX); | ||
| 1560 | Sint16 y = SDL_GetGamepadAxis(controller->gamepad, SDL_GAMEPAD_AXIS_LEFTY); | ||
| 1561 | |||
| 1562 | if (!set_LED) { | ||
| 1563 | set_LED = (x < -8000 || x > 8000 || y > 8000); | ||
| 1564 | } | ||
| 1565 | if (set_LED) { | ||
| 1566 | Uint8 r, g, b; | ||
| 1567 | |||
| 1568 | if (x < 0) { | ||
| 1569 | r = (Uint8)(((~x) * 255) / 32767); | ||
| 1570 | b = 0; | ||
| 1571 | } else { | ||
| 1572 | r = 0; | ||
| 1573 | b = (Uint8)(((int)(x)*255) / 32767); | ||
| 1574 | } | ||
| 1575 | if (y > 0) { | ||
| 1576 | g = (Uint8)(((int)(y)*255) / 32767); | ||
| 1577 | } else { | ||
| 1578 | g = 0; | ||
| 1579 | } | ||
| 1580 | |||
| 1581 | SDL_SetGamepadLED(controller->gamepad, r, g, b); | ||
| 1582 | } | ||
| 1583 | } | ||
| 1584 | |||
| 1585 | if (controller->trigger_effect == 0) { | ||
| 1586 | /* Update rumble based on trigger state */ | ||
| 1587 | { | ||
| 1588 | Sint16 left = SDL_GetGamepadAxis(controller->gamepad, SDL_GAMEPAD_AXIS_LEFT_TRIGGER); | ||
| 1589 | Sint16 right = SDL_GetGamepadAxis(controller->gamepad, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER); | ||
| 1590 | Uint16 low_frequency_rumble = ConvertAxisToRumble(left); | ||
| 1591 | Uint16 high_frequency_rumble = ConvertAxisToRumble(right); | ||
| 1592 | SDL_RumbleGamepad(controller->gamepad, low_frequency_rumble, high_frequency_rumble, 250); | ||
| 1593 | } | ||
| 1594 | |||
| 1595 | /* Update trigger rumble based on thumbstick state */ | ||
| 1596 | { | ||
| 1597 | Sint16 left = SDL_GetGamepadAxis(controller->gamepad, SDL_GAMEPAD_AXIS_LEFTY); | ||
| 1598 | Sint16 right = SDL_GetGamepadAxis(controller->gamepad, SDL_GAMEPAD_AXIS_RIGHTY); | ||
| 1599 | Uint16 left_rumble = ConvertAxisToRumble(~left); | ||
| 1600 | Uint16 right_rumble = ConvertAxisToRumble(~right); | ||
| 1601 | |||
| 1602 | SDL_RumbleGamepadTriggers(controller->gamepad, left_rumble, right_rumble, 250); | ||
| 1603 | } | ||
| 1604 | } | ||
| 1605 | } | ||
| 1606 | |||
| 1607 | SDL_AppResult SDLCALL SDL_AppEvent(void *appstate, SDL_Event *event) | ||
| 1608 | { | ||
| 1609 | SDL_ConvertEventToRenderCoordinates(screen, event); | ||
| 1610 | |||
| 1611 | switch (event->type) { | ||
| 1612 | case SDL_EVENT_JOYSTICK_ADDED: | ||
| 1613 | AddController(event->jdevice.which, true); | ||
| 1614 | break; | ||
| 1615 | |||
| 1616 | case SDL_EVENT_JOYSTICK_REMOVED: | ||
| 1617 | DelController(event->jdevice.which); | ||
| 1618 | break; | ||
| 1619 | |||
| 1620 | case SDL_EVENT_JOYSTICK_AXIS_MOTION: | ||
| 1621 | if (display_mode == CONTROLLER_MODE_TESTING) { | ||
| 1622 | if (event->jaxis.value <= (-SDL_JOYSTICK_AXIS_MAX / 2) || event->jaxis.value >= (SDL_JOYSTICK_AXIS_MAX / 2)) { | ||
| 1623 | SetController(event->jaxis.which); | ||
| 1624 | } | ||
| 1625 | } else if (display_mode == CONTROLLER_MODE_BINDING && | ||
| 1626 | event->jaxis.which == controller->id && | ||
| 1627 | event->jaxis.axis < controller->num_axes && | ||
| 1628 | binding_element != SDL_GAMEPAD_ELEMENT_INVALID) { | ||
| 1629 | const int MAX_ALLOWED_JITTER = SDL_JOYSTICK_AXIS_MAX / 80; /* ShanWan PS3 gamepad needed 96 */ | ||
| 1630 | AxisState *pAxisState = &controller->axis_state[event->jaxis.axis]; | ||
| 1631 | int nValue = event->jaxis.value; | ||
| 1632 | int nCurrentDistance, nFarthestDistance; | ||
| 1633 | if (!pAxisState->m_bMoving) { | ||
| 1634 | Sint16 nInitialValue; | ||
| 1635 | pAxisState->m_bMoving = SDL_GetJoystickAxisInitialState(controller->joystick, event->jaxis.axis, &nInitialValue); | ||
| 1636 | pAxisState->m_nLastValue = nValue; | ||
| 1637 | pAxisState->m_nStartingValue = nInitialValue; | ||
| 1638 | pAxisState->m_nFarthestValue = nInitialValue; | ||
| 1639 | } else if (SDL_abs(nValue - pAxisState->m_nLastValue) <= MAX_ALLOWED_JITTER) { | ||
| 1640 | break; | ||
| 1641 | } else { | ||
| 1642 | pAxisState->m_nLastValue = nValue; | ||
| 1643 | } | ||
| 1644 | nCurrentDistance = SDL_abs(nValue - pAxisState->m_nStartingValue); | ||
| 1645 | nFarthestDistance = SDL_abs(pAxisState->m_nFarthestValue - pAxisState->m_nStartingValue); | ||
| 1646 | if (nCurrentDistance > nFarthestDistance) { | ||
| 1647 | pAxisState->m_nFarthestValue = nValue; | ||
| 1648 | nFarthestDistance = SDL_abs(pAxisState->m_nFarthestValue - pAxisState->m_nStartingValue); | ||
| 1649 | } | ||
| 1650 | |||
| 1651 | #ifdef DEBUG_AXIS_MAPPING | ||
| 1652 | SDL_Log("AXIS %d nValue %d nCurrentDistance %d nFarthestDistance %d", event->jaxis.axis, nValue, nCurrentDistance, nFarthestDistance); | ||
| 1653 | #endif | ||
| 1654 | /* If we've gone out far enough and started to come back, let's bind this axis */ | ||
| 1655 | if (nFarthestDistance >= 16000 && nCurrentDistance <= 10000) { | ||
| 1656 | char binding[12]; | ||
| 1657 | int axis_min = StandardizeAxisValue(pAxisState->m_nStartingValue); | ||
| 1658 | int axis_max = StandardizeAxisValue(pAxisState->m_nFarthestValue); | ||
| 1659 | |||
| 1660 | if (axis_min == 0 && axis_max == SDL_JOYSTICK_AXIS_MIN) { | ||
| 1661 | /* The negative half axis */ | ||
| 1662 | (void)SDL_snprintf(binding, sizeof(binding), "-a%d", event->jaxis.axis); | ||
| 1663 | } else if (axis_min == 0 && axis_max == SDL_JOYSTICK_AXIS_MAX) { | ||
| 1664 | /* The positive half axis */ | ||
| 1665 | (void)SDL_snprintf(binding, sizeof(binding), "+a%d", event->jaxis.axis); | ||
| 1666 | } else { | ||
| 1667 | (void)SDL_snprintf(binding, sizeof(binding), "a%d", event->jaxis.axis); | ||
| 1668 | if (axis_min > axis_max) { | ||
| 1669 | /* Invert the axis */ | ||
| 1670 | SDL_strlcat(binding, "~", SDL_arraysize(binding)); | ||
| 1671 | } | ||
| 1672 | } | ||
| 1673 | #ifdef DEBUG_AXIS_MAPPING | ||
| 1674 | SDL_Log("AXIS %d axis_min = %d, axis_max = %d, binding = %s", event->jaxis.axis, axis_min, axis_max, binding); | ||
| 1675 | #endif | ||
| 1676 | CommitBindingElement(binding, false); | ||
| 1677 | } | ||
| 1678 | } | ||
| 1679 | break; | ||
| 1680 | |||
| 1681 | case SDL_EVENT_JOYSTICK_BUTTON_DOWN: | ||
| 1682 | if (display_mode == CONTROLLER_MODE_TESTING) { | ||
| 1683 | SetController(event->jbutton.which); | ||
| 1684 | } | ||
| 1685 | break; | ||
| 1686 | |||
| 1687 | case SDL_EVENT_JOYSTICK_BUTTON_UP: | ||
| 1688 | if (display_mode == CONTROLLER_MODE_BINDING && | ||
| 1689 | event->jbutton.which == controller->id && | ||
| 1690 | binding_element != SDL_GAMEPAD_ELEMENT_INVALID) { | ||
| 1691 | char binding[12]; | ||
| 1692 | |||
| 1693 | SDL_snprintf(binding, sizeof(binding), "b%d", event->jbutton.button); | ||
| 1694 | CommitBindingElement(binding, false); | ||
| 1695 | } | ||
| 1696 | break; | ||
| 1697 | |||
| 1698 | case SDL_EVENT_JOYSTICK_HAT_MOTION: | ||
| 1699 | if (display_mode == CONTROLLER_MODE_BINDING && | ||
| 1700 | event->jhat.which == controller->id && | ||
| 1701 | event->jhat.value != SDL_HAT_CENTERED && | ||
| 1702 | binding_element != SDL_GAMEPAD_ELEMENT_INVALID) { | ||
| 1703 | char binding[12]; | ||
| 1704 | |||
| 1705 | SDL_snprintf(binding, sizeof(binding), "h%d.%d", event->jhat.hat, event->jhat.value); | ||
| 1706 | CommitBindingElement(binding, false); | ||
| 1707 | } | ||
| 1708 | break; | ||
| 1709 | |||
| 1710 | case SDL_EVENT_GAMEPAD_ADDED: | ||
| 1711 | HandleGamepadAdded(event->gdevice.which, true); | ||
| 1712 | break; | ||
| 1713 | |||
| 1714 | case SDL_EVENT_GAMEPAD_REMOVED: | ||
| 1715 | HandleGamepadRemoved(event->gdevice.which); | ||
| 1716 | break; | ||
| 1717 | |||
| 1718 | case SDL_EVENT_GAMEPAD_REMAPPED: | ||
| 1719 | HandleGamepadRemapped(event->gdevice.which); | ||
| 1720 | break; | ||
| 1721 | |||
| 1722 | case SDL_EVENT_GAMEPAD_STEAM_HANDLE_UPDATED: | ||
| 1723 | RefreshControllerName(); | ||
| 1724 | break; | ||
| 1725 | |||
| 1726 | #ifdef VERBOSE_TOUCHPAD | ||
| 1727 | case SDL_EVENT_GAMEPAD_TOUCHPAD_DOWN: | ||
| 1728 | case SDL_EVENT_GAMEPAD_TOUCHPAD_MOTION: | ||
| 1729 | case SDL_EVENT_GAMEPAD_TOUCHPAD_UP: | ||
| 1730 | SDL_Log("Gamepad %" SDL_PRIu32 " touchpad %" SDL_PRIs32 " finger %" SDL_PRIs32 " %s %.2f, %.2f, %.2f", | ||
| 1731 | event->gtouchpad.which, | ||
| 1732 | event->gtouchpad.touchpad, | ||
| 1733 | event->gtouchpad.finger, | ||
| 1734 | (event->type == SDL_EVENT_GAMEPAD_TOUCHPAD_DOWN ? "pressed at" : (event->type == SDL_EVENT_GAMEPAD_TOUCHPAD_UP ? "released at" : "moved to")), | ||
| 1735 | event->gtouchpad.x, | ||
| 1736 | event->gtouchpad.y, | ||
| 1737 | event->gtouchpad.pressure); | ||
| 1738 | break; | ||
| 1739 | #endif /* VERBOSE_TOUCHPAD */ | ||
| 1740 | |||
| 1741 | #ifdef VERBOSE_SENSORS | ||
| 1742 | case SDL_EVENT_GAMEPAD_SENSOR_UPDATE: | ||
| 1743 | SDL_Log("Gamepad %" SDL_PRIu32 " sensor %s: %.2f, %.2f, %.2f (%" SDL_PRIu64 ")", | ||
| 1744 | event->gsensor.which, | ||
| 1745 | GetSensorName((SDL_SensorType) event->gsensor.sensor), | ||
| 1746 | event->gsensor.data[0], | ||
| 1747 | event->gsensor.data[1], | ||
| 1748 | event->gsensor.data[2], | ||
| 1749 | event->gsensor.sensor_timestamp); | ||
| 1750 | break; | ||
| 1751 | #endif /* VERBOSE_SENSORS */ | ||
| 1752 | |||
| 1753 | #ifdef VERBOSE_AXES | ||
| 1754 | case SDL_EVENT_GAMEPAD_AXIS_MOTION: | ||
| 1755 | if (display_mode == CONTROLLER_MODE_TESTING) { | ||
| 1756 | if (event->gaxis.value <= (-SDL_JOYSTICK_AXIS_MAX / 2) || event->gaxis.value >= (SDL_JOYSTICK_AXIS_MAX / 2)) { | ||
| 1757 | SetController(event->gaxis.which); | ||
| 1758 | } | ||
| 1759 | } | ||
| 1760 | SDL_Log("Gamepad %" SDL_PRIu32 " axis %s changed to %d", | ||
| 1761 | event->gaxis.which, | ||
| 1762 | SDL_GetGamepadStringForAxis((SDL_GamepadAxis) event->gaxis.axis), | ||
| 1763 | event->gaxis.value); | ||
| 1764 | break; | ||
| 1765 | #endif /* VERBOSE_AXES */ | ||
| 1766 | |||
| 1767 | case SDL_EVENT_GAMEPAD_BUTTON_DOWN: | ||
| 1768 | case SDL_EVENT_GAMEPAD_BUTTON_UP: | ||
| 1769 | if (display_mode == CONTROLLER_MODE_TESTING) { | ||
| 1770 | if (event->type == SDL_EVENT_GAMEPAD_BUTTON_DOWN) { | ||
| 1771 | SetController(event->gbutton.which); | ||
| 1772 | } | ||
| 1773 | } | ||
| 1774 | #ifdef VERBOSE_BUTTONS | ||
| 1775 | SDL_Log("Gamepad %" SDL_PRIu32 " button %s %s", | ||
| 1776 | event->gbutton.which, | ||
| 1777 | SDL_GetGamepadStringForButton((SDL_GamepadButton) event->gbutton.button), | ||
| 1778 | event->gbutton.state ? "pressed" : "released"); | ||
| 1779 | #endif /* VERBOSE_BUTTONS */ | ||
| 1780 | |||
| 1781 | if (display_mode == CONTROLLER_MODE_TESTING) { | ||
| 1782 | if (event->type == SDL_EVENT_GAMEPAD_BUTTON_DOWN && | ||
| 1783 | controller && SDL_GetGamepadType(controller->gamepad) == SDL_GAMEPAD_TYPE_PS5) { | ||
| 1784 | /* Cycle PS5 audio routing when the microphone button is pressed */ | ||
| 1785 | if (event->gbutton.button == SDL_GAMEPAD_BUTTON_MISC1) { | ||
| 1786 | CyclePS5AudioRoute(controller); | ||
| 1787 | } | ||
| 1788 | |||
| 1789 | /* Cycle PS5 trigger effects when the triangle button is pressed */ | ||
| 1790 | if (event->gbutton.button == SDL_GAMEPAD_BUTTON_NORTH) { | ||
| 1791 | CyclePS5TriggerEffect(controller); | ||
| 1792 | } | ||
| 1793 | } | ||
| 1794 | } | ||
| 1795 | break; | ||
| 1796 | |||
| 1797 | case SDL_EVENT_MOUSE_BUTTON_DOWN: | ||
| 1798 | if (virtual_joystick && controller && controller->joystick == virtual_joystick) { | ||
| 1799 | VirtualGamepadMouseDown(event->button.x, event->button.y); | ||
| 1800 | } | ||
| 1801 | UpdateButtonHighlights(event->button.x, event->button.y, event->button.down); | ||
| 1802 | break; | ||
| 1803 | |||
| 1804 | case SDL_EVENT_MOUSE_BUTTON_UP: | ||
| 1805 | if (virtual_joystick && controller && controller->joystick == virtual_joystick) { | ||
| 1806 | VirtualGamepadMouseUp(event->button.x, event->button.y); | ||
| 1807 | } | ||
| 1808 | |||
| 1809 | if (display_mode == CONTROLLER_MODE_TESTING) { | ||
| 1810 | if (GamepadButtonContains(setup_mapping_button, event->button.x, event->button.y)) { | ||
| 1811 | SetDisplayMode(CONTROLLER_MODE_BINDING); | ||
| 1812 | } | ||
| 1813 | } else if (display_mode == CONTROLLER_MODE_BINDING) { | ||
| 1814 | if (GamepadButtonContains(done_mapping_button, event->button.x, event->button.y)) { | ||
| 1815 | if (controller->mapping) { | ||
| 1816 | SDL_Log("Mapping complete:"); | ||
| 1817 | SDL_Log("%s", controller->mapping); | ||
| 1818 | } | ||
| 1819 | SetDisplayMode(CONTROLLER_MODE_TESTING); | ||
| 1820 | } else if (GamepadButtonContains(cancel_button, event->button.x, event->button.y)) { | ||
| 1821 | CancelMapping(); | ||
| 1822 | } else if (GamepadButtonContains(clear_button, event->button.x, event->button.y)) { | ||
| 1823 | ClearMapping(); | ||
| 1824 | } else if (controller->has_bindings && | ||
| 1825 | GamepadButtonContains(copy_button, event->button.x, event->button.y)) { | ||
| 1826 | CopyMapping(); | ||
| 1827 | } else if (GamepadButtonContains(paste_button, event->button.x, event->button.y)) { | ||
| 1828 | PasteMapping(); | ||
| 1829 | } else if (title_pressed) { | ||
| 1830 | SetCurrentBindingElement(SDL_GAMEPAD_ELEMENT_NAME, false); | ||
| 1831 | } else if (type_pressed) { | ||
| 1832 | SetCurrentBindingElement(SDL_GAMEPAD_ELEMENT_TYPE, false); | ||
| 1833 | } else if (binding_element == SDL_GAMEPAD_ELEMENT_TYPE) { | ||
| 1834 | int type = GetGamepadTypeDisplayAt(gamepad_type, event->button.x, event->button.y); | ||
| 1835 | if (type != SDL_GAMEPAD_TYPE_UNSELECTED) { | ||
| 1836 | CommitGamepadType((SDL_GamepadType)type); | ||
| 1837 | StopBinding(); | ||
| 1838 | } | ||
| 1839 | } else { | ||
| 1840 | int gamepad_element = SDL_GAMEPAD_ELEMENT_INVALID; | ||
| 1841 | char *joystick_element; | ||
| 1842 | |||
| 1843 | if (controller->joystick != virtual_joystick) { | ||
| 1844 | gamepad_element = GetGamepadImageElementAt(image, event->button.x, event->button.y); | ||
| 1845 | } | ||
| 1846 | if (gamepad_element == SDL_GAMEPAD_ELEMENT_INVALID) { | ||
| 1847 | gamepad_element = GetGamepadDisplayElementAt(gamepad_elements, controller->gamepad, event->button.x, event->button.y); | ||
| 1848 | } | ||
| 1849 | if (gamepad_element != SDL_GAMEPAD_ELEMENT_INVALID) { | ||
| 1850 | /* Set this to false if you don't want to start the binding flow at this point */ | ||
| 1851 | const bool should_start_flow = true; | ||
| 1852 | SetCurrentBindingElement(gamepad_element, should_start_flow); | ||
| 1853 | } | ||
| 1854 | |||
| 1855 | joystick_element = GetJoystickDisplayElementAt(joystick_elements, controller->joystick, event->button.x, event->button.y); | ||
| 1856 | if (joystick_element) { | ||
| 1857 | CommitBindingElement(joystick_element, true); | ||
| 1858 | SDL_free(joystick_element); | ||
| 1859 | } | ||
| 1860 | } | ||
| 1861 | } | ||
| 1862 | UpdateButtonHighlights(event->button.x, event->button.y, event->button.down); | ||
| 1863 | break; | ||
| 1864 | |||
| 1865 | case SDL_EVENT_MOUSE_MOTION: | ||
| 1866 | if (virtual_joystick && controller && controller->joystick == virtual_joystick) { | ||
| 1867 | VirtualGamepadMouseMotion(event->motion.x, event->motion.y); | ||
| 1868 | } | ||
| 1869 | UpdateButtonHighlights(event->motion.x, event->motion.y, event->motion.state ? true : false); | ||
| 1870 | break; | ||
| 1871 | |||
| 1872 | case SDL_EVENT_KEY_DOWN: | ||
| 1873 | if (display_mode == CONTROLLER_MODE_TESTING) { | ||
| 1874 | if (event->key.key >= SDLK_0 && event->key.key <= SDLK_9) { | ||
| 1875 | if (controller && controller->gamepad) { | ||
| 1876 | int player_index = (event->key.key - SDLK_0); | ||
| 1877 | |||
| 1878 | SDL_SetGamepadPlayerIndex(controller->gamepad, player_index); | ||
| 1879 | } | ||
| 1880 | break; | ||
| 1881 | } else if (event->key.key == SDLK_A) { | ||
| 1882 | OpenVirtualGamepad(); | ||
| 1883 | } else if (event->key.key == SDLK_D) { | ||
| 1884 | CloseVirtualGamepad(); | ||
| 1885 | } else if (event->key.key == SDLK_R && (event->key.mod & SDL_KMOD_CTRL)) { | ||
| 1886 | SDL_ReloadGamepadMappings(); | ||
| 1887 | } else if (event->key.key == SDLK_ESCAPE) { | ||
| 1888 | done = true; | ||
| 1889 | } | ||
| 1890 | } else if (display_mode == CONTROLLER_MODE_BINDING) { | ||
| 1891 | if (event->key.key == SDLK_C && (event->key.mod & SDL_KMOD_CTRL)) { | ||
| 1892 | if (binding_element == SDL_GAMEPAD_ELEMENT_NAME) { | ||
| 1893 | CopyControllerName(); | ||
| 1894 | } else { | ||
| 1895 | CopyMapping(); | ||
| 1896 | } | ||
| 1897 | } else if (event->key.key == SDLK_V && (event->key.mod & SDL_KMOD_CTRL)) { | ||
| 1898 | if (binding_element == SDL_GAMEPAD_ELEMENT_NAME) { | ||
| 1899 | ClearControllerName(); | ||
| 1900 | PasteControllerName(); | ||
| 1901 | } else { | ||
| 1902 | PasteMapping(); | ||
| 1903 | } | ||
| 1904 | } else if (event->key.key == SDLK_X && (event->key.mod & SDL_KMOD_CTRL)) { | ||
| 1905 | if (binding_element == SDL_GAMEPAD_ELEMENT_NAME) { | ||
| 1906 | CopyControllerName(); | ||
| 1907 | ClearControllerName(); | ||
| 1908 | } else { | ||
| 1909 | CopyMapping(); | ||
| 1910 | ClearMapping(); | ||
| 1911 | } | ||
| 1912 | } else if (event->key.key == SDLK_SPACE) { | ||
| 1913 | if (binding_element != SDL_GAMEPAD_ELEMENT_NAME) { | ||
| 1914 | ClearBinding(); | ||
| 1915 | } | ||
| 1916 | } else if (event->key.key == SDLK_BACKSPACE) { | ||
| 1917 | if (binding_element == SDL_GAMEPAD_ELEMENT_NAME) { | ||
| 1918 | BackspaceControllerName(); | ||
| 1919 | } | ||
| 1920 | } else if (event->key.key == SDLK_RETURN) { | ||
| 1921 | if (binding_element == SDL_GAMEPAD_ELEMENT_NAME) { | ||
| 1922 | StopBinding(); | ||
| 1923 | } | ||
| 1924 | } else if (event->key.key == SDLK_ESCAPE) { | ||
| 1925 | if (binding_element != SDL_GAMEPAD_ELEMENT_INVALID) { | ||
| 1926 | StopBinding(); | ||
| 1927 | } else { | ||
| 1928 | CancelMapping(); | ||
| 1929 | } | ||
| 1930 | } | ||
| 1931 | } | ||
| 1932 | break; | ||
| 1933 | case SDL_EVENT_TEXT_INPUT: | ||
| 1934 | if (display_mode == CONTROLLER_MODE_BINDING) { | ||
| 1935 | if (binding_element == SDL_GAMEPAD_ELEMENT_NAME) { | ||
| 1936 | AddControllerNameText(event->text.text); | ||
| 1937 | } | ||
| 1938 | } | ||
| 1939 | break; | ||
| 1940 | case SDL_EVENT_QUIT: | ||
| 1941 | done = true; | ||
| 1942 | break; | ||
| 1943 | default: | ||
| 1944 | break; | ||
| 1945 | } | ||
| 1946 | |||
| 1947 | if (done) { | ||
| 1948 | return SDL_APP_SUCCESS; | ||
| 1949 | } else { | ||
| 1950 | return SDL_APP_CONTINUE; | ||
| 1951 | } | ||
| 1952 | } | ||
| 1953 | |||
| 1954 | SDL_AppResult SDLCALL SDL_AppIterate(void *appstate) | ||
| 1955 | { | ||
| 1956 | /* If we have a virtual controller, send a virtual accelerometer sensor reading */ | ||
| 1957 | if (virtual_joystick) { | ||
| 1958 | float data[3] = { 0.0f, SDL_STANDARD_GRAVITY, 0.0f }; | ||
| 1959 | SDL_SendJoystickVirtualSensorData(virtual_joystick, SDL_SENSOR_ACCEL, SDL_GetTicksNS(), data, SDL_arraysize(data)); | ||
| 1960 | } | ||
| 1961 | |||
| 1962 | /* Wait 30 ms for joystick events to stop coming in, | ||
| 1963 | in case a gamepad sends multiple events for a single control (e.g. axis and button for trigger) | ||
| 1964 | */ | ||
| 1965 | if (binding_advance_time && SDL_GetTicks() > (binding_advance_time + 30)) { | ||
| 1966 | if (binding_flow) { | ||
| 1967 | SetNextBindingElement(); | ||
| 1968 | } else { | ||
| 1969 | StopBinding(); | ||
| 1970 | } | ||
| 1971 | } | ||
| 1972 | |||
| 1973 | /* blank screen, set up for drawing this frame. */ | ||
| 1974 | SDL_SetRenderDrawColor(screen, 0xFF, 0xFF, 0xFF, SDL_ALPHA_OPAQUE); | ||
| 1975 | SDL_RenderClear(screen); | ||
| 1976 | SDL_SetRenderDrawColor(screen, 0x10, 0x10, 0x10, SDL_ALPHA_OPAQUE); | ||
| 1977 | |||
| 1978 | if (controller) { | ||
| 1979 | SetGamepadImageShowingFront(image, ShowingFront()); | ||
| 1980 | UpdateGamepadImageFromGamepad(image, controller->gamepad); | ||
| 1981 | if (display_mode == CONTROLLER_MODE_BINDING && | ||
| 1982 | binding_element != SDL_GAMEPAD_ELEMENT_INVALID) { | ||
| 1983 | SetGamepadImageElement(image, binding_element, true); | ||
| 1984 | } | ||
| 1985 | RenderGamepadImage(image); | ||
| 1986 | |||
| 1987 | if (binding_element == SDL_GAMEPAD_ELEMENT_TYPE) { | ||
| 1988 | SetGamepadTypeDisplayRealType(gamepad_type, SDL_GetRealGamepadType(controller->gamepad)); | ||
| 1989 | RenderGamepadTypeDisplay(gamepad_type); | ||
| 1990 | } else { | ||
| 1991 | RenderGamepadDisplay(gamepad_elements, controller->gamepad); | ||
| 1992 | } | ||
| 1993 | RenderJoystickDisplay(joystick_elements, controller->joystick); | ||
| 1994 | |||
| 1995 | if (display_mode == CONTROLLER_MODE_TESTING) { | ||
| 1996 | RenderGamepadButton(setup_mapping_button); | ||
| 1997 | } else if (display_mode == CONTROLLER_MODE_BINDING) { | ||
| 1998 | DrawBindingTips(screen); | ||
| 1999 | RenderGamepadButton(done_mapping_button); | ||
| 2000 | RenderGamepadButton(cancel_button); | ||
| 2001 | RenderGamepadButton(clear_button); | ||
| 2002 | if (controller->has_bindings) { | ||
| 2003 | RenderGamepadButton(copy_button); | ||
| 2004 | } | ||
| 2005 | RenderGamepadButton(paste_button); | ||
| 2006 | } | ||
| 2007 | |||
| 2008 | DrawGamepadInfo(screen); | ||
| 2009 | |||
| 2010 | UpdateGamepadEffects(); | ||
| 2011 | } else { | ||
| 2012 | DrawGamepadWaiting(screen); | ||
| 2013 | } | ||
| 2014 | SDL_Delay(16); | ||
| 2015 | SDL_RenderPresent(screen); | ||
| 2016 | |||
| 2017 | return SDL_APP_CONTINUE; | ||
| 2018 | } | ||
| 2019 | |||
| 2020 | SDL_AppResult SDLCALL SDL_AppInit(void **appstate, int argc, char *argv[]) | ||
| 2021 | { | ||
| 2022 | bool show_mappings = false; | ||
| 2023 | int i; | ||
| 2024 | float content_scale; | ||
| 2025 | int screen_width, screen_height; | ||
| 2026 | SDL_FRect area; | ||
| 2027 | int gamepad_index = -1; | ||
| 2028 | |||
| 2029 | /* Initialize test framework */ | ||
| 2030 | state = SDLTest_CommonCreateState(argv, 0); | ||
| 2031 | if (!state) { | ||
| 2032 | return SDL_APP_FAILURE; | ||
| 2033 | } | ||
| 2034 | |||
| 2035 | SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI, "1"); | ||
| 2036 | SDL_SetHint(SDL_HINT_JOYSTICK_ENHANCED_REPORTS, "auto"); | ||
| 2037 | SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_STEAM, "1"); | ||
| 2038 | SDL_SetHint(SDL_HINT_JOYSTICK_ROG_CHAKRAM, "1"); | ||
| 2039 | SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1"); | ||
| 2040 | SDL_SetHint(SDL_HINT_JOYSTICK_LINUX_DEADZONES, "1"); | ||
| 2041 | |||
| 2042 | /* Enable input debug logging */ | ||
| 2043 | SDL_SetLogPriority(SDL_LOG_CATEGORY_INPUT, SDL_LOG_PRIORITY_DEBUG); | ||
| 2044 | |||
| 2045 | /* Parse commandline */ | ||
| 2046 | for (i = 1; i < argc;) { | ||
| 2047 | int consumed; | ||
| 2048 | |||
| 2049 | consumed = SDLTest_CommonArg(state, i); | ||
| 2050 | if (!consumed) { | ||
| 2051 | if (SDL_strcmp(argv[i], "--mappings") == 0) { | ||
| 2052 | show_mappings = true; | ||
| 2053 | consumed = 1; | ||
| 2054 | } else if (SDL_strcmp(argv[i], "--virtual") == 0) { | ||
| 2055 | OpenVirtualGamepad(); | ||
| 2056 | consumed = 1; | ||
| 2057 | } else if (gamepad_index < 0) { | ||
| 2058 | char *endptr = NULL; | ||
| 2059 | gamepad_index = (int)SDL_strtol(argv[i], &endptr, 0); | ||
| 2060 | if (endptr != argv[i] && *endptr == '\0' && gamepad_index >= 0) { | ||
| 2061 | consumed = 1; | ||
| 2062 | } | ||
| 2063 | } | ||
| 2064 | } | ||
| 2065 | if (consumed <= 0) { | ||
| 2066 | static const char *options[] = { "[--mappings]", "[--virtual]", "[index]", NULL }; | ||
| 2067 | SDLTest_CommonLogUsage(state, argv[0], options); | ||
| 2068 | return SDL_APP_FAILURE; | ||
| 2069 | } | ||
| 2070 | |||
| 2071 | i += consumed; | ||
| 2072 | } | ||
| 2073 | if (gamepad_index < 0) { | ||
| 2074 | gamepad_index = 0; | ||
| 2075 | } | ||
| 2076 | |||
| 2077 | /* Initialize SDL (Note: video is required to start event loop) */ | ||
| 2078 | if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK | SDL_INIT_GAMEPAD)) { | ||
| 2079 | SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't initialize SDL: %s", SDL_GetError()); | ||
| 2080 | return SDL_APP_FAILURE; | ||
| 2081 | } | ||
| 2082 | |||
| 2083 | SDL_AddGamepadMappingsFromFile("gamecontrollerdb.txt"); | ||
| 2084 | |||
| 2085 | if (show_mappings) { | ||
| 2086 | int count = 0; | ||
| 2087 | char **mappings = SDL_GetGamepadMappings(&count); | ||
| 2088 | int map_i; | ||
| 2089 | SDL_Log("Supported mappings:"); | ||
| 2090 | for (map_i = 0; map_i < count; ++map_i) { | ||
| 2091 | SDL_Log("\t%s", mappings[map_i]); | ||
| 2092 | } | ||
| 2093 | SDL_Log("%s", ""); | ||
| 2094 | SDL_free(mappings); | ||
| 2095 | } | ||
| 2096 | |||
| 2097 | /* Create a window to display gamepad state */ | ||
| 2098 | content_scale = SDL_GetDisplayContentScale(SDL_GetPrimaryDisplay()); | ||
| 2099 | if (content_scale == 0.0f) { | ||
| 2100 | content_scale = 1.0f; | ||
| 2101 | } | ||
| 2102 | screen_width = (int)SDL_ceilf(SCREEN_WIDTH * content_scale); | ||
| 2103 | screen_height = (int)SDL_ceilf(SCREEN_HEIGHT * content_scale); | ||
| 2104 | window = SDL_CreateWindow("SDL Controller Test", screen_width, screen_height, SDL_WINDOW_HIGH_PIXEL_DENSITY); | ||
| 2105 | if (!window) { | ||
| 2106 | SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't create window: %s", SDL_GetError()); | ||
| 2107 | return SDL_APP_FAILURE; | ||
| 2108 | } | ||
| 2109 | |||
| 2110 | screen = SDL_CreateRenderer(window, NULL); | ||
| 2111 | if (!screen) { | ||
| 2112 | SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't create renderer: %s", SDL_GetError()); | ||
| 2113 | SDL_DestroyWindow(window); | ||
| 2114 | return SDL_APP_FAILURE; | ||
| 2115 | } | ||
| 2116 | |||
| 2117 | SDL_SetRenderDrawColor(screen, 0x00, 0x00, 0x00, SDL_ALPHA_OPAQUE); | ||
| 2118 | SDL_RenderClear(screen); | ||
| 2119 | SDL_RenderPresent(screen); | ||
| 2120 | |||
| 2121 | /* scale for platforms that don't give you the window size you asked for. */ | ||
| 2122 | SDL_SetRenderLogicalPresentation(screen, (int)SCREEN_WIDTH, (int)SCREEN_HEIGHT, | ||
| 2123 | SDL_LOGICAL_PRESENTATION_LETTERBOX); | ||
| 2124 | |||
| 2125 | |||
| 2126 | title_area.w = GAMEPAD_WIDTH; | ||
| 2127 | title_area.h = FONT_CHARACTER_SIZE + 2 * BUTTON_MARGIN; | ||
| 2128 | title_area.x = PANEL_WIDTH + PANEL_SPACING; | ||
| 2129 | title_area.y = TITLE_HEIGHT / 2 - title_area.h / 2; | ||
| 2130 | |||
| 2131 | type_area.w = PANEL_WIDTH - 2 * BUTTON_MARGIN; | ||
| 2132 | type_area.h = FONT_CHARACTER_SIZE + 2 * BUTTON_MARGIN; | ||
| 2133 | type_area.x = BUTTON_MARGIN; | ||
| 2134 | type_area.y = TITLE_HEIGHT / 2 - type_area.h / 2; | ||
| 2135 | |||
| 2136 | image = CreateGamepadImage(screen); | ||
| 2137 | if (!image) { | ||
| 2138 | SDL_DestroyRenderer(screen); | ||
| 2139 | SDL_DestroyWindow(window); | ||
| 2140 | return SDL_APP_FAILURE; | ||
| 2141 | } | ||
| 2142 | SetGamepadImagePosition(image, PANEL_WIDTH + PANEL_SPACING, TITLE_HEIGHT); | ||
| 2143 | |||
| 2144 | gamepad_elements = CreateGamepadDisplay(screen); | ||
| 2145 | area.x = 0; | ||
| 2146 | area.y = TITLE_HEIGHT; | ||
| 2147 | area.w = PANEL_WIDTH; | ||
| 2148 | area.h = GAMEPAD_HEIGHT; | ||
| 2149 | SetGamepadDisplayArea(gamepad_elements, &area); | ||
| 2150 | |||
| 2151 | gamepad_type = CreateGamepadTypeDisplay(screen); | ||
| 2152 | area.x = 0; | ||
| 2153 | area.y = TITLE_HEIGHT; | ||
| 2154 | area.w = PANEL_WIDTH; | ||
| 2155 | area.h = GAMEPAD_HEIGHT; | ||
| 2156 | SetGamepadTypeDisplayArea(gamepad_type, &area); | ||
| 2157 | |||
| 2158 | joystick_elements = CreateJoystickDisplay(screen); | ||
| 2159 | area.x = PANEL_WIDTH + PANEL_SPACING + GAMEPAD_WIDTH + PANEL_SPACING; | ||
| 2160 | area.y = TITLE_HEIGHT; | ||
| 2161 | area.w = PANEL_WIDTH; | ||
| 2162 | area.h = GAMEPAD_HEIGHT; | ||
| 2163 | SetJoystickDisplayArea(joystick_elements, &area); | ||
| 2164 | |||
| 2165 | setup_mapping_button = CreateGamepadButton(screen, "Setup Mapping"); | ||
| 2166 | area.w = SDL_max(MINIMUM_BUTTON_WIDTH, GetGamepadButtonLabelWidth(setup_mapping_button) + 2 * BUTTON_PADDING); | ||
| 2167 | area.h = GetGamepadButtonLabelHeight(setup_mapping_button) + 2 * BUTTON_PADDING; | ||
| 2168 | area.x = BUTTON_MARGIN; | ||
| 2169 | area.y = SCREEN_HEIGHT - BUTTON_MARGIN - area.h; | ||
| 2170 | SetGamepadButtonArea(setup_mapping_button, &area); | ||
| 2171 | |||
| 2172 | cancel_button = CreateGamepadButton(screen, "Cancel"); | ||
| 2173 | area.w = SDL_max(MINIMUM_BUTTON_WIDTH, GetGamepadButtonLabelWidth(cancel_button) + 2 * BUTTON_PADDING); | ||
| 2174 | area.h = GetGamepadButtonLabelHeight(cancel_button) + 2 * BUTTON_PADDING; | ||
| 2175 | area.x = BUTTON_MARGIN; | ||
| 2176 | area.y = SCREEN_HEIGHT - BUTTON_MARGIN - area.h; | ||
| 2177 | SetGamepadButtonArea(cancel_button, &area); | ||
| 2178 | |||
| 2179 | clear_button = CreateGamepadButton(screen, "Clear"); | ||
| 2180 | area.x += area.w + BUTTON_PADDING; | ||
| 2181 | area.w = SDL_max(MINIMUM_BUTTON_WIDTH, GetGamepadButtonLabelWidth(clear_button) + 2 * BUTTON_PADDING); | ||
| 2182 | area.h = GetGamepadButtonLabelHeight(clear_button) + 2 * BUTTON_PADDING; | ||
| 2183 | area.y = SCREEN_HEIGHT - BUTTON_MARGIN - area.h; | ||
| 2184 | SetGamepadButtonArea(clear_button, &area); | ||
| 2185 | |||
| 2186 | copy_button = CreateGamepadButton(screen, "Copy"); | ||
| 2187 | area.x += area.w + BUTTON_PADDING; | ||
| 2188 | area.w = SDL_max(MINIMUM_BUTTON_WIDTH, GetGamepadButtonLabelWidth(copy_button) + 2 * BUTTON_PADDING); | ||
| 2189 | area.h = GetGamepadButtonLabelHeight(copy_button) + 2 * BUTTON_PADDING; | ||
| 2190 | area.y = SCREEN_HEIGHT - BUTTON_MARGIN - area.h; | ||
| 2191 | SetGamepadButtonArea(copy_button, &area); | ||
| 2192 | |||
| 2193 | paste_button = CreateGamepadButton(screen, "Paste"); | ||
| 2194 | area.x += area.w + BUTTON_PADDING; | ||
| 2195 | area.w = SDL_max(MINIMUM_BUTTON_WIDTH, GetGamepadButtonLabelWidth(paste_button) + 2 * BUTTON_PADDING); | ||
| 2196 | area.h = GetGamepadButtonLabelHeight(paste_button) + 2 * BUTTON_PADDING; | ||
| 2197 | area.y = SCREEN_HEIGHT - BUTTON_MARGIN - area.h; | ||
| 2198 | SetGamepadButtonArea(paste_button, &area); | ||
| 2199 | |||
| 2200 | done_mapping_button = CreateGamepadButton(screen, "Done"); | ||
| 2201 | area.w = SDL_max(MINIMUM_BUTTON_WIDTH, GetGamepadButtonLabelWidth(done_mapping_button) + 2 * BUTTON_PADDING); | ||
| 2202 | area.h = GetGamepadButtonLabelHeight(done_mapping_button) + 2 * BUTTON_PADDING; | ||
| 2203 | area.x = SCREEN_WIDTH / 2 - area.w / 2; | ||
| 2204 | area.y = SCREEN_HEIGHT - BUTTON_MARGIN - area.h; | ||
| 2205 | SetGamepadButtonArea(done_mapping_button, &area); | ||
| 2206 | |||
| 2207 | /* Process the initial gamepad list */ | ||
| 2208 | SDL_AppIterate(NULL); | ||
| 2209 | |||
| 2210 | if (gamepad_index < num_controllers) { | ||
| 2211 | SetController(controllers[gamepad_index].id); | ||
| 2212 | } else if (num_controllers > 0) { | ||
| 2213 | SetController(controllers[0].id); | ||
| 2214 | } | ||
| 2215 | |||
| 2216 | return SDL_APP_CONTINUE; | ||
| 2217 | } | ||
| 2218 | |||
| 2219 | void SDLCALL SDL_AppQuit(void *appstate, SDL_AppResult result) | ||
| 2220 | { | ||
| 2221 | CloseVirtualGamepad(); | ||
| 2222 | while (num_controllers > 0) { | ||
| 2223 | HandleGamepadRemoved(controllers[0].id); | ||
| 2224 | DelController(controllers[0].id); | ||
| 2225 | } | ||
| 2226 | SDL_free(controllers); | ||
| 2227 | SDL_free(controller_name); | ||
| 2228 | DestroyGamepadImage(image); | ||
| 2229 | DestroyGamepadDisplay(gamepad_elements); | ||
| 2230 | DestroyGamepadTypeDisplay(gamepad_type); | ||
| 2231 | DestroyJoystickDisplay(joystick_elements); | ||
| 2232 | DestroyGamepadButton(setup_mapping_button); | ||
| 2233 | DestroyGamepadButton(done_mapping_button); | ||
| 2234 | DestroyGamepadButton(cancel_button); | ||
| 2235 | DestroyGamepadButton(clear_button); | ||
| 2236 | DestroyGamepadButton(copy_button); | ||
| 2237 | DestroyGamepadButton(paste_button); | ||
| 2238 | SDLTest_CleanupTextDrawing(); | ||
| 2239 | SDL_DestroyRenderer(screen); | ||
| 2240 | SDL_DestroyWindow(window); | ||
| 2241 | SDL_Quit(); | ||
| 2242 | SDLTest_CommonDestroyState(state); | ||
| 2243 | } | ||
