Atlas - testcontroller.c
Home / ext / SDL / test Lines: 1 | Size: 101223 bytes [Download] [Show on GitHub] [Search similar files] [Raw] [Raw (proxy)][FILE BEGIN]1/* 2 Copyright (C) 1997-2026 Sam Lantinga <[email protected]> 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 GAMEPAD_WIDTH 512.0f 36#define GAMEPAD_HEIGHT 560.0f 37#define BUTTON_MARGIN 16.0f 38#define SCREEN_WIDTH (PANEL_WIDTH + PANEL_SPACING + GAMEPAD_WIDTH + PANEL_SPACING + PANEL_WIDTH) 39#define SCREEN_HEIGHT (TITLE_HEIGHT + GAMEPAD_HEIGHT) 40 41typedef struct 42{ 43 bool m_bMoving; 44 int m_nLastValue; 45 int m_nStartingValue; 46 int m_nFarthestValue; 47} AxisState; 48 49struct Quaternion 50{ 51 float x, y, z, w; 52}; 53 54static Quaternion quat_identity = { 0.0f, 0.0f, 0.0f, 1.0f }; 55 56Quaternion QuaternionFromEuler(float pitch, float yaw, float roll) 57{ 58 float cx = SDL_cosf(pitch * 0.5f); 59 float sx = SDL_sinf(pitch * 0.5f); 60 float cy = SDL_cosf(yaw * 0.5f); 61 float sy = SDL_sinf(yaw * 0.5f); 62 float cz = SDL_cosf(roll * 0.5f); 63 float sz = SDL_sinf(roll * 0.5f); 64 65 Quaternion q; 66 q.w = cx * cy * cz + sx * sy * sz; 67 q.x = sx * cy * cz - cx * sy * sz; 68 q.y = cx * sy * cz + sx * cy * sz; 69 q.z = cx * cy * sz - sx * sy * cz; 70 71 return q; 72} 73 74#define RAD_TO_DEG (180.0f / SDL_PI_F) 75 76/* Decomposes quaternion into Yaw (Y), Pitch (X), Roll (Z) using Y-X-Z order in a left-handed system */ 77void QuaternionToYXZ(Quaternion q, float *pitch, float *yaw, float *roll) 78{ 79 /* Precalculate repeated expressions */ 80 float qxx = q.x * q.x; 81 float qyy = q.y * q.y; 82 float qzz = q.z * q.z; 83 84 float qxy = q.x * q.y; 85 float qxz = q.x * q.z; 86 float qyz = q.y * q.z; 87 float qwx = q.w * q.x; 88 float qwy = q.w * q.y; 89 float qwz = q.w * q.z; 90 91 /* Yaw (around Y) */ 92 if (yaw) { 93 *yaw = SDL_atan2f(2.0f * (qwy + qxz), 1.0f - 2.0f * (qyy + qzz)) * RAD_TO_DEG; 94 } 95 96 /* Pitch (around X) */ 97 float sinp = 2.0f * (qwx - qyz); 98 if (pitch) { 99 if (SDL_fabsf(sinp) >= 1.0f) { 100 *pitch = SDL_copysignf(90.0f, sinp); /* Clamp to avoid domain error */ 101 } else { 102 *pitch = SDL_asinf(sinp) * RAD_TO_DEG; 103 } 104 } 105 106 /* Roll (around Z) */ 107 if (roll) { 108 *roll = SDL_atan2f(2.0f * (qwz + qxy), 1.0f - 2.0f * (qxx + qzz)) * RAD_TO_DEG; 109 } 110} 111 112Quaternion MultiplyQuaternion(Quaternion a, Quaternion b) 113{ 114 Quaternion q; 115 q.x = a.x * b.w + a.y * b.z - a.z * b.y + a.w * b.x; 116 q.y = -a.x * b.z + a.y * b.w + a.z * b.x + a.w * b.y; 117 q.z = a.x * b.y - a.y * b.x + a.z * b.w + a.w * b.z; 118 q.w = -a.x * b.x - a.y * b.y - a.z * b.z + a.w * b.w; 119 return q; 120} 121 122void NormalizeQuaternion(Quaternion *q) 123{ 124 float mag = SDL_sqrtf(q->x * q->x + q->y * q->y + q->z * q->z + q->w * q->w); 125 if (mag > 0.0f) { 126 q->x /= mag; 127 q->y /= mag; 128 q->z /= mag; 129 q->w /= mag; 130 } 131} 132 133float Normalize180(float angle) 134{ 135 angle = SDL_fmodf(angle + 180.0f, 360.0f); 136 if (angle < 0.0f) { 137 angle += 360.0f; 138 } 139 return angle - 180.0f; 140} 141 142typedef struct 143{ 144 Uint64 gyro_packet_number; 145 Uint64 accelerometer_packet_number; 146 /* When both gyro and accelerometer events have been processed, we can increment this and use it to calculate polling rate over time.*/ 147 Uint64 imu_packet_counter; 148 149 Uint64 starting_time_stamp_ns; /* Use this to help estimate how many packets are received over a duration */ 150 Uint16 imu_estimated_sensor_rate; /* in Hz, used to estimate how many packets are received over a duration */ 151 152 Uint64 last_sensor_time_stamp_ns;/* Comes from the event data/HID implementation. Official PS5/Edge gives true hardware time stamps. Others are simulated. Nanoseconds i.e. 1e9 */ 153 154 /* Fresh data copied from sensor events. */ 155 float accel_data[3]; /* Meters per second squared, i.e. 9.81f means 9.81 meters per second squared */ 156 float gyro_data[3]; /* Degrees per second, i.e. 100.0f means 100 degrees per second */ 157 158 float last_accel_data[3];/* Needed to detect motion (and inhibit drift calibration) */ 159 float accelerometer_length_squared; /* The current length squared from last packet to this packet */ 160 float accelerometer_tolerance_squared; /* In phase one of calibration we calculate this as the largest accelerometer_length_squared over the time period */ 161 162 float gyro_drift_accumulator[3]; 163 164 EGyroCalibrationPhase calibration_phase; /* [ GYRO_CALIBRATION_PHASE_OFF, GYRO_CALIBRATION_PHASE_NOISE_PROFILING, GYRO_CALIBRATION_PHASE_DRIFT_PROFILING,GYRO_CALIBRATION_PHASE_COMPLETE ] */ 165 Uint64 calibration_phase_start_time_ticks_ns; /* Set each time a calibration phase begins so that we can a real time number for evaluation of drift. Previously we would use a fixed number of packets but given that gyro polling rates vary wildly this made the duration very different. */ 166 167 int gyro_drift_sample_count; 168 float gyro_drift_solution[3]; /* Non zero if calibration is complete. */ 169 170 Quaternion integrated_rotation; /* Used to help test whether the time stamps and gyro degrees per second are set up correctly by the HID implementation */ 171} IMUState; 172 173/* First stage of calibration - get the noise profile of the accelerometer */ 174void BeginNoiseCalibrationPhase(IMUState *imustate) 175{ 176 imustate->accelerometer_tolerance_squared = ACCELEROMETER_NOISE_THRESHOLD; 177 imustate->calibration_phase = GYRO_CALIBRATION_PHASE_NOISE_PROFILING; 178 imustate->calibration_phase_start_time_ticks_ns = SDL_GetTicksNS(); 179} 180 181/* Reset the Drift calculation state */ 182void BeginDriftCalibrationPhase(IMUState *imustate) 183{ 184 imustate->calibration_phase = GYRO_CALIBRATION_PHASE_DRIFT_PROFILING; 185 imustate->calibration_phase_start_time_ticks_ns = SDL_GetTicksNS(); 186 imustate->gyro_drift_sample_count = 0; 187 SDL_zeroa(imustate->gyro_drift_solution); 188 SDL_zeroa(imustate->gyro_drift_accumulator); 189} 190 191/* Initial/full reset of state */ 192void ResetIMUState(IMUState *imustate) 193{ 194 imustate->gyro_packet_number = 0; 195 imustate->accelerometer_packet_number = 0; 196 imustate->starting_time_stamp_ns = SDL_GetTicksNS(); 197 imustate->integrated_rotation = quat_identity; 198 imustate->accelerometer_length_squared = 0.0f; 199 imustate->accelerometer_tolerance_squared = ACCELEROMETER_NOISE_THRESHOLD; 200 imustate->calibration_phase = GYRO_CALIBRATION_PHASE_OFF; 201 imustate->calibration_phase_start_time_ticks_ns = SDL_GetTicksNS(); 202 imustate->integrated_rotation = quat_identity; 203 SDL_zeroa(imustate->last_accel_data); 204 SDL_zeroa(imustate->gyro_drift_solution); 205 SDL_zeroa(imustate->gyro_drift_accumulator); 206} 207 208void ResetGyroOrientation(IMUState *imustate) 209{ 210 imustate->integrated_rotation = quat_identity; 211} 212 213/* More time = more accurate drift correction*/ 214#define SDL_GAMEPAD_IMU_NOISE_SETTLING_PERIOD_NS ( SDL_NS_PER_SECOND / 2) 215#define SDL_GAMEPAD_IMU_NOISE_EVALUATION_PERIOD_NS (4 * SDL_NS_PER_SECOND) 216#define SDL_GAMEPAD_IMU_NOISE_PROFILING_PHASE_DURATION_NS (SDL_GAMEPAD_IMU_NOISE_SETTLING_PERIOD_NS + SDL_GAMEPAD_IMU_NOISE_EVALUATION_PERIOD_NS) 217#define SDL_GAMEPAD_IMU_CALIBRATION_PHASE_DURATION_NS (5 * SDL_NS_PER_SECOND) 218 219/* 220 * Find the maximum accelerometer noise over the duration of the GYRO_CALIBRATION_PHASE_NOISE_PROFILING phase. 221 */ 222void CalibrationPhase_NoiseProfiling(IMUState *imustate) 223{ 224 /* If we have really large movement (i.e. greater than a fraction of G), then we want to start noise evaluation over. The frontend will warn the user to put down the controller. */ 225 if (imustate->accelerometer_length_squared > ACCELEROMETER_MAX_NOISE_G_SQ) { 226 BeginNoiseCalibrationPhase(imustate); 227 return; 228 } 229 230 Uint64 now = SDL_GetTicksNS(); 231 Uint64 delta_ns = now - imustate->calibration_phase_start_time_ticks_ns; 232 233 /* Nuanced behavior - give the evaluation system some time to settle after placing the controller down before _actually_ evaluating, as the accelerometer could still be "ringing" after the user has placed it down, resulting in exaggerated tolerances */ 234 if (delta_ns > SDL_GAMEPAD_IMU_NOISE_SETTLING_PERIOD_NS) { 235 /* Get the largest noise spike in the period of evaluation */ 236 if (imustate->accelerometer_length_squared > imustate->accelerometer_tolerance_squared) { 237 imustate->accelerometer_tolerance_squared = imustate->accelerometer_length_squared; 238 } 239 } 240 241 /* Switch phase if we go over the time limit */ 242 if (delta_ns >= SDL_GAMEPAD_IMU_NOISE_PROFILING_PHASE_DURATION_NS) { 243 BeginDriftCalibrationPhase(imustate); 244 } 245} 246 247/* 248 * Average drift _per packet_ as opposed to _per second_ 249 * This reduces a small amount of overhead when applying the drift correction. 250 */ 251void FinalizeDriftSolution(IMUState *imustate) 252{ 253 if (imustate->gyro_drift_sample_count >= 0) { 254 imustate->gyro_drift_solution[0] = imustate->gyro_drift_accumulator[0] / (float)imustate->gyro_drift_sample_count; 255 imustate->gyro_drift_solution[1] = imustate->gyro_drift_accumulator[1] / (float)imustate->gyro_drift_sample_count; 256 imustate->gyro_drift_solution[2] = imustate->gyro_drift_accumulator[2] / (float)imustate->gyro_drift_sample_count; 257 } 258 259 imustate->calibration_phase = GYRO_CALIBRATION_PHASE_COMPLETE; 260 ResetGyroOrientation(imustate); 261} 262 263void CalibrationPhase_DriftProfiling(IMUState *imustate) 264{ 265 /* Ideal threshold will vary considerably depending on IMU. PS5 needs a low value (0.05f). Nintendo Switch needs a higher value (0.15f). */ 266 if (imustate->accelerometer_length_squared > imustate->accelerometer_tolerance_squared) { 267 /* Reset the drift calibration if the accelerometer has moved significantly */ 268 BeginDriftCalibrationPhase(imustate); 269 } else { 270 /* Sensor is stationary enough to evaluate for drift.*/ 271 ++imustate->gyro_drift_sample_count; 272 273 imustate->gyro_drift_accumulator[0] += imustate->gyro_data[0]; 274 imustate->gyro_drift_accumulator[1] += imustate->gyro_data[1]; 275 imustate->gyro_drift_accumulator[2] += imustate->gyro_data[2]; 276 277 /* Finish phase if we go over the time limit */ 278 Uint64 now = SDL_GetTicksNS(); 279 Uint64 delta_ns = now - imustate->calibration_phase_start_time_ticks_ns; 280 if (delta_ns >= SDL_GAMEPAD_IMU_CALIBRATION_PHASE_DURATION_NS) { 281 FinalizeDriftSolution(imustate); 282 } 283 } 284} 285 286/* Sample gyro packet in order to calculate drift*/ 287void SampleGyroPacketForDrift(IMUState *imustate) 288{ 289 /* Get the length squared difference of the last accelerometer data vs. the new one */ 290 float accelerometer_difference[3]; 291 accelerometer_difference[0] = imustate->accel_data[0] - imustate->last_accel_data[0]; 292 accelerometer_difference[1] = imustate->accel_data[1] - imustate->last_accel_data[1]; 293 accelerometer_difference[2] = imustate->accel_data[2] - imustate->last_accel_data[2]; 294 SDL_memcpy(imustate->last_accel_data, imustate->accel_data, sizeof(imustate->last_accel_data)); 295 imustate->accelerometer_length_squared = accelerometer_difference[0] * accelerometer_difference[0] + accelerometer_difference[1] * accelerometer_difference[1] + accelerometer_difference[2] * accelerometer_difference[2]; 296 297 if (imustate->calibration_phase == GYRO_CALIBRATION_PHASE_NOISE_PROFILING) 298 CalibrationPhase_NoiseProfiling(imustate); 299 300 if (imustate->calibration_phase == GYRO_CALIBRATION_PHASE_DRIFT_PROFILING) 301 CalibrationPhase_DriftProfiling(imustate); 302} 303 304void ApplyDriftSolution(float *gyro_data, const float *drift_solution) 305{ 306 gyro_data[0] -= drift_solution[0]; 307 gyro_data[1] -= drift_solution[1]; 308 gyro_data[2] -= drift_solution[2]; 309} 310 311void UpdateGyroRotation(IMUState *imustate, Uint64 sensorTimeStampDelta_ns) 312{ 313 float sensorTimeDeltaTimeSeconds = SDL_NS_TO_SECONDS((float)sensorTimeStampDelta_ns); 314 /* Integrate speeds to get Rotational Displacement*/ 315 float pitch = imustate->gyro_data[0] * sensorTimeDeltaTimeSeconds; 316 float yaw = imustate->gyro_data[1] * sensorTimeDeltaTimeSeconds; 317 float roll = imustate->gyro_data[2] * sensorTimeDeltaTimeSeconds; 318 319 /* Use quaternions to avoid gimbal lock*/ 320 Quaternion delta_rotation = QuaternionFromEuler(pitch, yaw, roll); 321 imustate->integrated_rotation = MultiplyQuaternion(imustate->integrated_rotation, delta_rotation); 322 NormalizeQuaternion(&imustate->integrated_rotation); 323} 324 325typedef struct 326{ 327 SDL_JoystickID id; 328 329 SDL_Joystick *joystick; 330 int num_axes; 331 AxisState *axis_state; 332 IMUState *imu_state; 333 334 SDL_Gamepad *gamepad; 335 char *mapping; 336 bool has_bindings; 337 338 int audio_route; 339 int trigger_effect; 340} Controller; 341 342static SDLTest_CommonState *state; 343static SDL_Window *window = NULL; 344static SDL_Renderer *screen = NULL; 345static ControllerDisplayMode display_mode = CONTROLLER_MODE_TESTING; 346static GamepadImage *image = NULL; 347static GamepadDisplay *gamepad_elements = NULL; 348static GyroDisplay *gyro_elements = NULL; 349static GamepadTypeDisplay *gamepad_type = NULL; 350static JoystickDisplay *joystick_elements = NULL; 351static GamepadButton *setup_mapping_button = NULL; 352static GamepadButton *done_mapping_button = NULL; 353static GamepadButton *cancel_button = NULL; 354static GamepadButton *clear_button = NULL; 355static GamepadButton *copy_button = NULL; 356static GamepadButton *paste_button = NULL; 357static char *backup_mapping = NULL; 358static bool done = false; 359static bool set_LED = false; 360static int num_controllers = 0; 361static Controller *controllers; 362static Controller *controller; 363static SDL_JoystickID mapping_controller = 0; 364static int binding_element = SDL_GAMEPAD_ELEMENT_INVALID; 365static int last_binding_element = SDL_GAMEPAD_ELEMENT_INVALID; 366static bool binding_flow = false; 367static int binding_flow_direction = 0; 368static Uint64 binding_advance_time = 0; 369static SDL_FRect title_area; 370static bool title_highlighted; 371static bool title_pressed; 372static SDL_FRect type_area; 373static bool type_highlighted; 374static bool type_pressed; 375static char *controller_name; 376static SDL_Joystick *virtual_joystick = NULL; 377static SDL_GamepadAxis virtual_axis_active = SDL_GAMEPAD_AXIS_INVALID; 378static float virtual_axis_start_x; 379static float virtual_axis_start_y; 380static SDL_GamepadButton virtual_button_active = SDL_GAMEPAD_BUTTON_INVALID; 381static bool virtual_touchpad_active = false; 382static float virtual_touchpad_x; 383static float virtual_touchpad_y; 384 385static int s_arrBindingOrder[] = { 386 /* Standard sequence */ 387 SDL_GAMEPAD_BUTTON_SOUTH, 388 SDL_GAMEPAD_BUTTON_EAST, 389 SDL_GAMEPAD_BUTTON_WEST, 390 SDL_GAMEPAD_BUTTON_NORTH, 391 SDL_GAMEPAD_BUTTON_DPAD_LEFT, 392 SDL_GAMEPAD_BUTTON_DPAD_RIGHT, 393 SDL_GAMEPAD_BUTTON_DPAD_UP, 394 SDL_GAMEPAD_BUTTON_DPAD_DOWN, 395 SDL_GAMEPAD_ELEMENT_AXIS_LEFTX_NEGATIVE, 396 SDL_GAMEPAD_ELEMENT_AXIS_LEFTX_POSITIVE, 397 SDL_GAMEPAD_ELEMENT_AXIS_LEFTY_NEGATIVE, 398 SDL_GAMEPAD_ELEMENT_AXIS_LEFTY_POSITIVE, 399 SDL_GAMEPAD_BUTTON_LEFT_STICK, 400 SDL_GAMEPAD_ELEMENT_AXIS_RIGHTX_NEGATIVE, 401 SDL_GAMEPAD_ELEMENT_AXIS_RIGHTX_POSITIVE, 402 SDL_GAMEPAD_ELEMENT_AXIS_RIGHTY_NEGATIVE, 403 SDL_GAMEPAD_ELEMENT_AXIS_RIGHTY_POSITIVE, 404 SDL_GAMEPAD_BUTTON_RIGHT_STICK, 405 SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, 406 SDL_GAMEPAD_ELEMENT_AXIS_LEFT_TRIGGER, 407 SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, 408 SDL_GAMEPAD_ELEMENT_AXIS_RIGHT_TRIGGER, 409 SDL_GAMEPAD_BUTTON_BACK, 410 SDL_GAMEPAD_BUTTON_START, 411 SDL_GAMEPAD_BUTTON_GUIDE, 412 SDL_GAMEPAD_BUTTON_MISC1, 413 SDL_GAMEPAD_ELEMENT_INVALID, 414 415 /* Paddle sequence */ 416 SDL_GAMEPAD_BUTTON_RIGHT_PADDLE1, 417 SDL_GAMEPAD_BUTTON_LEFT_PADDLE1, 418 SDL_GAMEPAD_BUTTON_RIGHT_PADDLE2, 419 SDL_GAMEPAD_BUTTON_LEFT_PADDLE2, 420 SDL_GAMEPAD_ELEMENT_INVALID, 421}; 422 423 424static const char *GetSensorName(SDL_SensorType sensor) 425{ 426 switch (sensor) { 427 case SDL_SENSOR_ACCEL: 428 return "accelerometer"; 429 case SDL_SENSOR_GYRO: 430 return "gyro"; 431 case SDL_SENSOR_ACCEL_L: 432 return "accelerometer (L)"; 433 case SDL_SENSOR_GYRO_L: 434 return "gyro (L)"; 435 case SDL_SENSOR_ACCEL_R: 436 return "accelerometer (R)"; 437 case SDL_SENSOR_GYRO_R: 438 return "gyro (R)"; 439 default: 440 return "UNKNOWN"; 441 } 442} 443 444/* PS5 trigger effect documentation: 445 https://controllers.fandom.com/wiki/Sony_DualSense#FFB_Trigger_Modes 446*/ 447typedef struct 448{ 449 Uint8 ucEnableBits1; /* 0 */ 450 Uint8 ucEnableBits2; /* 1 */ 451 Uint8 ucRumbleRight; /* 2 */ 452 Uint8 ucRumbleLeft; /* 3 */ 453 Uint8 ucHeadphoneVolume; /* 4 */ 454 Uint8 ucSpeakerVolume; /* 5 */ 455 Uint8 ucMicrophoneVolume; /* 6 */ 456 Uint8 ucAudioEnableBits; /* 7 */ 457 Uint8 ucMicLightMode; /* 8 */ 458 Uint8 ucAudioMuteBits; /* 9 */ 459 Uint8 rgucRightTriggerEffect[11]; /* 10 */ 460 Uint8 rgucLeftTriggerEffect[11]; /* 21 */ 461 Uint8 rgucUnknown1[6]; /* 32 */ 462 Uint8 ucLedFlags; /* 38 */ 463 Uint8 rgucUnknown2[2]; /* 39 */ 464 Uint8 ucLedAnim; /* 41 */ 465 Uint8 ucLedBrightness; /* 42 */ 466 Uint8 ucPadLights; /* 43 */ 467 Uint8 ucLedRed; /* 44 */ 468 Uint8 ucLedGreen; /* 45 */ 469 Uint8 ucLedBlue; /* 46 */ 470} DS5EffectsState_t; 471 472static void CyclePS5AudioRoute(Controller *device) 473{ 474 DS5EffectsState_t effects; 475 476 device->audio_route = (device->audio_route + 1) % 4; 477 478 SDL_zero(effects); 479 switch (device->audio_route) { 480 case 0: 481 /* Audio disabled */ 482 effects.ucEnableBits1 |= (0x80 | 0x20 | 0x10); /* Modify audio route and speaker / headphone volume */ 483 effects.ucSpeakerVolume = 0; /* Minimum volume */ 484 effects.ucHeadphoneVolume = 0; /* Minimum volume */ 485 effects.ucAudioEnableBits = 0x00; /* Output to headphones */ 486 break; 487 case 1: 488 /* Headphones */ 489 effects.ucEnableBits1 |= (0x80 | 0x10); /* Modify audio route and headphone volume */ 490 effects.ucHeadphoneVolume = 50; /* 50% volume - don't blast into the ears */ 491 effects.ucAudioEnableBits = 0x00; /* Output to headphones */ 492 break; 493 case 2: 494 /* Speaker */ 495 effects.ucEnableBits1 |= (0x80 | 0x20); /* Modify audio route and speaker volume */ 496 effects.ucSpeakerVolume = 100; /* Maximum volume */ 497 effects.ucAudioEnableBits = 0x30; /* Output to speaker */ 498 break; 499 case 3: 500 /* Both */ 501 effects.ucEnableBits1 |= (0x80 | 0x20 | 0x10); /* Modify audio route and speaker / headphone volume */ 502 effects.ucSpeakerVolume = 100; /* Maximum volume */ 503 effects.ucHeadphoneVolume = 50; /* 50% volume - don't blast into the ears */ 504 effects.ucAudioEnableBits = 0x20; /* Output to both speaker and headphones */ 505 break; 506 } 507 SDL_SendGamepadEffect(device->gamepad, &effects, sizeof(effects)); 508} 509 510static void CyclePS5TriggerEffect(Controller *device) 511{ 512 DS5EffectsState_t effects; 513 514 Uint8 trigger_effects[3][11] = { 515 /* Clear trigger effect */ 516 { 0x05, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, 517 /* Constant resistance across entire trigger pull */ 518 { 0x01, 0, 110, 0, 0, 0, 0, 0, 0, 0, 0 }, 519 /* Resistance and vibration when trigger is pulled */ 520 { 0x06, 15, 63, 128, 0, 0, 0, 0, 0, 0, 0 }, 521 }; 522 523 device->trigger_effect = (device->trigger_effect + 1) % SDL_arraysize(trigger_effects); 524 525 SDL_zero(effects); 526 effects.ucEnableBits1 |= (0x04 | 0x08); /* Modify right and left trigger effect respectively */ 527 SDL_memcpy(effects.rgucRightTriggerEffect, trigger_effects[device->trigger_effect], sizeof(trigger_effects[0])); 528 SDL_memcpy(effects.rgucLeftTriggerEffect, trigger_effects[device->trigger_effect], sizeof(trigger_effects[0])); 529 SDL_SendGamepadEffect(device->gamepad, &effects, sizeof(effects)); 530} 531 532static void ClearButtonHighlights(void) 533{ 534 title_highlighted = false; 535 title_pressed = false; 536 537 type_highlighted = false; 538 type_pressed = false; 539 540 ClearGamepadImage(image); 541 SetGamepadDisplayHighlight(gamepad_elements, SDL_GAMEPAD_ELEMENT_INVALID, false); 542 SetGamepadTypeDisplayHighlight(gamepad_type, SDL_GAMEPAD_TYPE_UNSELECTED, false); 543 SetGamepadButtonHighlight(GetGyroResetButton( gyro_elements ), false, false); 544 SetGamepadButtonHighlight(GetGyroCalibrateButton(gyro_elements), false, false); 545 SetGamepadButtonHighlight(setup_mapping_button, false, false); 546 SetGamepadButtonHighlight(done_mapping_button, false, false); 547 SetGamepadButtonHighlight(cancel_button, false, false); 548 SetGamepadButtonHighlight(clear_button, false, false); 549 SetGamepadButtonHighlight(copy_button, false, false); 550 SetGamepadButtonHighlight(paste_button, false, false); 551} 552 553static void UpdateButtonHighlights(float x, float y, bool button_down) 554{ 555 ClearButtonHighlights(); 556 SetGamepadButtonHighlight(GetGyroResetButton(gyro_elements), GamepadButtonContains(GetGyroResetButton(gyro_elements), x, y), button_down); 557 SetGamepadButtonHighlight(GetGyroCalibrateButton(gyro_elements), GamepadButtonContains(GetGyroCalibrateButton(gyro_elements), x, y), button_down); 558 559 if (display_mode == CONTROLLER_MODE_TESTING) { 560 SetGamepadButtonHighlight(setup_mapping_button, GamepadButtonContains(setup_mapping_button, x, y), button_down); 561 } else if (display_mode == CONTROLLER_MODE_BINDING) { 562 SDL_FPoint point; 563 int gamepad_highlight_element = SDL_GAMEPAD_ELEMENT_INVALID; 564 char *joystick_highlight_element; 565 566 point.x = x; 567 point.y = y; 568 if (SDL_PointInRectFloat(&point, &title_area)) { 569 title_highlighted = true; 570 title_pressed = button_down; 571 } else { 572 title_highlighted = false; 573 title_pressed = false; 574 } 575 576 if (SDL_PointInRectFloat(&point, &type_area)) { 577 type_highlighted = true; 578 type_pressed = button_down; 579 } else { 580 type_highlighted = false; 581 type_pressed = false; 582 } 583 584 if (controller->joystick != virtual_joystick) { 585 gamepad_highlight_element = GetGamepadImageElementAt(image, x, y); 586 } 587 if (gamepad_highlight_element == SDL_GAMEPAD_ELEMENT_INVALID) { 588 gamepad_highlight_element = GetGamepadDisplayElementAt(gamepad_elements, controller->gamepad, x, y); 589 } 590 SetGamepadDisplayHighlight(gamepad_elements, gamepad_highlight_element, button_down); 591 592 if (binding_element == SDL_GAMEPAD_ELEMENT_TYPE) { 593 int gamepad_highlight_type = GetGamepadTypeDisplayAt(gamepad_type, x, y); 594 SetGamepadTypeDisplayHighlight(gamepad_type, gamepad_highlight_type, button_down); 595 } 596 597 joystick_highlight_element = GetJoystickDisplayElementAt(joystick_elements, controller->joystick, x, y); 598 SetJoystickDisplayHighlight(joystick_elements, joystick_highlight_element, button_down); 599 SDL_free(joystick_highlight_element); 600 601 SetGamepadButtonHighlight(done_mapping_button, GamepadButtonContains(done_mapping_button, x, y), button_down); 602 SetGamepadButtonHighlight(cancel_button, GamepadButtonContains(cancel_button, x, y), button_down); 603 SetGamepadButtonHighlight(clear_button, GamepadButtonContains(clear_button, x, y), button_down); 604 SetGamepadButtonHighlight(copy_button, GamepadButtonContains(copy_button, x, y), button_down); 605 SetGamepadButtonHighlight(paste_button, GamepadButtonContains(paste_button, x, y), button_down); 606 } 607} 608 609static int StandardizeAxisValue(int nValue) 610{ 611 if (nValue > SDL_JOYSTICK_AXIS_MAX / 2) { 612 return SDL_JOYSTICK_AXIS_MAX; 613 } else if (nValue < SDL_JOYSTICK_AXIS_MIN / 2) { 614 return SDL_JOYSTICK_AXIS_MIN; 615 } else { 616 return 0; 617 } 618} 619 620static void RefreshControllerName(void) 621{ 622 const char *name = NULL; 623 624 SDL_free(controller_name); 625 controller_name = NULL; 626 627 if (controller) { 628 if (controller->gamepad) { 629 name = SDL_GetGamepadName(controller->gamepad); 630 } else { 631 name = SDL_GetJoystickName(controller->joystick); 632 } 633 } 634 635 if (name) { 636 controller_name = SDL_strdup(name); 637 } else { 638 controller_name = SDL_strdup(""); 639 } 640} 641 642static void SetAndFreeGamepadMapping(char *mapping) 643{ 644 SDL_SetGamepadMapping(controller->id, mapping); 645 SDL_free(mapping); 646} 647 648static void SetCurrentBindingElement(int element, bool flow) 649{ 650 int i; 651 652 if (binding_element == SDL_GAMEPAD_ELEMENT_NAME) { 653 RefreshControllerName(); 654 } 655 656 if (element == SDL_GAMEPAD_ELEMENT_INVALID) { 657 binding_flow_direction = 0; 658 last_binding_element = SDL_GAMEPAD_ELEMENT_INVALID; 659 } else { 660 last_binding_element = binding_element; 661 } 662 binding_element = element; 663 binding_flow = flow || (element == SDL_GAMEPAD_BUTTON_SOUTH); 664 binding_advance_time = 0; 665 666 for (i = 0; i < controller->num_axes; ++i) { 667 controller->axis_state[i].m_nFarthestValue = controller->axis_state[i].m_nStartingValue; 668 } 669 670 SetGamepadDisplaySelected(gamepad_elements, element); 671} 672 673static void SetNextBindingElement(void) 674{ 675 int i; 676 677 if (binding_element == SDL_GAMEPAD_ELEMENT_INVALID) { 678 return; 679 } 680 681 for (i = 0; i < SDL_arraysize(s_arrBindingOrder); ++i) { 682 if (binding_element == s_arrBindingOrder[i]) { 683 binding_flow_direction = 1; 684 SetCurrentBindingElement(s_arrBindingOrder[i + 1], true); 685 return; 686 } 687 } 688 SetCurrentBindingElement(SDL_GAMEPAD_ELEMENT_INVALID, false); 689} 690 691static void SetPrevBindingElement(void) 692{ 693 int i; 694 695 if (binding_element == SDL_GAMEPAD_ELEMENT_INVALID) { 696 return; 697 } 698 699 for (i = 1; i < SDL_arraysize(s_arrBindingOrder); ++i) { 700 if (binding_element == s_arrBindingOrder[i]) { 701 binding_flow_direction = -1; 702 SetCurrentBindingElement(s_arrBindingOrder[i - 1], true); 703 return; 704 } 705 } 706 SetCurrentBindingElement(SDL_GAMEPAD_ELEMENT_INVALID, false); 707} 708 709static void StopBinding(void) 710{ 711 SetCurrentBindingElement(SDL_GAMEPAD_ELEMENT_INVALID, false); 712} 713 714typedef struct 715{ 716 int axis; 717 int direction; 718} AxisInfo; 719 720static bool ParseAxisInfo(const char *description, AxisInfo *info) 721{ 722 if (!description) { 723 return false; 724 } 725 726 if (*description == '-') { 727 info->direction = -1; 728 ++description; 729 } else if (*description == '+') { 730 info->direction = 1; 731 ++description; 732 } else { 733 info->direction = 0; 734 } 735 736 if (description[0] == 'a' && SDL_isdigit(description[1])) { 737 ++description; 738 info->axis = SDL_atoi(description); 739 return true; 740 } 741 return false; 742} 743 744static void CommitBindingElement(const char *binding, bool force) 745{ 746 char *mapping; 747 int direction = 1; 748 bool ignore_binding = false; 749 750 if (binding_element == SDL_GAMEPAD_ELEMENT_INVALID) { 751 return; 752 } 753 754 if (controller->mapping) { 755 mapping = SDL_strdup(controller->mapping); 756 } else { 757 mapping = NULL; 758 } 759 760 /* If the controller generates multiple events for a single element, pick the best one */ 761 if (!force && binding_advance_time) { 762 char *current = GetElementBinding(mapping, binding_element); 763 bool native_button = (binding_element < SDL_GAMEPAD_BUTTON_COUNT); 764 bool native_axis = (binding_element >= SDL_GAMEPAD_BUTTON_COUNT && 765 binding_element <= SDL_GAMEPAD_ELEMENT_AXIS_MAX); 766 bool native_trigger = (binding_element == SDL_GAMEPAD_ELEMENT_AXIS_LEFT_TRIGGER || 767 binding_element == SDL_GAMEPAD_ELEMENT_AXIS_RIGHT_TRIGGER); 768 bool native_dpad = (binding_element == SDL_GAMEPAD_BUTTON_DPAD_UP || 769 binding_element == SDL_GAMEPAD_BUTTON_DPAD_DOWN || 770 binding_element == SDL_GAMEPAD_BUTTON_DPAD_LEFT || 771 binding_element == SDL_GAMEPAD_BUTTON_DPAD_RIGHT); 772 773 if (native_button) { 774 bool current_button = (current && *current == 'b'); 775 bool proposed_button = (binding && *binding == 'b'); 776 if (current_button && !proposed_button) { 777 ignore_binding = true; 778 } 779 /* Use the lower index button (we map from lower to higher button index) */ 780 if (current_button && proposed_button && current[1] < binding[1]) { 781 ignore_binding = true; 782 } 783 } 784 if (native_axis) { 785 AxisInfo current_axis_info = { 0, 0 }; 786 AxisInfo proposed_axis_info = { 0, 0 }; 787 bool current_axis = ParseAxisInfo(current, ¤t_axis_info); 788 bool proposed_axis = ParseAxisInfo(binding, &proposed_axis_info); 789 790 if (current_axis) { 791 /* Ignore this unless the proposed binding extends the existing axis */ 792 ignore_binding = true; 793 794 if (native_trigger && 795 ((*current == '-' && *binding == '+' && 796 SDL_strcmp(current + 1, binding + 1) == 0) || 797 (*current == '+' && *binding == '-' && 798 SDL_strcmp(current + 1, binding + 1) == 0))) { 799 /* Merge two half axes into a whole axis for a trigger */ 800 ++binding; 801 ignore_binding = false; 802 } 803 804 /* Use the lower index axis (we map from lower to higher axis index) */ 805 if (proposed_axis && proposed_axis_info.axis < current_axis_info.axis) { 806 ignore_binding = false; 807 } 808 } 809 } 810 if (native_dpad) { 811 bool current_hat = (current && *current == 'h'); 812 bool proposed_hat = (binding && *binding == 'h'); 813 if (current_hat && !proposed_hat) { 814 ignore_binding = true; 815 } 816 /* Use the lower index hat (we map from lower to higher hat index) */ 817 if (current_hat && proposed_hat && current[1] < binding[1]) { 818 ignore_binding = true; 819 } 820 } 821 SDL_free(current); 822 } 823 824 if (!ignore_binding && binding_flow && !force) { 825 int existing = GetElementForBinding(mapping, binding); 826 if (existing != SDL_GAMEPAD_ELEMENT_INVALID) { 827 SDL_GamepadButton action_forward = SDL_GAMEPAD_BUTTON_SOUTH; 828 SDL_GamepadButton action_backward = SDL_GAMEPAD_BUTTON_EAST; 829 SDL_GamepadButton action_delete = SDL_GAMEPAD_BUTTON_WEST; 830 if (binding_element == action_forward) { 831 /* Bind it! */ 832 } else if (binding_element == action_backward) { 833 if (existing == action_forward) { 834 bool bound_backward = MappingHasElement(controller->mapping, action_backward); 835 if (bound_backward) { 836 /* Just move on to the next one */ 837 ignore_binding = true; 838 SetNextBindingElement(); 839 } else { 840 /* You can't skip the backward action, go back and start over */ 841 ignore_binding = true; 842 SetPrevBindingElement(); 843 } 844 } else if (existing == action_backward && binding_flow_direction == -1) { 845 /* Keep going backwards */ 846 ignore_binding = true; 847 SetPrevBindingElement(); 848 } else { 849 /* Bind it! */ 850 } 851 } else if (existing == action_forward) { 852 /* Just move on to the next one */ 853 ignore_binding = true; 854 SetNextBindingElement(); 855 } else if (existing == action_backward) { 856 ignore_binding = true; 857 SetPrevBindingElement(); 858 } else if (existing == binding_element) { 859 /* We're rebinding the same thing, just move to the next one */ 860 ignore_binding = true; 861 SetNextBindingElement(); 862 } else if (existing == action_delete) { 863 /* Clear the current binding and move to the next one */ 864 binding = NULL; 865 direction = 1; 866 force = true; 867 } else if (binding_element != action_forward && 868 binding_element != action_backward) { 869 /* Actually, we'll just clear the existing binding */ 870 /*ignore_binding = true;*/ 871 } 872 } 873 } 874 875 if (ignore_binding) { 876 SDL_free(mapping); 877 return; 878 } 879 880 mapping = ClearMappingBinding(mapping, binding); 881 mapping = SetElementBinding(mapping, binding_element, binding); 882 SetAndFreeGamepadMapping(mapping); 883 884 if (force) { 885 if (binding_flow) { 886 if (direction > 0) { 887 SetNextBindingElement(); 888 } else if (direction < 0) { 889 SetPrevBindingElement(); 890 } 891 } else { 892 StopBinding(); 893 } 894 } else { 895 /* Wait to see if any more bindings come in */ 896 binding_advance_time = SDL_GetTicks() + 30; 897 } 898} 899 900static void ClearBinding(void) 901{ 902 CommitBindingElement(NULL, true); 903} 904 905static void SetDisplayMode(ControllerDisplayMode mode) 906{ 907 float x, y; 908 SDL_MouseButtonFlags button_state; 909 910 if (mode == CONTROLLER_MODE_BINDING) { 911 /* Make a backup of the current mapping */ 912 if (controller->mapping) { 913 backup_mapping = SDL_strdup(controller->mapping); 914 } 915 mapping_controller = controller->id; 916 if (MappingHasBindings(backup_mapping)) { 917 SetCurrentBindingElement(SDL_GAMEPAD_ELEMENT_INVALID, false); 918 } else { 919 SetCurrentBindingElement(SDL_GAMEPAD_BUTTON_SOUTH, true); 920 } 921 } else { 922 if (backup_mapping) { 923 SDL_free(backup_mapping); 924 backup_mapping = NULL; 925 } 926 mapping_controller = 0; 927 StopBinding(); 928 } 929 930 display_mode = mode; 931 SetGamepadImageDisplayMode(image, mode); 932 SetGamepadDisplayDisplayMode(gamepad_elements, mode); 933 934 button_state = SDL_GetMouseState(&x, &y); 935 SDL_RenderCoordinatesFromWindow(screen, x, y, &x, &y); 936 UpdateButtonHighlights(x, y, button_state ? true : false); 937} 938 939static void CancelMapping(void) 940{ 941 SetAndFreeGamepadMapping(backup_mapping); 942 backup_mapping = NULL; 943 944 SetDisplayMode(CONTROLLER_MODE_TESTING); 945} 946 947static void ClearMapping(void) 948{ 949 SetAndFreeGamepadMapping(NULL); 950 SetCurrentBindingElement(SDL_GAMEPAD_ELEMENT_INVALID, false); 951} 952 953static void CopyMapping(void) 954{ 955 if (controller && controller->mapping) { 956 SDL_SetClipboardText(controller->mapping); 957 } 958} 959 960static void PasteMapping(void) 961{ 962 if (controller) { 963 char *mapping = SDL_GetClipboardText(); 964 if (MappingHasBindings(mapping)) { 965 StopBinding(); 966 SDL_SetGamepadMapping(controller->id, mapping); 967 RefreshControllerName(); 968 } else { 969 /* Not a valid mapping, ignore it */ 970 } 971 SDL_free(mapping); 972 } 973} 974 975static void CommitControllerName(void) 976{ 977 char *mapping = NULL; 978 979 if (controller->mapping) { 980 mapping = SDL_strdup(controller->mapping); 981 } else { 982 mapping = NULL; 983 } 984 mapping = SetMappingName(mapping, controller_name); 985 SetAndFreeGamepadMapping(mapping); 986} 987 988static void AddControllerNameText(const char *text) 989{ 990 size_t current_length = (controller_name ? SDL_strlen(controller_name) : 0); 991 size_t text_length = SDL_strlen(text); 992 size_t size = current_length + text_length + 1; 993 char *name = (char *)SDL_realloc(controller_name, size); 994 if (name) { 995 SDL_memcpy(&name[current_length], text, text_length + 1); 996 controller_name = name; 997 } 998 CommitControllerName(); 999} 1000 1001static void BackspaceControllerName(void) 1002{ 1003 size_t length = (controller_name ? SDL_strlen(controller_name) : 0); 1004 if (length > 0) { 1005 controller_name[length - 1] = '\0'; 1006 } 1007 CommitControllerName(); 1008} 1009 1010static void ClearControllerName(void) 1011{ 1012 if (controller_name) { 1013 *controller_name = '\0'; 1014 } 1015 CommitControllerName(); 1016} 1017 1018static void CopyControllerName(void) 1019{ 1020 SDL_SetClipboardText(controller_name); 1021} 1022 1023static void PasteControllerName(void) 1024{ 1025 SDL_free(controller_name); 1026 controller_name = SDL_GetClipboardText(); 1027 CommitControllerName(); 1028} 1029 1030static void CommitGamepadType(SDL_GamepadType type) 1031{ 1032 char *mapping = NULL; 1033 1034 if (controller->mapping) { 1035 mapping = SDL_strdup(controller->mapping); 1036 } else { 1037 mapping = NULL; 1038 } 1039 mapping = SetMappingType(mapping, type); 1040 SetAndFreeGamepadMapping(mapping); 1041} 1042 1043static const char *GetBindingInstruction(void) 1044{ 1045 switch (binding_element) { 1046 case SDL_GAMEPAD_ELEMENT_INVALID: 1047 return "Select an element to bind from the list on the left"; 1048 case SDL_GAMEPAD_BUTTON_SOUTH: 1049 case SDL_GAMEPAD_BUTTON_EAST: 1050 case SDL_GAMEPAD_BUTTON_WEST: 1051 case SDL_GAMEPAD_BUTTON_NORTH: 1052 switch (SDL_GetGamepadButtonLabelForType(GetGamepadImageType(image), (SDL_GamepadButton)binding_element)) { 1053 case SDL_GAMEPAD_BUTTON_LABEL_A: 1054 return "Press the A button"; 1055 case SDL_GAMEPAD_BUTTON_LABEL_B: 1056 return "Press the B button"; 1057 case SDL_GAMEPAD_BUTTON_LABEL_X: 1058 return "Press the X button"; 1059 case SDL_GAMEPAD_BUTTON_LABEL_Y: 1060 return "Press the Y button"; 1061 case SDL_GAMEPAD_BUTTON_LABEL_CROSS: 1062 return "Press the Cross (X) button"; 1063 case SDL_GAMEPAD_BUTTON_LABEL_CIRCLE: 1064 return "Press the Circle button"; 1065 case SDL_GAMEPAD_BUTTON_LABEL_SQUARE: 1066 return "Press the Square button"; 1067 case SDL_GAMEPAD_BUTTON_LABEL_TRIANGLE: 1068 return "Press the Triangle button"; 1069 default: 1070 return ""; 1071 } 1072 case SDL_GAMEPAD_BUTTON_BACK: 1073 return "Press the left center button (Back/View/Share)"; 1074 case SDL_GAMEPAD_BUTTON_GUIDE: 1075 return "Press the center button (Home/Guide)"; 1076 case SDL_GAMEPAD_BUTTON_START: 1077 return "Press the right center button (Start/Menu/Options)"; 1078 case SDL_GAMEPAD_BUTTON_LEFT_STICK: 1079 return "Press the left thumbstick button (LSB/L3)"; 1080 case SDL_GAMEPAD_BUTTON_RIGHT_STICK: 1081 return "Press the right thumbstick button (RSB/R3)"; 1082 case SDL_GAMEPAD_BUTTON_LEFT_SHOULDER: 1083 return "Press the left shoulder button (LB/L1)"; 1084 case SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER: 1085 return "Press the right shoulder button (RB/R1)"; 1086 case SDL_GAMEPAD_BUTTON_DPAD_UP: 1087 return "Press the D-Pad up"; 1088 case SDL_GAMEPAD_BUTTON_DPAD_DOWN: 1089 return "Press the D-Pad down"; 1090 case SDL_GAMEPAD_BUTTON_DPAD_LEFT: 1091 return "Press the D-Pad left"; 1092 case SDL_GAMEPAD_BUTTON_DPAD_RIGHT: 1093 return "Press the D-Pad right"; 1094 case SDL_GAMEPAD_BUTTON_MISC1: 1095 return "Press the bottom center button (Share/Capture)"; 1096 case SDL_GAMEPAD_BUTTON_RIGHT_PADDLE1: 1097 return "Press the upper paddle under your right hand"; 1098 case SDL_GAMEPAD_BUTTON_LEFT_PADDLE1: 1099 return "Press the upper paddle under your left hand"; 1100 case SDL_GAMEPAD_BUTTON_RIGHT_PADDLE2: 1101 return "Press the lower paddle under your right hand"; 1102 case SDL_GAMEPAD_BUTTON_LEFT_PADDLE2: 1103 return "Press the lower paddle under your left hand"; 1104 case SDL_GAMEPAD_BUTTON_TOUCHPAD: 1105 return "Press down on the touchpad"; 1106 case SDL_GAMEPAD_BUTTON_MISC2: 1107 case SDL_GAMEPAD_BUTTON_MISC3: 1108 case SDL_GAMEPAD_BUTTON_MISC4: 1109 case SDL_GAMEPAD_BUTTON_MISC5: 1110 case SDL_GAMEPAD_BUTTON_MISC6: 1111 return "Press any additional button not already bound"; 1112 case SDL_GAMEPAD_ELEMENT_AXIS_LEFTX_NEGATIVE: 1113 return "Move the left thumbstick to the left"; 1114 case SDL_GAMEPAD_ELEMENT_AXIS_LEFTX_POSITIVE: 1115 return "Move the left thumbstick to the right"; 1116 case SDL_GAMEPAD_ELEMENT_AXIS_LEFTY_NEGATIVE: 1117 return "Move the left thumbstick up"; 1118 case SDL_GAMEPAD_ELEMENT_AXIS_LEFTY_POSITIVE: 1119 return "Move the left thumbstick down"; 1120 case SDL_GAMEPAD_ELEMENT_AXIS_RIGHTX_NEGATIVE: 1121 return "Move the right thumbstick to the left"; 1122 case SDL_GAMEPAD_ELEMENT_AXIS_RIGHTX_POSITIVE: 1123 return "Move the right thumbstick to the right"; 1124 case SDL_GAMEPAD_ELEMENT_AXIS_RIGHTY_NEGATIVE: 1125 return "Move the right thumbstick up"; 1126 case SDL_GAMEPAD_ELEMENT_AXIS_RIGHTY_POSITIVE: 1127 return "Move the right thumbstick down"; 1128 case SDL_GAMEPAD_ELEMENT_AXIS_LEFT_TRIGGER: 1129 return "Pull the left trigger (LT/L2)"; 1130 case SDL_GAMEPAD_ELEMENT_AXIS_RIGHT_TRIGGER: 1131 return "Pull the right trigger (RT/R2)"; 1132 case SDL_GAMEPAD_ELEMENT_NAME: 1133 return "Type the name of your controller"; 1134 case SDL_GAMEPAD_ELEMENT_TYPE: 1135 return "Select the type of your controller"; 1136 default: 1137 return ""; 1138 } 1139} 1140 1141static int FindController(SDL_JoystickID id) 1142{ 1143 int i; 1144 1145 for (i = 0; i < num_controllers; ++i) { 1146 if (id == controllers[i].id) { 1147 return i; 1148 } 1149 } 1150 return -1; 1151} 1152 1153static void SetController(SDL_JoystickID id) 1154{ 1155 int i = FindController(id); 1156 1157 if (i < 0 && num_controllers > 0) { 1158 i = 0; 1159 } 1160 1161 if (i >= 0) { 1162 controller = &controllers[i]; 1163 } else { 1164 controller = NULL; 1165 } 1166 1167 RefreshControllerName(); 1168} 1169 1170static void AddController(SDL_JoystickID id, bool verbose) 1171{ 1172 Controller *new_controllers; 1173 Controller *new_controller; 1174 SDL_Joystick *joystick; 1175 1176 if (FindController(id) >= 0) { 1177 /* We already have this controller */ 1178 return; 1179 } 1180 1181 new_controllers = (Controller *)SDL_realloc(controllers, (num_controllers + 1) * sizeof(*controllers)); 1182 if (!new_controllers) { 1183 return; 1184 } 1185 1186 controller = NULL; 1187 controllers = new_controllers; 1188 new_controller = &new_controllers[num_controllers++]; 1189 SDL_zerop(new_controller); 1190 new_controller->id = id; 1191 1192 new_controller->joystick = SDL_OpenJoystick(id); 1193 if (new_controller->joystick) { 1194 new_controller->num_axes = SDL_GetNumJoystickAxes(new_controller->joystick); 1195 new_controller->axis_state = (AxisState *)SDL_calloc(new_controller->num_axes, sizeof(*new_controller->axis_state)); 1196 new_controller->imu_state = (IMUState *)SDL_calloc(1, sizeof(*new_controller->imu_state)); 1197 ResetIMUState(new_controller->imu_state); 1198 } 1199 1200 joystick = new_controller->joystick; 1201 if (joystick) { 1202 if (verbose && !SDL_IsGamepad(id)) { 1203 const char *name = SDL_GetJoystickName(joystick); 1204 const char *path = SDL_GetJoystickPath(joystick); 1205 char guid[33]; 1206 SDL_Log("Opened joystick %s%s%s", name, path ? ", " : "", path ? path : ""); 1207 SDL_GUIDToString(SDL_GetJoystickGUID(joystick), guid, sizeof(guid)); 1208 SDL_Log("No gamepad mapping for %s", guid); 1209 } 1210 } else { 1211 SDL_Log("Couldn't open joystick: %s", SDL_GetError()); 1212 } 1213 1214 if (mapping_controller) { 1215 SetController(mapping_controller); 1216 } else { 1217 SetController(id); 1218 } 1219} 1220 1221static void DelController(SDL_JoystickID id) 1222{ 1223 int i = FindController(id); 1224 1225 if (i < 0) { 1226 return; 1227 } 1228 1229 if (display_mode == CONTROLLER_MODE_BINDING && id == controller->id) { 1230 SetDisplayMode(CONTROLLER_MODE_TESTING); 1231 } 1232 1233 /* Reset trigger state */ 1234 if (controllers[i].trigger_effect != 0) { 1235 controllers[i].trigger_effect = -1; 1236 CyclePS5TriggerEffect(&controllers[i]); 1237 } 1238 SDL_assert(controllers[i].gamepad == NULL); 1239 SDL_free(controllers[i].axis_state); 1240 SDL_free(controllers[i].imu_state); 1241 if (controllers[i].joystick) { 1242 SDL_CloseJoystick(controllers[i].joystick); 1243 } 1244 1245 --num_controllers; 1246 if (i < num_controllers) { 1247 SDL_memcpy(&controllers[i], &controllers[i + 1], (num_controllers - i) * sizeof(*controllers)); 1248 } 1249 1250 if (mapping_controller) { 1251 SetController(mapping_controller); 1252 } else { 1253 SetController(id); 1254 } 1255} 1256 1257static void HandleGamepadRemapped(SDL_JoystickID id) 1258{ 1259 char *mapping; 1260 int i = FindController(id); 1261 1262 SDL_assert(i >= 0); 1263 if (i < 0) { 1264 return; 1265 } 1266 1267 if (!controllers[i].gamepad) { 1268 /* Failed to open this controller */ 1269 return; 1270 } 1271 1272 /* Get the current mapping */ 1273 mapping = SDL_GetGamepadMapping(controllers[i].gamepad); 1274 1275 /* Make sure the mapping has a valid name */ 1276 if (mapping && !MappingHasName(mapping)) { 1277 mapping = SetMappingName(mapping, SDL_GetJoystickName(controllers[i].joystick)); 1278 } 1279 1280 SDL_free(controllers[i].mapping); 1281 controllers[i].mapping = mapping; 1282 controllers[i].has_bindings = MappingHasBindings(mapping); 1283} 1284 1285static void HandleGamepadAdded(SDL_JoystickID id, bool verbose) 1286{ 1287 SDL_Gamepad *gamepad; 1288 Uint16 firmware_version; 1289 SDL_SensorType sensors[] = { 1290 SDL_SENSOR_ACCEL, 1291 SDL_SENSOR_GYRO, 1292 SDL_SENSOR_ACCEL_L, 1293 SDL_SENSOR_GYRO_L, 1294 SDL_SENSOR_ACCEL_R, 1295 SDL_SENSOR_GYRO_R 1296 }; 1297 int i; 1298 1299 i = FindController(id); 1300 if (i < 0) { 1301 return; 1302 } 1303 SDL_Log("Gamepad %" SDL_PRIu32 " added", id); 1304 1305 SDL_assert(!controllers[i].gamepad); 1306 controllers[i].gamepad = SDL_OpenGamepad(id); 1307 1308 gamepad = controllers[i].gamepad; 1309 if (gamepad) { 1310 if (verbose) { 1311 SDL_PropertiesID props = SDL_GetGamepadProperties(gamepad); 1312 const char *name = SDL_GetGamepadName(gamepad); 1313 const char *path = SDL_GetGamepadPath(gamepad); 1314 SDL_GUID guid = SDL_GetGamepadGUIDForID(id); 1315 char guid_string[33]; 1316 SDL_GUIDToString(guid, guid_string, sizeof(guid_string)); 1317 SDL_Log("Opened gamepad %s, guid %s%s%s", name, guid_string, path ? ", " : "", path ? path : ""); 1318 1319 firmware_version = SDL_GetGamepadFirmwareVersion(gamepad); 1320 if (firmware_version) { 1321 SDL_Log("Firmware version: 0x%x (%d)", firmware_version, firmware_version); 1322 } 1323 1324 if (SDL_GetBooleanProperty(props, SDL_PROP_GAMEPAD_CAP_PLAYER_LED_BOOLEAN, false)) { 1325 SDL_Log("Has player LED"); 1326 } 1327 1328 if (SDL_GetBooleanProperty(props, SDL_PROP_GAMEPAD_CAP_RUMBLE_BOOLEAN, false)) { 1329 SDL_Log("Rumble supported"); 1330 } 1331 1332 if (SDL_GetBooleanProperty(props, SDL_PROP_GAMEPAD_CAP_TRIGGER_RUMBLE_BOOLEAN, false)) { 1333 SDL_Log("Trigger rumble supported"); 1334 } 1335 1336 if (SDL_GetGamepadPlayerIndex(gamepad) >= 0) { 1337 SDL_Log("Player index: %d", SDL_GetGamepadPlayerIndex(gamepad)); 1338 } 1339 1340 switch (SDL_GetJoystickTypeForID(id)) { 1341 case SDL_JOYSTICK_TYPE_WHEEL: 1342 SDL_Log("Controller is a wheel"); 1343 break; 1344 case SDL_JOYSTICK_TYPE_ARCADE_STICK: 1345 SDL_Log("Controller is an arcade stick"); 1346 break; 1347 case SDL_JOYSTICK_TYPE_FLIGHT_STICK: 1348 SDL_Log("Controller is a flight stick"); 1349 break; 1350 case SDL_JOYSTICK_TYPE_DANCE_PAD: 1351 SDL_Log("Controller is a dance pad"); 1352 break; 1353 case SDL_JOYSTICK_TYPE_GUITAR: 1354 SDL_Log("Controller is a guitar"); 1355 break; 1356 case SDL_JOYSTICK_TYPE_DRUM_KIT: 1357 SDL_Log("Controller is a drum kit"); 1358 break; 1359 case SDL_JOYSTICK_TYPE_ARCADE_PAD: 1360 SDL_Log("Controller is an arcade pad"); 1361 break; 1362 case SDL_JOYSTICK_TYPE_THROTTLE: 1363 SDL_Log("Controller is a throttle"); 1364 break; 1365 default: 1366 break; 1367 } 1368 } 1369 1370 for (i = 0; i < SDL_arraysize(sensors); ++i) { 1371 SDL_SensorType sensor = sensors[i]; 1372 1373 if (SDL_GamepadHasSensor(gamepad, sensor)) { 1374 if (verbose) { 1375 SDL_Log("Enabling %s at %.2f Hz", GetSensorName(sensor), SDL_GetGamepadSensorDataRate(gamepad, sensor)); 1376 } 1377 SDL_SetGamepadSensorEnabled(gamepad, sensor, true); 1378 } 1379 } 1380 1381 if (verbose) { 1382 char *mapping = SDL_GetGamepadMapping(gamepad); 1383 if (mapping) { 1384 SDL_Log("Mapping: %s", mapping); 1385 SDL_free(mapping); 1386 } 1387 } 1388 } else { 1389 SDL_Log("Couldn't open gamepad: %s", SDL_GetError()); 1390 } 1391 1392 HandleGamepadRemapped(id); 1393 SetController(id); 1394} 1395 1396static void HandleGamepadRemoved(SDL_JoystickID id) 1397{ 1398 int i = FindController(id); 1399 1400 SDL_assert(i >= 0); 1401 if (i < 0) { 1402 return; 1403 } 1404 SDL_Log("Gamepad %" SDL_PRIu32 " removed", id); 1405 1406 if (controllers[i].mapping) { 1407 SDL_free(controllers[i].mapping); 1408 controllers[i].mapping = NULL; 1409 } 1410 if (controllers[i].gamepad) { 1411 SDL_CloseGamepad(controllers[i].gamepad); 1412 controllers[i].gamepad = NULL; 1413 } 1414} 1415static void HandleGamepadAccelerometerEvent(SDL_Event *event) 1416{ 1417 controller->imu_state->accelerometer_packet_number++; 1418 SDL_memcpy(controller->imu_state->accel_data, event->gsensor.data, sizeof(controller->imu_state->accel_data)); 1419} 1420 1421static void HandleGamepadGyroEvent(SDL_Event *event) 1422{ 1423 controller->imu_state->gyro_packet_number++; 1424 SDL_memcpy(controller->imu_state->gyro_data, event->gsensor.data, sizeof(controller->imu_state->gyro_data)); 1425} 1426 1427/* Two strategies for evaluating polling rate - one based on a fixed packet count, and one using a fixed time window. 1428 * Smaller values in either will give you a more responsive polling rate estimate, but this may fluctuate more. 1429 * Larger values in either will give you a more stable average but they will require more time to evaluate. 1430 * Generally, wired connections tend to give much more stable 1431 */ 1432/* #define SDL_USE_FIXED_PACKET_COUNT_FOR_ESTIMATION */ 1433#define SDL_GAMEPAD_IMU_MIN_POLLING_RATE_ESTIMATION_COUNT 2048 1434#define SDL_GAMEPAD_IMU_MIN_POLLING_RATE_ESTIMATION_TIME_NS (SDL_NS_PER_SECOND * 2) 1435 1436 1437static void EstimatePacketRate(void) 1438{ 1439 Uint64 now_ns = SDL_GetTicksNS(); 1440 if (controller->imu_state->imu_packet_counter == 0) { 1441 controller->imu_state->starting_time_stamp_ns = now_ns; 1442 } 1443 1444 /* Require a significant sample size before averaging rate. */ 1445#ifdef SDL_USE_FIXED_PACKET_COUNT_FOR_ESTIMATION 1446 if (controller->imu_state->imu_packet_counter >= SDL_GAMEPAD_IMU_MIN_POLLING_RATE_ESTIMATION_COUNT) { 1447 Uint64 deltatime_ns = now_ns - controller->imu_state->starting_time_stamp_ns; 1448 controller->imu_state->imu_estimated_sensor_rate = (Uint16)((controller->imu_state->imu_packet_counter * SDL_NS_PER_SECOND) / deltatime_ns); 1449 controller->imu_state->imu_packet_counter = 0; 1450 } 1451#else 1452 Uint64 deltatime_ns = now_ns - controller->imu_state->starting_time_stamp_ns; 1453 if (deltatime_ns >= SDL_GAMEPAD_IMU_MIN_POLLING_RATE_ESTIMATION_TIME_NS) { 1454 controller->imu_state->imu_estimated_sensor_rate = (Uint16)((controller->imu_state->imu_packet_counter * SDL_NS_PER_SECOND) / deltatime_ns); 1455 controller->imu_state->imu_packet_counter = 0; 1456 } 1457#endif 1458 else { 1459 ++controller->imu_state->imu_packet_counter; 1460 } 1461} 1462 1463static void UpdateGamepadOrientation( Uint64 delta_time_ns ) 1464{ 1465 if (!controller || !controller->imu_state) 1466 return; 1467 1468 SampleGyroPacketForDrift(controller->imu_state); 1469 ApplyDriftSolution(controller->imu_state->gyro_data, controller->imu_state->gyro_drift_solution); 1470 UpdateGyroRotation(controller->imu_state, delta_time_ns); 1471} 1472 1473static void HandleGamepadSensorEvent( SDL_Event* event ) 1474{ 1475 if (!controller) 1476 return; 1477 1478 if (controller->id != event->gsensor.which) 1479 return; 1480 1481 if (event->gsensor.sensor == SDL_SENSOR_GYRO) { 1482 HandleGamepadGyroEvent(event); 1483 } else if (event->gsensor.sensor == SDL_SENSOR_ACCEL) { 1484 HandleGamepadAccelerometerEvent(event); 1485 } 1486 1487 /* 1488 This is where we can update the quaternion because we need to have a drift solution, which requires both 1489 accelerometer and gyro events are received before progressing. 1490 */ 1491 if ( controller->imu_state->accelerometer_packet_number == controller->imu_state->gyro_packet_number ) { 1492 EstimatePacketRate(); 1493 Uint64 sensorTimeStampDelta_ns = event->gsensor.sensor_timestamp - controller->imu_state->last_sensor_time_stamp_ns ; 1494 UpdateGamepadOrientation(sensorTimeStampDelta_ns); 1495 1496 float display_euler_angles[3]; 1497 QuaternionToYXZ(controller->imu_state->integrated_rotation, &display_euler_angles[0], &display_euler_angles[1], &display_euler_angles[2]); 1498 1499 /* Show how far we are through the current phase. When off, just default to zero progress */ 1500 Uint64 now = SDL_GetTicksNS(); 1501 Uint64 duration = 0; 1502 if (controller->imu_state->calibration_phase == GYRO_CALIBRATION_PHASE_NOISE_PROFILING) { 1503 duration = SDL_GAMEPAD_IMU_NOISE_PROFILING_PHASE_DURATION_NS; 1504 } else if (controller->imu_state->calibration_phase == GYRO_CALIBRATION_PHASE_DRIFT_PROFILING) { 1505 duration = SDL_GAMEPAD_IMU_CALIBRATION_PHASE_DURATION_NS; 1506 } 1507 1508 Uint64 delta_ns = now - controller->imu_state->calibration_phase_start_time_ticks_ns; 1509 float drift_calibration_progress_fraction = duration > 0.0f ? ((float)delta_ns / (float)duration) : 0.0f; 1510 1511 int reported_polling_rate_hz = sensorTimeStampDelta_ns > 0 ? (int)(SDL_NS_PER_SECOND / sensorTimeStampDelta_ns) : 0; 1512 1513 /* Send the results to the frontend */ 1514 SetGamepadDisplayIMUValues(gyro_elements, 1515 controller->imu_state->gyro_drift_solution, 1516 display_euler_angles, 1517 &controller->imu_state->integrated_rotation, 1518 reported_polling_rate_hz, 1519 controller->imu_state->imu_estimated_sensor_rate, 1520 controller->imu_state->calibration_phase, 1521 drift_calibration_progress_fraction, 1522 controller->imu_state->accelerometer_length_squared, 1523 controller->imu_state->accelerometer_tolerance_squared 1524 ); 1525 1526 /* Also show the gyro correction next to the gyro speed - this is useful in turntable tests as you can use a turntable to calibrate for drift, and that drift correction is functionally the same as the turn table speed (ignoring drift) */ 1527 SetGamepadDisplayGyroDriftCorrection(gamepad_elements, controller->imu_state->gyro_drift_solution); 1528 1529 controller->imu_state->last_sensor_time_stamp_ns = event->gsensor.sensor_timestamp; 1530 } 1531} 1532 1533static Uint16 ConvertAxisToRumble(Sint16 axisval) 1534{ 1535 /* Only start rumbling if the axis is past the halfway point */ 1536 const Sint16 half_axis = (Sint16)SDL_ceil(SDL_JOYSTICK_AXIS_MAX / 2.0f); 1537 if (axisval > half_axis) { 1538 return (Uint16)(axisval - half_axis) * 4; 1539 } else { 1540 return 0; 1541 } 1542} 1543 1544static bool ShowingFront(void) 1545{ 1546 bool showing_front = true; 1547 int i; 1548 1549 /* Show the back of the gamepad if the paddles are being held or bound */ 1550 for (i = SDL_GAMEPAD_BUTTON_RIGHT_PADDLE1; i <= SDL_GAMEPAD_BUTTON_LEFT_PADDLE2; ++i) { 1551 if (SDL_GetGamepadButton(controller->gamepad, (SDL_GamepadButton)i) || 1552 binding_element == i) { 1553 showing_front = false; 1554 break; 1555 } 1556 } 1557 if ((SDL_GetModState() & SDL_KMOD_SHIFT) && binding_element != SDL_GAMEPAD_ELEMENT_NAME) { 1558 showing_front = false; 1559 } 1560 return showing_front; 1561} 1562 1563static void SDLCALL VirtualGamepadSetPlayerIndex(void *userdata, int player_index) 1564{ 1565 SDL_Log("Virtual Gamepad: player index set to %d", player_index); 1566} 1567 1568static bool SDLCALL VirtualGamepadRumble(void *userdata, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble) 1569{ 1570 SDL_Log("Virtual Gamepad: rumble set to %d/%d", low_frequency_rumble, high_frequency_rumble); 1571 return true; 1572} 1573 1574static bool SDLCALL VirtualGamepadRumbleTriggers(void *userdata, Uint16 left_rumble, Uint16 right_rumble) 1575{ 1576 SDL_Log("Virtual Gamepad: trigger rumble set to %d/%d", left_rumble, right_rumble); 1577 return true; 1578} 1579 1580static bool SDLCALL VirtualGamepadSetLED(void *userdata, Uint8 red, Uint8 green, Uint8 blue) 1581{ 1582 SDL_Log("Virtual Gamepad: LED set to RGB %d,%d,%d", red, green, blue); 1583 return true; 1584} 1585 1586static void OpenVirtualGamepad(void) 1587{ 1588 SDL_VirtualJoystickTouchpadDesc virtual_touchpad = { 1, { 0, 0, 0 } }; 1589 SDL_VirtualJoystickSensorDesc virtual_sensors[] = { 1590 { SDL_SENSOR_ACCEL, 0.0f }, 1591 { SDL_SENSOR_GYRO, 0.0f } 1592 }; 1593 SDL_VirtualJoystickDesc desc; 1594 SDL_JoystickID virtual_id; 1595 1596 if (virtual_joystick) { 1597 return; 1598 } 1599 1600 SDL_INIT_INTERFACE(&desc); 1601 desc.type = SDL_JOYSTICK_TYPE_GAMEPAD; 1602 desc.naxes = SDL_GAMEPAD_AXIS_COUNT; 1603 desc.nbuttons = SDL_GAMEPAD_BUTTON_COUNT; 1604 desc.ntouchpads = 1; 1605 desc.touchpads = &virtual_touchpad; 1606 desc.nsensors = SDL_arraysize(virtual_sensors); 1607 desc.sensors = virtual_sensors; 1608 desc.SetPlayerIndex = VirtualGamepadSetPlayerIndex; 1609 desc.Rumble = VirtualGamepadRumble; 1610 desc.RumbleTriggers = VirtualGamepadRumbleTriggers; 1611 desc.SetLED = VirtualGamepadSetLED; 1612 1613 virtual_id = SDL_AttachVirtualJoystick(&desc); 1614 if (virtual_id == 0) { 1615 SDL_Log("Couldn't attach virtual device: %s", SDL_GetError()); 1616 } else { 1617 virtual_joystick = SDL_OpenJoystick(virtual_id); 1618 if (!virtual_joystick) { 1619 SDL_Log("Couldn't open virtual device: %s", SDL_GetError()); 1620 } 1621 } 1622} 1623 1624static void CloseVirtualGamepad(void) 1625{ 1626 int i; 1627 SDL_JoystickID *joysticks = SDL_GetJoysticks(NULL); 1628 if (joysticks) { 1629 for (i = 0; joysticks[i]; ++i) { 1630 SDL_JoystickID instance_id = joysticks[i]; 1631 if (SDL_IsJoystickVirtual(instance_id)) { 1632 SDL_DetachVirtualJoystick(instance_id); 1633 } 1634 } 1635 SDL_free(joysticks); 1636 } 1637 1638 if (virtual_joystick) { 1639 SDL_CloseJoystick(virtual_joystick); 1640 virtual_joystick = NULL; 1641 } 1642} 1643 1644static void VirtualGamepadMouseMotion(float x, float y) 1645{ 1646 if (virtual_button_active != SDL_GAMEPAD_BUTTON_INVALID) { 1647 if (virtual_axis_active != SDL_GAMEPAD_AXIS_INVALID) { 1648 const float MOVING_DISTANCE = 2.0f; 1649 if (SDL_fabs(x - virtual_axis_start_x) >= MOVING_DISTANCE || 1650 SDL_fabs(y - virtual_axis_start_y) >= MOVING_DISTANCE) { 1651 SDL_SetJoystickVirtualButton(virtual_joystick, virtual_button_active, false); 1652 virtual_button_active = SDL_GAMEPAD_BUTTON_INVALID; 1653 } 1654 } 1655 } 1656 1657 if (virtual_axis_active != SDL_GAMEPAD_AXIS_INVALID) { 1658 if (virtual_axis_active == SDL_GAMEPAD_AXIS_LEFT_TRIGGER || 1659 virtual_axis_active == SDL_GAMEPAD_AXIS_RIGHT_TRIGGER) { 1660 int range = (SDL_JOYSTICK_AXIS_MAX - SDL_JOYSTICK_AXIS_MIN); 1661 float distance = SDL_clamp((y - virtual_axis_start_y) / GetGamepadImageAxisHeight(image), 0.0f, 1.0f); 1662 Sint16 value = (Sint16)(SDL_JOYSTICK_AXIS_MIN + (distance * range)); 1663 SDL_SetJoystickVirtualAxis(virtual_joystick, virtual_axis_active, value); 1664 } else { 1665 float distanceX = SDL_clamp((x - virtual_axis_start_x) / GetGamepadImageAxisWidth(image), -1.0f, 1.0f); 1666 float distanceY = SDL_clamp((y - virtual_axis_start_y) / GetGamepadImageAxisHeight(image), -1.0f, 1.0f); 1667 Sint16 valueX, valueY; 1668 1669 if (distanceX >= 0) { 1670 valueX = (Sint16)(distanceX * SDL_JOYSTICK_AXIS_MAX); 1671 } else { 1672 valueX = (Sint16)(distanceX * -SDL_JOYSTICK_AXIS_MIN); 1673 } 1674 if (distanceY >= 0) { 1675 valueY = (Sint16)(distanceY * SDL_JOYSTICK_AXIS_MAX); 1676 } else { 1677 valueY = (Sint16)(distanceY * -SDL_JOYSTICK_AXIS_MIN); 1678 } 1679 SDL_SetJoystickVirtualAxis(virtual_joystick, virtual_axis_active, valueX); 1680 SDL_SetJoystickVirtualAxis(virtual_joystick, virtual_axis_active + 1, valueY); 1681 } 1682 } 1683 1684 if (virtual_touchpad_active) { 1685 SDL_FRect touchpad; 1686 GetGamepadTouchpadArea(image, &touchpad); 1687 virtual_touchpad_x = (x - touchpad.x) / touchpad.w; 1688 virtual_touchpad_y = (y - touchpad.y) / touchpad.h; 1689 SDL_SetJoystickVirtualTouchpad(virtual_joystick, 0, 0, true, virtual_touchpad_x, virtual_touchpad_y, 1.0f); 1690 } 1691} 1692 1693static void VirtualGamepadMouseDown(float x, float y) 1694{ 1695 int element = GetGamepadImageElementAt(image, x, y); 1696 1697 if (element == SDL_GAMEPAD_ELEMENT_INVALID) { 1698 SDL_FPoint point; 1699 point.x = x; 1700 point.y = y; 1701 SDL_FRect touchpad; 1702 GetGamepadTouchpadArea(image, &touchpad); 1703 if (SDL_PointInRectFloat(&point, &touchpad)) { 1704 virtual_touchpad_active = true; 1705 virtual_touchpad_x = (x - touchpad.x) / touchpad.w; 1706 virtual_touchpad_y = (y - touchpad.y) / touchpad.h; 1707 SDL_SetJoystickVirtualTouchpad(virtual_joystick, 0, 0, true, virtual_touchpad_x, virtual_touchpad_y, 1.0f); 1708 } 1709 return; 1710 } 1711 1712 if (element < SDL_GAMEPAD_BUTTON_COUNT) { 1713 virtual_button_active = (SDL_GamepadButton)element; 1714 SDL_SetJoystickVirtualButton(virtual_joystick, virtual_button_active, true); 1715 } else { 1716 switch (element) { 1717 case SDL_GAMEPAD_ELEMENT_AXIS_LEFTX_NEGATIVE: 1718 case SDL_GAMEPAD_ELEMENT_AXIS_LEFTX_POSITIVE: 1719 case SDL_GAMEPAD_ELEMENT_AXIS_LEFTY_NEGATIVE: 1720 case SDL_GAMEPAD_ELEMENT_AXIS_LEFTY_POSITIVE: 1721 virtual_axis_active = SDL_GAMEPAD_AXIS_LEFTX; 1722 break; 1723 case SDL_GAMEPAD_ELEMENT_AXIS_RIGHTX_NEGATIVE: 1724 case SDL_GAMEPAD_ELEMENT_AXIS_RIGHTX_POSITIVE: 1725 case SDL_GAMEPAD_ELEMENT_AXIS_RIGHTY_NEGATIVE: 1726 case SDL_GAMEPAD_ELEMENT_AXIS_RIGHTY_POSITIVE: 1727 virtual_axis_active = SDL_GAMEPAD_AXIS_RIGHTX; 1728 break; 1729 case SDL_GAMEPAD_ELEMENT_AXIS_LEFT_TRIGGER: 1730 virtual_axis_active = SDL_GAMEPAD_AXIS_LEFT_TRIGGER; 1731 break; 1732 case SDL_GAMEPAD_ELEMENT_AXIS_RIGHT_TRIGGER: 1733 virtual_axis_active = SDL_GAMEPAD_AXIS_RIGHT_TRIGGER; 1734 break; 1735 } 1736 virtual_axis_start_x = x; 1737 virtual_axis_start_y = y; 1738 } 1739} 1740 1741static void VirtualGamepadMouseUp(float x, float y) 1742{ 1743 if (virtual_button_active != SDL_GAMEPAD_BUTTON_INVALID) { 1744 SDL_SetJoystickVirtualButton(virtual_joystick, virtual_button_active, false); 1745 virtual_button_active = SDL_GAMEPAD_BUTTON_INVALID; 1746 } 1747 1748 if (virtual_axis_active != SDL_GAMEPAD_AXIS_INVALID) { 1749 if (virtual_axis_active == SDL_GAMEPAD_AXIS_LEFT_TRIGGER || 1750 virtual_axis_active == SDL_GAMEPAD_AXIS_RIGHT_TRIGGER) { 1751 SDL_SetJoystickVirtualAxis(virtual_joystick, virtual_axis_active, SDL_JOYSTICK_AXIS_MIN); 1752 } else { 1753 SDL_SetJoystickVirtualAxis(virtual_joystick, virtual_axis_active, 0); 1754 SDL_SetJoystickVirtualAxis(virtual_joystick, virtual_axis_active + 1, 0); 1755 } 1756 virtual_axis_active = SDL_GAMEPAD_AXIS_INVALID; 1757 } 1758 1759 if (virtual_touchpad_active) { 1760 SDL_SetJoystickVirtualTouchpad(virtual_joystick, 0, 0, false, virtual_touchpad_x, virtual_touchpad_y, 0.0f); 1761 virtual_touchpad_active = false; 1762 } 1763} 1764 1765static void DrawGamepadWaiting(SDL_Renderer *renderer) 1766{ 1767 const char *text = "Waiting for gamepad, press A to add a virtual controller"; 1768 float x, y; 1769 1770 x = SCREEN_WIDTH / 2 - (FONT_CHARACTER_SIZE * SDL_strlen(text)) / 2; 1771 y = TITLE_HEIGHT / 2 - FONT_CHARACTER_SIZE / 2; 1772 SDLTest_DrawString(renderer, x, y, text); 1773} 1774 1775static void DrawGamepadInfo(SDL_Renderer *renderer) 1776{ 1777 const char *type; 1778 const char *serial; 1779 char text[128]; 1780 float x, y; 1781 1782 if (title_highlighted) { 1783 Uint8 r, g, b, a; 1784 1785 SDL_GetRenderDrawColor(renderer, &r, &g, &b, &a); 1786 1787 if (title_pressed) { 1788 SDL_SetRenderDrawColor(renderer, PRESSED_COLOR); 1789 } else { 1790 SDL_SetRenderDrawColor(renderer, HIGHLIGHT_COLOR); 1791 } 1792 SDL_RenderFillRect(renderer, &title_area); 1793 1794 SDL_SetRenderDrawColor(renderer, r, g, b, a); 1795 } 1796 1797 if (type_highlighted) { 1798 Uint8 r, g, b, a; 1799 1800 SDL_GetRenderDrawColor(renderer, &r, &g, &b, &a); 1801 1802 if (type_pressed) { 1803 SDL_SetRenderDrawColor(renderer, PRESSED_COLOR); 1804 } else { 1805 SDL_SetRenderDrawColor(renderer, HIGHLIGHT_COLOR); 1806 } 1807 SDL_RenderFillRect(renderer, &type_area); 1808 1809 SDL_SetRenderDrawColor(renderer, r, g, b, a); 1810 } 1811 1812 if (controller->joystick) { 1813 SDL_snprintf(text, sizeof(text), "(%" SDL_PRIu32 ")", SDL_GetJoystickID(controller->joystick)); 1814 x = SCREEN_WIDTH - (FONT_CHARACTER_SIZE * SDL_strlen(text)) - 8.0f; 1815 y = 8.0f; 1816 SDLTest_DrawString(renderer, x, y, text); 1817 } 1818 1819 if (controller_name && *controller_name) { 1820 x = title_area.x + title_area.w / 2 - (FONT_CHARACTER_SIZE * SDL_strlen(controller_name)) / 2; 1821 y = title_area.y + title_area.h / 2 - FONT_CHARACTER_SIZE / 2; 1822 SDLTest_DrawString(renderer, x, y, controller_name); 1823 } 1824 1825 if (SDL_IsJoystickVirtual(controller->id)) { 1826 SDL_strlcpy(text, "Click on the gamepad image below to generate input", sizeof(text)); 1827 x = SCREEN_WIDTH / 2 - (FONT_CHARACTER_SIZE * SDL_strlen(text)) / 2; 1828 y = TITLE_HEIGHT / 2 - FONT_CHARACTER_SIZE / 2 + FONT_LINE_HEIGHT + 2.0f; 1829 SDLTest_DrawString(renderer, x, y, text); 1830 } 1831 1832 type = GetGamepadTypeString(SDL_GetGamepadType(controller->gamepad)); 1833 x = type_area.x + type_area.w / 2 - (FONT_CHARACTER_SIZE * SDL_strlen(type)) / 2; 1834 y = type_area.y + type_area.h / 2 - FONT_CHARACTER_SIZE / 2; 1835 SDLTest_DrawString(renderer, x, y, type); 1836 1837 if (display_mode == CONTROLLER_MODE_TESTING) { 1838 Uint64 steam_handle = SDL_GetGamepadSteamHandle(controller->gamepad); 1839 if (steam_handle) { 1840 SDL_snprintf(text, SDL_arraysize(text), "Steam: 0x%.16" SDL_PRIx64, steam_handle); 1841 y = SCREEN_HEIGHT - 2 * (8.0f + FONT_LINE_HEIGHT); 1842 x = SCREEN_WIDTH - 8.0f - (FONT_CHARACTER_SIZE * SDL_strlen(text)); 1843 SDLTest_DrawString(renderer, x, y, text); 1844 } 1845 1846 SDL_snprintf(text, SDL_arraysize(text), "VID: 0x%.4x PID: 0x%.4x", 1847 SDL_GetJoystickVendor(controller->joystick), 1848 SDL_GetJoystickProduct(controller->joystick)); 1849 y = SCREEN_HEIGHT - 8.0f - FONT_LINE_HEIGHT; 1850 x = SCREEN_WIDTH - 8.0f - (FONT_CHARACTER_SIZE * SDL_strlen(text)); 1851 SDLTest_DrawString(renderer, x, y, text); 1852 1853 serial = SDL_GetJoystickSerial(controller->joystick); 1854 if (serial && *serial) { 1855 SDL_snprintf(text, SDL_arraysize(text), "Serial: %s", serial); 1856 x = SCREEN_WIDTH / 2 - (FONT_CHARACTER_SIZE * SDL_strlen(text)) / 2; 1857 y = SCREEN_HEIGHT - 8.0f - FONT_LINE_HEIGHT; 1858 SDLTest_DrawString(renderer, x, y, text); 1859 } 1860 } 1861} 1862 1863static const char *GetButtonLabel(SDL_GamepadType type, SDL_GamepadButton button) 1864{ 1865 switch (SDL_GetGamepadButtonLabelForType(type, button)) { 1866 case SDL_GAMEPAD_BUTTON_LABEL_A: 1867 return "A"; 1868 case SDL_GAMEPAD_BUTTON_LABEL_B: 1869 return "B"; 1870 case SDL_GAMEPAD_BUTTON_LABEL_X: 1871 return "X"; 1872 case SDL_GAMEPAD_BUTTON_LABEL_Y: 1873 return "Y"; 1874 case SDL_GAMEPAD_BUTTON_LABEL_CROSS: 1875 return "Cross (X)"; 1876 case SDL_GAMEPAD_BUTTON_LABEL_CIRCLE: 1877 return "Circle"; 1878 case SDL_GAMEPAD_BUTTON_LABEL_SQUARE: 1879 return "Square"; 1880 case SDL_GAMEPAD_BUTTON_LABEL_TRIANGLE: 1881 return "Triangle"; 1882 default: 1883 return "UNKNOWN"; 1884 } 1885} 1886 1887static void DrawBindingTips(SDL_Renderer *renderer) 1888{ 1889 const char *text; 1890 SDL_FRect image_area, button_area; 1891 float x, y; 1892 1893 GetGamepadImageArea(image, &image_area); 1894 GetGamepadButtonArea(done_mapping_button, &button_area); 1895 x = image_area.x + image_area.w / 2; 1896 y = image_area.y + image_area.h; 1897 y += (button_area.y - y - FONT_CHARACTER_SIZE) / 2; 1898 1899 text = GetBindingInstruction(); 1900 1901 if (binding_element == SDL_GAMEPAD_ELEMENT_INVALID) { 1902 SDLTest_DrawString(renderer, x - (FONT_CHARACTER_SIZE * SDL_strlen(text)) / 2, y, text); 1903 } else { 1904 Uint8 r, g, b, a; 1905 SDL_FRect rect; 1906 SDL_GamepadButton action_forward = SDL_GAMEPAD_BUTTON_SOUTH; 1907 bool bound_forward = MappingHasElement(controller->mapping, action_forward); 1908 SDL_GamepadButton action_backward = SDL_GAMEPAD_BUTTON_EAST; 1909 bool bound_backward = MappingHasElement(controller->mapping, action_backward); 1910 SDL_GamepadButton action_delete = SDL_GAMEPAD_BUTTON_WEST; 1911 bool bound_delete = MappingHasElement(controller->mapping, action_delete); 1912 1913 y -= (FONT_CHARACTER_SIZE + BUTTON_MARGIN) / 2; 1914 1915 rect.w = 2.0f + (FONT_CHARACTER_SIZE * SDL_strlen(text)) + 2.0f; 1916 rect.h = 2.0f + FONT_CHARACTER_SIZE + 2.0f; 1917 rect.x = x - rect.w / 2; 1918 rect.y = y - 2.0f; 1919 1920 SDL_GetRenderDrawColor(renderer, &r, &g, &b, &a); 1921 SDL_SetRenderDrawColor(renderer, SELECTED_COLOR); 1922 SDL_RenderFillRect(renderer, &rect); 1923 SDL_SetRenderDrawColor(renderer, r, g, b, a); 1924 SDLTest_DrawString(renderer, x - (FONT_CHARACTER_SIZE * SDL_strlen(text)) / 2, y, text); 1925 1926 y += (FONT_CHARACTER_SIZE + BUTTON_MARGIN); 1927 1928 if (binding_element == SDL_GAMEPAD_ELEMENT_NAME) { 1929 text = "(press RETURN to complete)"; 1930 } else if (binding_element == SDL_GAMEPAD_ELEMENT_TYPE || 1931 binding_element == action_forward || 1932 binding_element == action_backward) { 1933 text = "(press ESC to cancel)"; 1934 } else { 1935 static char dynamic_text[128]; 1936 SDL_GamepadType type = GetGamepadImageType(image); 1937 if (binding_flow && bound_forward && bound_backward) { 1938 if (binding_element != action_delete && bound_delete) { 1939 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)); 1940 } else { 1941 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)); 1942 } 1943 text = dynamic_text; 1944 } else { 1945 text = "(press SPACE to delete and ESC to cancel)"; 1946 } 1947 } 1948 SDLTest_DrawString(renderer, x - (FONT_CHARACTER_SIZE * SDL_strlen(text)) / 2, y, text); 1949 } 1950} 1951 1952static void UpdateGamepadEffects(void) 1953{ 1954 if (display_mode != CONTROLLER_MODE_TESTING || !controller->gamepad) { 1955 return; 1956 } 1957 1958 /* Update LED based on left thumbstick position */ 1959 { 1960 Sint16 x = SDL_GetGamepadAxis(controller->gamepad, SDL_GAMEPAD_AXIS_LEFTX); 1961 Sint16 y = SDL_GetGamepadAxis(controller->gamepad, SDL_GAMEPAD_AXIS_LEFTY); 1962 1963 if (!set_LED) { 1964 set_LED = (x < -8000 || x > 8000 || y > 8000); 1965 } 1966 if (set_LED) { 1967 Uint8 r, g, b; 1968 1969 if (x < 0) { 1970 r = (Uint8)(((~x) * 255) / 32767); 1971 b = 0; 1972 } else { 1973 r = 0; 1974 b = (Uint8)(((int)(x)*255) / 32767); 1975 } 1976 if (y > 0) { 1977 g = (Uint8)(((int)(y)*255) / 32767); 1978 } else { 1979 g = 0; 1980 } 1981 1982 SDL_SetGamepadLED(controller->gamepad, r, g, b); 1983 } 1984 } 1985 1986 if (controller->trigger_effect == 0) { 1987 /* Update rumble based on trigger state */ 1988 { 1989 Sint16 left = SDL_GetGamepadAxis(controller->gamepad, SDL_GAMEPAD_AXIS_LEFT_TRIGGER); 1990 Sint16 right = SDL_GetGamepadAxis(controller->gamepad, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER); 1991 Uint16 low_frequency_rumble = ConvertAxisToRumble(left); 1992 Uint16 high_frequency_rumble = ConvertAxisToRumble(right); 1993 SDL_RumbleGamepad(controller->gamepad, low_frequency_rumble, high_frequency_rumble, 250); 1994 } 1995 1996 /* Update trigger rumble based on thumbstick state */ 1997 { 1998 Sint16 left = SDL_GetGamepadAxis(controller->gamepad, SDL_GAMEPAD_AXIS_LEFTY); 1999 Sint16 right = SDL_GetGamepadAxis(controller->gamepad, SDL_GAMEPAD_AXIS_RIGHTY); 2000 Uint16 left_rumble = ConvertAxisToRumble(~left); 2001 Uint16 right_rumble = ConvertAxisToRumble(~right); 2002 2003 SDL_RumbleGamepadTriggers(controller->gamepad, left_rumble, right_rumble, 250); 2004 } 2005 } 2006} 2007 2008SDL_AppResult SDLCALL SDL_AppEvent(void *appstate, SDL_Event *event) 2009{ 2010 SDL_ConvertEventToRenderCoordinates(screen, event); 2011 2012 switch (event->type) { 2013 case SDL_EVENT_JOYSTICK_ADDED: 2014 AddController(event->jdevice.which, true); 2015 break; 2016 2017 case SDL_EVENT_JOYSTICK_REMOVED: 2018 DelController(event->jdevice.which); 2019 break; 2020 2021 case SDL_EVENT_JOYSTICK_AXIS_MOTION: 2022 if (display_mode == CONTROLLER_MODE_TESTING) { 2023 if (event->jaxis.value <= (-SDL_JOYSTICK_AXIS_MAX / 2) || event->jaxis.value >= (SDL_JOYSTICK_AXIS_MAX / 2)) { 2024 SetController(event->jaxis.which); 2025 } 2026 } else if (display_mode == CONTROLLER_MODE_BINDING && 2027 event->jaxis.which == controller->id && 2028 event->jaxis.axis < controller->num_axes && 2029 binding_element != SDL_GAMEPAD_ELEMENT_INVALID) { 2030 const int MAX_ALLOWED_JITTER = SDL_JOYSTICK_AXIS_MAX / 80; /* ShanWan PS3 gamepad needed 96 */ 2031 AxisState *pAxisState = &controller->axis_state[event->jaxis.axis]; 2032 int nValue = event->jaxis.value; 2033 int nCurrentDistance, nFarthestDistance; 2034 if (!pAxisState->m_bMoving) { 2035 Sint16 nInitialValue; 2036 pAxisState->m_bMoving = SDL_GetJoystickAxisInitialState(controller->joystick, event->jaxis.axis, &nInitialValue); 2037 pAxisState->m_nLastValue = nValue; 2038 pAxisState->m_nStartingValue = nInitialValue; 2039 pAxisState->m_nFarthestValue = nInitialValue; 2040 } else if (SDL_abs(nValue - pAxisState->m_nLastValue) <= MAX_ALLOWED_JITTER) { 2041 break; 2042 } else { 2043 pAxisState->m_nLastValue = nValue; 2044 } 2045 nCurrentDistance = SDL_abs(nValue - pAxisState->m_nStartingValue); 2046 nFarthestDistance = SDL_abs(pAxisState->m_nFarthestValue - pAxisState->m_nStartingValue); 2047 if (nCurrentDistance > nFarthestDistance) { 2048 pAxisState->m_nFarthestValue = nValue; 2049 nFarthestDistance = SDL_abs(pAxisState->m_nFarthestValue - pAxisState->m_nStartingValue); 2050 } 2051 2052#ifdef DEBUG_AXIS_MAPPING 2053 SDL_Log("AXIS %d nValue %d nCurrentDistance %d nFarthestDistance %d", event->jaxis.axis, nValue, nCurrentDistance, nFarthestDistance); 2054#endif 2055 /* If we've gone out far enough and started to come back, let's bind this axis */ 2056 if (nFarthestDistance >= 16000 && nCurrentDistance <= 10000) { 2057 char binding[12]; 2058 int axis_min = StandardizeAxisValue(pAxisState->m_nStartingValue); 2059 int axis_max = StandardizeAxisValue(pAxisState->m_nFarthestValue); 2060 2061 if (axis_min == 0 && axis_max == SDL_JOYSTICK_AXIS_MIN) { 2062 /* The negative half axis */ 2063 (void)SDL_snprintf(binding, sizeof(binding), "-a%d", event->jaxis.axis); 2064 } else if (axis_min == 0 && axis_max == SDL_JOYSTICK_AXIS_MAX) { 2065 /* The positive half axis */ 2066 (void)SDL_snprintf(binding, sizeof(binding), "+a%d", event->jaxis.axis); 2067 } else { 2068 (void)SDL_snprintf(binding, sizeof(binding), "a%d", event->jaxis.axis); 2069 if (axis_min > axis_max) { 2070 /* Invert the axis */ 2071 SDL_strlcat(binding, "~", SDL_arraysize(binding)); 2072 } 2073 } 2074#ifdef DEBUG_AXIS_MAPPING 2075 SDL_Log("AXIS %d axis_min = %d, axis_max = %d, binding = %s", event->jaxis.axis, axis_min, axis_max, binding); 2076#endif 2077 CommitBindingElement(binding, false); 2078 } 2079 } 2080 break; 2081 2082 case SDL_EVENT_JOYSTICK_BUTTON_DOWN: 2083 if (display_mode == CONTROLLER_MODE_TESTING) { 2084 SetController(event->jbutton.which); 2085 } 2086 break; 2087 2088 case SDL_EVENT_JOYSTICK_BUTTON_UP: 2089 if (display_mode == CONTROLLER_MODE_BINDING && 2090 event->jbutton.which == controller->id && 2091 binding_element != SDL_GAMEPAD_ELEMENT_INVALID) { 2092 char binding[12]; 2093 2094 SDL_snprintf(binding, sizeof(binding), "b%d", event->jbutton.button); 2095 CommitBindingElement(binding, false); 2096 } 2097 break; 2098 2099 case SDL_EVENT_JOYSTICK_HAT_MOTION: 2100 if (display_mode == CONTROLLER_MODE_BINDING && 2101 event->jhat.which == controller->id && 2102 event->jhat.value != SDL_HAT_CENTERED && 2103 binding_element != SDL_GAMEPAD_ELEMENT_INVALID) { 2104 char binding[12]; 2105 2106 SDL_snprintf(binding, sizeof(binding), "h%d.%d", event->jhat.hat, event->jhat.value); 2107 CommitBindingElement(binding, false); 2108 } 2109 break; 2110 2111 case SDL_EVENT_GAMEPAD_ADDED: 2112 HandleGamepadAdded(event->gdevice.which, true); 2113 break; 2114 2115 case SDL_EVENT_GAMEPAD_REMOVED: 2116 HandleGamepadRemoved(event->gdevice.which); 2117 break; 2118 2119 case SDL_EVENT_GAMEPAD_REMAPPED: 2120 HandleGamepadRemapped(event->gdevice.which); 2121 break; 2122 2123 case SDL_EVENT_GAMEPAD_STEAM_HANDLE_UPDATED: 2124 RefreshControllerName(); 2125 break; 2126 2127#ifdef VERBOSE_TOUCHPAD 2128 case SDL_EVENT_GAMEPAD_TOUCHPAD_DOWN: 2129 case SDL_EVENT_GAMEPAD_TOUCHPAD_MOTION: 2130 case SDL_EVENT_GAMEPAD_TOUCHPAD_UP: 2131 SDL_Log("Gamepad %" SDL_PRIu32 " touchpad %" SDL_PRIs32 " finger %" SDL_PRIs32 " %s %.2f, %.2f, %.2f", 2132 event->gtouchpad.which, 2133 event->gtouchpad.touchpad, 2134 event->gtouchpad.finger, 2135 (event->type == SDL_EVENT_GAMEPAD_TOUCHPAD_DOWN ? "pressed at" : (event->type == SDL_EVENT_GAMEPAD_TOUCHPAD_UP ? "released at" : "moved to")), 2136 event->gtouchpad.x, 2137 event->gtouchpad.y, 2138 event->gtouchpad.pressure); 2139 break; 2140#endif /* VERBOSE_TOUCHPAD */ 2141 2142 2143 case SDL_EVENT_GAMEPAD_SENSOR_UPDATE: 2144#ifdef VERBOSE_SENSORS 2145 SDL_Log("Gamepad %" SDL_PRIu32 " sensor %s: %.2f, %.2f, %.2f (%" SDL_PRIu64 ")", 2146 event->gsensor.which, 2147 GetSensorName((SDL_SensorType) event->gsensor.sensor), 2148 event->gsensor.data[0], 2149 event->gsensor.data[1], 2150 event->gsensor.data[2], 2151 event->gsensor.sensor_timestamp); 2152#endif /* VERBOSE_SENSORS */ 2153 HandleGamepadSensorEvent(event); 2154 break; 2155 2156#ifdef VERBOSE_AXES 2157 case SDL_EVENT_GAMEPAD_AXIS_MOTION: 2158 if (display_mode == CONTROLLER_MODE_TESTING) { 2159 if (event->gaxis.value <= (-SDL_JOYSTICK_AXIS_MAX / 2) || event->gaxis.value >= (SDL_JOYSTICK_AXIS_MAX / 2)) { 2160 SetController(event->gaxis.which); 2161 } 2162 } 2163 SDL_Log("Gamepad %" SDL_PRIu32 " axis %s changed to %d", 2164 event->gaxis.which, 2165 SDL_GetGamepadStringForAxis((SDL_GamepadAxis) event->gaxis.axis), 2166 event->gaxis.value); 2167 break; 2168#endif /* VERBOSE_AXES */ 2169 2170 case SDL_EVENT_GAMEPAD_BUTTON_DOWN: 2171 case SDL_EVENT_GAMEPAD_BUTTON_UP: 2172 if (display_mode == CONTROLLER_MODE_TESTING) { 2173 if (event->type == SDL_EVENT_GAMEPAD_BUTTON_DOWN) { 2174 SetController(event->gbutton.which); 2175 } 2176 } 2177#ifdef VERBOSE_BUTTONS 2178 SDL_Log("Gamepad %" SDL_PRIu32 " button %s %s", 2179 event->gbutton.which, 2180 SDL_GetGamepadStringForButton((SDL_GamepadButton) event->gbutton.button), 2181 event->gbutton.state ? "pressed" : "released"); 2182#endif /* VERBOSE_BUTTONS */ 2183 2184 if (display_mode == CONTROLLER_MODE_TESTING) { 2185 if (event->type == SDL_EVENT_GAMEPAD_BUTTON_DOWN && 2186 controller && SDL_GetGamepadType(controller->gamepad) == SDL_GAMEPAD_TYPE_PS5) { 2187 /* Cycle PS5 audio routing when the microphone button is pressed */ 2188 if (event->gbutton.button == SDL_GAMEPAD_BUTTON_MISC1) { 2189 CyclePS5AudioRoute(controller); 2190 } 2191 2192 /* Cycle PS5 trigger effects when the triangle button is pressed */ 2193 if (event->gbutton.button == SDL_GAMEPAD_BUTTON_NORTH) { 2194 CyclePS5TriggerEffect(controller); 2195 } 2196 } 2197 } 2198 break; 2199 2200 case SDL_EVENT_MOUSE_BUTTON_DOWN: 2201 if (virtual_joystick && controller && controller->joystick == virtual_joystick) { 2202 VirtualGamepadMouseDown(event->button.x, event->button.y); 2203 } 2204 UpdateButtonHighlights(event->button.x, event->button.y, event->button.down); 2205 break; 2206 2207 case SDL_EVENT_MOUSE_BUTTON_UP: 2208 if (virtual_joystick && controller && controller->joystick == virtual_joystick) { 2209 VirtualGamepadMouseUp(event->button.x, event->button.y); 2210 } 2211 2212 if (display_mode == CONTROLLER_MODE_TESTING) { 2213 if (controller && GamepadButtonContains(GetGyroResetButton(gyro_elements), event->button.x, event->button.y)) { 2214 ResetGyroOrientation(controller->imu_state); 2215 } else if (controller && GamepadButtonContains(GetGyroCalibrateButton(gyro_elements), event->button.x, event->button.y)) { 2216 BeginNoiseCalibrationPhase(controller->imu_state); 2217 } else if (GamepadButtonContains(setup_mapping_button, event->button.x, event->button.y)) { 2218 SetDisplayMode(CONTROLLER_MODE_BINDING); 2219 } 2220 } else if (display_mode == CONTROLLER_MODE_BINDING) { 2221 if (GamepadButtonContains(done_mapping_button, event->button.x, event->button.y)) { 2222 if (controller->mapping) { 2223 SDL_Log("Mapping complete:"); 2224 SDL_Log("%s", controller->mapping); 2225 } 2226 SetDisplayMode(CONTROLLER_MODE_TESTING); 2227 } else if (GamepadButtonContains(cancel_button, event->button.x, event->button.y)) { 2228 CancelMapping(); 2229 } else if (GamepadButtonContains(clear_button, event->button.x, event->button.y)) { 2230 ClearMapping(); 2231 } else if (controller->has_bindings && 2232 GamepadButtonContains(copy_button, event->button.x, event->button.y)) { 2233 CopyMapping(); 2234 } else if (GamepadButtonContains(paste_button, event->button.x, event->button.y)) { 2235 PasteMapping(); 2236 } else if (title_pressed) { 2237 SetCurrentBindingElement(SDL_GAMEPAD_ELEMENT_NAME, false); 2238 } else if (type_pressed) { 2239 SetCurrentBindingElement(SDL_GAMEPAD_ELEMENT_TYPE, false); 2240 } else if (binding_element == SDL_GAMEPAD_ELEMENT_TYPE) { 2241 int type = GetGamepadTypeDisplayAt(gamepad_type, event->button.x, event->button.y); 2242 if (type != SDL_GAMEPAD_TYPE_UNSELECTED) { 2243 CommitGamepadType((SDL_GamepadType)type); 2244 StopBinding(); 2245 } 2246 } else { 2247 int gamepad_element = SDL_GAMEPAD_ELEMENT_INVALID; 2248 char *joystick_element; 2249 2250 if (controller->joystick != virtual_joystick) { 2251 gamepad_element = GetGamepadImageElementAt(image, event->button.x, event->button.y); 2252 } 2253 if (gamepad_element == SDL_GAMEPAD_ELEMENT_INVALID) { 2254 gamepad_element = GetGamepadDisplayElementAt(gamepad_elements, controller->gamepad, event->button.x, event->button.y); 2255 } 2256 if (gamepad_element != SDL_GAMEPAD_ELEMENT_INVALID) { 2257 /* Set this to false if you don't want to start the binding flow at this point */ 2258 const bool should_start_flow = true; 2259 SetCurrentBindingElement(gamepad_element, should_start_flow); 2260 } 2261 2262 joystick_element = GetJoystickDisplayElementAt(joystick_elements, controller->joystick, event->button.x, event->button.y); 2263 if (joystick_element) { 2264 CommitBindingElement(joystick_element, true); 2265 SDL_free(joystick_element); 2266 } 2267 } 2268 } 2269 UpdateButtonHighlights(event->button.x, event->button.y, event->button.down); 2270 break; 2271 2272 case SDL_EVENT_MOUSE_MOTION: 2273 if (virtual_joystick && controller && controller->joystick == virtual_joystick) { 2274 VirtualGamepadMouseMotion(event->motion.x, event->motion.y); 2275 } 2276 UpdateButtonHighlights(event->motion.x, event->motion.y, event->motion.state ? true : false); 2277 break; 2278 2279 case SDL_EVENT_KEY_DOWN: 2280 if (display_mode == CONTROLLER_MODE_TESTING) { 2281 if (event->key.key >= SDLK_0 && event->key.key <= SDLK_9) { 2282 if (controller && controller->gamepad) { 2283 int player_index = (event->key.key - SDLK_0); 2284 2285 SDL_SetGamepadPlayerIndex(controller->gamepad, player_index); 2286 } 2287 break; 2288 } else if (event->key.key == SDLK_A) { 2289 OpenVirtualGamepad(); 2290 } else if (event->key.key == SDLK_D) { 2291 CloseVirtualGamepad(); 2292 } else if (event->key.key == SDLK_R && (event->key.mod & SDL_KMOD_CTRL)) { 2293 SDL_ReloadGamepadMappings(); 2294 } else if (event->key.key == SDLK_ESCAPE) { 2295 done = true; 2296 } else if (event->key.key == SDLK_SPACE) { 2297 if (controller && controller->imu_state) { 2298 ResetGyroOrientation(controller->imu_state); 2299 } 2300 } 2301 } else if (display_mode == CONTROLLER_MODE_BINDING) { 2302 if (event->key.key == SDLK_C && (event->key.mod & SDL_KMOD_CTRL)) { 2303 if (binding_element == SDL_GAMEPAD_ELEMENT_NAME) { 2304 CopyControllerName(); 2305 } else { 2306 CopyMapping(); 2307 } 2308 } else if (event->key.key == SDLK_V && (event->key.mod & SDL_KMOD_CTRL)) { 2309 if (binding_element == SDL_GAMEPAD_ELEMENT_NAME) { 2310 ClearControllerName(); 2311 PasteControllerName(); 2312 } else { 2313 PasteMapping(); 2314 } 2315 } else if (event->key.key == SDLK_X && (event->key.mod & SDL_KMOD_CTRL)) { 2316 if (binding_element == SDL_GAMEPAD_ELEMENT_NAME) { 2317 CopyControllerName(); 2318 ClearControllerName(); 2319 } else { 2320 CopyMapping(); 2321 ClearMapping(); 2322 } 2323 } else if (event->key.key == SDLK_SPACE) { 2324 if (binding_element != SDL_GAMEPAD_ELEMENT_NAME) { 2325 ClearBinding(); 2326 } 2327 } else if (event->key.key == SDLK_BACKSPACE) { 2328 if (binding_element == SDL_GAMEPAD_ELEMENT_NAME) { 2329 BackspaceControllerName(); 2330 } 2331 } else if (event->key.key == SDLK_RETURN) { 2332 if (binding_element == SDL_GAMEPAD_ELEMENT_NAME) { 2333 StopBinding(); 2334 } 2335 } else if (event->key.key == SDLK_ESCAPE) { 2336 if (binding_element != SDL_GAMEPAD_ELEMENT_INVALID) { 2337 StopBinding(); 2338 } else { 2339 CancelMapping(); 2340 } 2341 } 2342 } 2343 break; 2344 case SDL_EVENT_TEXT_INPUT: 2345 if (display_mode == CONTROLLER_MODE_BINDING) { 2346 if (binding_element == SDL_GAMEPAD_ELEMENT_NAME) { 2347 AddControllerNameText(event->text.text); 2348 } 2349 } 2350 break; 2351 case SDL_EVENT_QUIT: 2352 done = true; 2353 break; 2354 default: 2355 break; 2356 } 2357 2358 if (done) { 2359 return SDL_APP_SUCCESS; 2360 } else { 2361 return SDL_APP_CONTINUE; 2362 } 2363} 2364 2365SDL_AppResult SDLCALL SDL_AppIterate(void *appstate) 2366{ 2367 /* If we have a virtual controller, send virtual sensor readings */ 2368 if (virtual_joystick) { 2369 float accel_data[3] = { 0.0f, SDL_STANDARD_GRAVITY, 0.0f }; 2370 float gyro_data[3] = { 0.01f, -0.01f, 0.0f }; 2371 Uint64 sensor_timestamp = SDL_GetTicksNS(); 2372 SDL_SendJoystickVirtualSensorData(virtual_joystick, SDL_SENSOR_ACCEL, sensor_timestamp, accel_data, SDL_arraysize(accel_data)); 2373 SDL_SendJoystickVirtualSensorData(virtual_joystick, SDL_SENSOR_GYRO, sensor_timestamp, gyro_data, SDL_arraysize(gyro_data)); 2374 } 2375 2376 /* Wait 30 ms for joystick events to stop coming in, 2377 in case a gamepad sends multiple events for a single control (e.g. axis and button for trigger) 2378 */ 2379 if (binding_advance_time && SDL_GetTicks() > (binding_advance_time + 30)) { 2380 if (binding_flow) { 2381 SetNextBindingElement(); 2382 } else { 2383 StopBinding(); 2384 } 2385 } 2386 2387 /* blank screen, set up for drawing this frame. */ 2388 SDL_SetRenderDrawColor(screen, 0xFF, 0xFF, 0xFF, SDL_ALPHA_OPAQUE); 2389 SDL_RenderClear(screen); 2390 SDL_SetRenderDrawColor(screen, 0x10, 0x10, 0x10, SDL_ALPHA_OPAQUE); 2391 2392 if (controller) { 2393 SetGamepadImageShowingFront(image, ShowingFront()); 2394 UpdateGamepadImageFromGamepad(image, controller->gamepad); 2395 if (display_mode == CONTROLLER_MODE_BINDING && 2396 binding_element != SDL_GAMEPAD_ELEMENT_INVALID) { 2397 SetGamepadImageElement(image, binding_element, true); 2398 } 2399 RenderGamepadImage(image); 2400 2401 if (binding_element == SDL_GAMEPAD_ELEMENT_TYPE) { 2402 SetGamepadTypeDisplayRealType(gamepad_type, SDL_GetRealGamepadType(controller->gamepad)); 2403 RenderGamepadTypeDisplay(gamepad_type); 2404 } else { 2405 RenderGamepadDisplay(gamepad_elements, controller->gamepad); 2406 } 2407 RenderJoystickDisplay(joystick_elements, controller->joystick); 2408 2409 if (display_mode == CONTROLLER_MODE_TESTING) { 2410 RenderGamepadButton(setup_mapping_button); 2411 RenderGyroDisplay(gyro_elements, gamepad_elements, controller->gamepad); 2412 } else if (display_mode == CONTROLLER_MODE_BINDING) { 2413 DrawBindingTips(screen); 2414 RenderGamepadButton(done_mapping_button); 2415 RenderGamepadButton(cancel_button); 2416 RenderGamepadButton(clear_button); 2417 if (controller->has_bindings) { 2418 RenderGamepadButton(copy_button); 2419 } 2420 RenderGamepadButton(paste_button); 2421 } 2422 2423 DrawGamepadInfo(screen); 2424 2425 UpdateGamepadEffects(); 2426 } else { 2427 DrawGamepadWaiting(screen); 2428 } 2429 SDL_Delay(16); 2430 SDL_RenderPresent(screen); 2431 2432 return SDL_APP_CONTINUE; 2433} 2434 2435SDL_AppResult SDLCALL SDL_AppInit(void **appstate, int argc, char *argv[]) 2436{ 2437 bool show_mappings = false; 2438 int i; 2439 float content_scale; 2440 int screen_width, screen_height; 2441 SDL_FRect area; 2442 int gamepad_index = -1; 2443 2444 /* Initialize test framework */ 2445 state = SDLTest_CommonCreateState(argv, 0); 2446 if (!state) { 2447 return SDL_APP_FAILURE; 2448 } 2449 2450 SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI, "1"); 2451 SDL_SetHint(SDL_HINT_JOYSTICK_ENHANCED_REPORTS, "auto"); 2452 SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_STEAM, "1"); 2453 SDL_SetHint(SDL_HINT_JOYSTICK_ROG_CHAKRAM, "1"); 2454 SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1"); 2455 SDL_SetHint(SDL_HINT_JOYSTICK_LINUX_DEADZONES, "1"); 2456 2457 /* Enable input debug logging */ 2458 SDL_SetLogPriority(SDL_LOG_CATEGORY_INPUT, SDL_LOG_PRIORITY_DEBUG); 2459 2460 /* Parse commandline */ 2461 for (i = 1; i < argc;) { 2462 int consumed; 2463 2464 consumed = SDLTest_CommonArg(state, i); 2465 if (!consumed) { 2466 if (SDL_strcmp(argv[i], "--mappings") == 0) { 2467 show_mappings = true; 2468 consumed = 1; 2469 } else if (SDL_strcmp(argv[i], "--virtual") == 0) { 2470 OpenVirtualGamepad(); 2471 consumed = 1; 2472 } else if (gamepad_index < 0) { 2473 char *endptr = NULL; 2474 gamepad_index = (int)SDL_strtol(argv[i], &endptr, 0); 2475 if (endptr != argv[i] && *endptr == '\0' && gamepad_index >= 0) { 2476 consumed = 1; 2477 } 2478 } 2479 } 2480 if (consumed <= 0) { 2481 static const char *options[] = { "[--mappings]", "[--virtual]", "[index]", NULL }; 2482 SDLTest_CommonLogUsage(state, argv[0], options); 2483 return SDL_APP_FAILURE; 2484 } 2485 2486 i += consumed; 2487 } 2488 if (gamepad_index < 0) { 2489 gamepad_index = 0; 2490 } 2491 2492 /* Initialize SDL (Note: video is required to start event loop) */ 2493 if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK | SDL_INIT_GAMEPAD)) { 2494 SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't initialize SDL: %s", SDL_GetError()); 2495 return SDL_APP_FAILURE; 2496 } 2497 2498 SDL_AddGamepadMappingsFromFile("gamecontrollerdb.txt"); 2499 2500 if (show_mappings) { 2501 int count = 0; 2502 char **mappings = SDL_GetGamepadMappings(&count); 2503 int map_i; 2504 SDL_Log("Supported mappings:"); 2505 for (map_i = 0; map_i < count; ++map_i) { 2506 SDL_Log("\t%s", mappings[map_i]); 2507 } 2508 SDL_Log("%s", ""); 2509 SDL_free(mappings); 2510 } 2511 2512 /* Create a window to display gamepad state */ 2513 content_scale = SDL_GetDisplayContentScale(SDL_GetPrimaryDisplay()); 2514 if (content_scale == 0.0f) { 2515 content_scale = 1.0f; 2516 } 2517 screen_width = (int)SDL_ceilf(SCREEN_WIDTH * content_scale); 2518 screen_height = (int)SDL_ceilf(SCREEN_HEIGHT * content_scale); 2519 window = SDL_CreateWindow("SDL Controller Test", screen_width, screen_height, SDL_WINDOW_HIGH_PIXEL_DENSITY); 2520 if (!window) { 2521 SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't create window: %s", SDL_GetError()); 2522 return SDL_APP_FAILURE; 2523 } 2524 2525 screen = SDL_CreateRenderer(window, NULL); 2526 if (!screen) { 2527 SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't create renderer: %s", SDL_GetError()); 2528 SDL_DestroyWindow(window); 2529 return SDL_APP_FAILURE; 2530 } 2531 2532 SDL_SetRenderDrawColor(screen, 0x00, 0x00, 0x00, SDL_ALPHA_OPAQUE); 2533 SDL_RenderClear(screen); 2534 SDL_RenderPresent(screen); 2535 2536 /* scale for platforms that don't give you the window size you asked for. */ 2537 SDL_SetRenderLogicalPresentation(screen, (int)SCREEN_WIDTH, (int)SCREEN_HEIGHT, 2538 SDL_LOGICAL_PRESENTATION_LETTERBOX); 2539 2540 2541 title_area.w = GAMEPAD_WIDTH; 2542 title_area.h = FONT_CHARACTER_SIZE + 2 * BUTTON_MARGIN; 2543 title_area.x = PANEL_WIDTH + PANEL_SPACING; 2544 title_area.y = TITLE_HEIGHT / 2 - title_area.h / 2; 2545 2546 type_area.w = PANEL_WIDTH - 2 * BUTTON_MARGIN; 2547 type_area.h = FONT_CHARACTER_SIZE + 2 * BUTTON_MARGIN; 2548 type_area.x = BUTTON_MARGIN; 2549 type_area.y = TITLE_HEIGHT / 2 - type_area.h / 2; 2550 2551 image = CreateGamepadImage(screen); 2552 if (!image) { 2553 SDL_DestroyRenderer(screen); 2554 SDL_DestroyWindow(window); 2555 return SDL_APP_FAILURE; 2556 } 2557 SetGamepadImagePosition(image, PANEL_WIDTH + PANEL_SPACING, TITLE_HEIGHT); 2558 2559 gamepad_elements = CreateGamepadDisplay(screen); 2560 area.x = 0; 2561 area.y = TITLE_HEIGHT; 2562 area.w = PANEL_WIDTH; 2563 area.h = GAMEPAD_HEIGHT; 2564 SetGamepadDisplayArea(gamepad_elements, &area); 2565 2566 gyro_elements = CreateGyroDisplay(screen); 2567 const float vidReservedHeight = 24.0f; 2568 /* Bottom right of the screen */ 2569 area.w = SCREEN_WIDTH * 0.375f; 2570 area.h = SCREEN_HEIGHT * 0.475f; 2571 area.x = SCREEN_WIDTH - area.w; 2572 area.y = SCREEN_HEIGHT - area.h - vidReservedHeight; 2573 2574 SetGyroDisplayArea(gyro_elements, &area); 2575 InitCirclePoints3D(); 2576 2577 gamepad_type = CreateGamepadTypeDisplay(screen); 2578 area.x = 0; 2579 area.y = TITLE_HEIGHT; 2580 area.w = PANEL_WIDTH; 2581 area.h = GAMEPAD_HEIGHT; 2582 SetGamepadTypeDisplayArea(gamepad_type, &area); 2583 2584 joystick_elements = CreateJoystickDisplay(screen); 2585 area.x = PANEL_WIDTH + PANEL_SPACING + GAMEPAD_WIDTH + PANEL_SPACING; 2586 area.y = TITLE_HEIGHT; 2587 area.w = PANEL_WIDTH; 2588 area.h = GAMEPAD_HEIGHT; 2589 SetJoystickDisplayArea(joystick_elements, &area); 2590 2591 setup_mapping_button = CreateGamepadButton(screen, "Setup Mapping"); 2592 area.w = SDL_max(MINIMUM_BUTTON_WIDTH, GetGamepadButtonLabelWidth(setup_mapping_button) + 2 * BUTTON_PADDING); 2593 area.h = GetGamepadButtonLabelHeight(setup_mapping_button) + 2 * BUTTON_PADDING; 2594 area.x = BUTTON_MARGIN; 2595 area.y = SCREEN_HEIGHT - BUTTON_MARGIN - area.h; 2596 SetGamepadButtonArea(setup_mapping_button, &area); 2597 2598 cancel_button = CreateGamepadButton(screen, "Cancel"); 2599 area.w = SDL_max(MINIMUM_BUTTON_WIDTH, GetGamepadButtonLabelWidth(cancel_button) + 2 * BUTTON_PADDING); 2600 area.h = GetGamepadButtonLabelHeight(cancel_button) + 2 * BUTTON_PADDING; 2601 area.x = BUTTON_MARGIN; 2602 area.y = SCREEN_HEIGHT - BUTTON_MARGIN - area.h; 2603 SetGamepadButtonArea(cancel_button, &area); 2604 2605 clear_button = CreateGamepadButton(screen, "Clear"); 2606 area.x += area.w + BUTTON_PADDING; 2607 area.w = SDL_max(MINIMUM_BUTTON_WIDTH, GetGamepadButtonLabelWidth(clear_button) + 2 * BUTTON_PADDING); 2608 area.h = GetGamepadButtonLabelHeight(clear_button) + 2 * BUTTON_PADDING; 2609 area.y = SCREEN_HEIGHT - BUTTON_MARGIN - area.h; 2610 SetGamepadButtonArea(clear_button, &area); 2611 2612 copy_button = CreateGamepadButton(screen, "Copy"); 2613 area.x += area.w + BUTTON_PADDING; 2614 area.w = SDL_max(MINIMUM_BUTTON_WIDTH, GetGamepadButtonLabelWidth(copy_button) + 2 * BUTTON_PADDING); 2615 area.h = GetGamepadButtonLabelHeight(copy_button) + 2 * BUTTON_PADDING; 2616 area.y = SCREEN_HEIGHT - BUTTON_MARGIN - area.h; 2617 SetGamepadButtonArea(copy_button, &area); 2618 2619 paste_button = CreateGamepadButton(screen, "Paste"); 2620 area.x += area.w + BUTTON_PADDING; 2621 area.w = SDL_max(MINIMUM_BUTTON_WIDTH, GetGamepadButtonLabelWidth(paste_button) + 2 * BUTTON_PADDING); 2622 area.h = GetGamepadButtonLabelHeight(paste_button) + 2 * BUTTON_PADDING; 2623 area.y = SCREEN_HEIGHT - BUTTON_MARGIN - area.h; 2624 SetGamepadButtonArea(paste_button, &area); 2625 2626 done_mapping_button = CreateGamepadButton(screen, "Done"); 2627 area.w = SDL_max(MINIMUM_BUTTON_WIDTH, GetGamepadButtonLabelWidth(done_mapping_button) + 2 * BUTTON_PADDING); 2628 area.h = GetGamepadButtonLabelHeight(done_mapping_button) + 2 * BUTTON_PADDING; 2629 area.x = SCREEN_WIDTH / 2 - area.w / 2; 2630 area.y = SCREEN_HEIGHT - BUTTON_MARGIN - area.h; 2631 SetGamepadButtonArea(done_mapping_button, &area); 2632 2633 /* Process the initial gamepad list */ 2634 SDL_AppIterate(NULL); 2635 2636 if (gamepad_index < num_controllers) { 2637 SetController(controllers[gamepad_index].id); 2638 } else if (num_controllers > 0) { 2639 SetController(controllers[0].id); 2640 } 2641 2642 return SDL_APP_CONTINUE; 2643} 2644 2645void SDLCALL SDL_AppQuit(void *appstate, SDL_AppResult result) 2646{ 2647 CloseVirtualGamepad(); 2648 while (num_controllers > 0) { 2649 HandleGamepadRemoved(controllers[0].id); 2650 DelController(controllers[0].id); 2651 } 2652 SDL_free(controllers); 2653 SDL_free(controller_name); 2654 DestroyGamepadImage(image); 2655 DestroyGamepadDisplay(gamepad_elements); 2656 DestroyGyroDisplay(gyro_elements); 2657 DestroyGamepadTypeDisplay(gamepad_type); 2658 DestroyJoystickDisplay(joystick_elements); 2659 DestroyGamepadButton(setup_mapping_button); 2660 DestroyGamepadButton(done_mapping_button); 2661 DestroyGamepadButton(cancel_button); 2662 DestroyGamepadButton(clear_button); 2663 DestroyGamepadButton(copy_button); 2664 DestroyGamepadButton(paste_button); 2665 SDLTest_CleanupTextDrawing(); 2666 SDL_DestroyRenderer(screen); 2667 SDL_DestroyWindow(window); 2668 SDL_Quit(); 2669 SDLTest_CommonDestroyState(state); 2670} 2671[FILE END](C) 2025 0x4248 (C) 2025 4248 Media and 4248 Systems, All part of 0x4248 See LICENCE files for more information. Not all files are by 0x4248 always check Licencing.