Atlas - testcontroller.c

Home / ext / SDL / test Lines: 1 | Size: 101238 bytes [Download] [Show on GitHub] [Search similar files] [Raw] [Raw (proxy)]
[FILE BEGIN]
1/* 2 Copyright (C) 1997-2025 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, &current_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 break; 1073 case SDL_GAMEPAD_BUTTON_BACK: 1074 return "Press the left center button (Back/View/Share)"; 1075 case SDL_GAMEPAD_BUTTON_GUIDE: 1076 return "Press the center button (Home/Guide)"; 1077 case SDL_GAMEPAD_BUTTON_START: 1078 return "Press the right center button (Start/Menu/Options)"; 1079 case SDL_GAMEPAD_BUTTON_LEFT_STICK: 1080 return "Press the left thumbstick button (LSB/L3)"; 1081 case SDL_GAMEPAD_BUTTON_RIGHT_STICK: 1082 return "Press the right thumbstick button (RSB/R3)"; 1083 case SDL_GAMEPAD_BUTTON_LEFT_SHOULDER: 1084 return "Press the left shoulder button (LB/L1)"; 1085 case SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER: 1086 return "Press the right shoulder button (RB/R1)"; 1087 case SDL_GAMEPAD_BUTTON_DPAD_UP: 1088 return "Press the D-Pad up"; 1089 case SDL_GAMEPAD_BUTTON_DPAD_DOWN: 1090 return "Press the D-Pad down"; 1091 case SDL_GAMEPAD_BUTTON_DPAD_LEFT: 1092 return "Press the D-Pad left"; 1093 case SDL_GAMEPAD_BUTTON_DPAD_RIGHT: 1094 return "Press the D-Pad right"; 1095 case SDL_GAMEPAD_BUTTON_MISC1: 1096 return "Press the bottom center button (Share/Capture)"; 1097 case SDL_GAMEPAD_BUTTON_RIGHT_PADDLE1: 1098 return "Press the upper paddle under your right hand"; 1099 case SDL_GAMEPAD_BUTTON_LEFT_PADDLE1: 1100 return "Press the upper paddle under your left hand"; 1101 case SDL_GAMEPAD_BUTTON_RIGHT_PADDLE2: 1102 return "Press the lower paddle under your right hand"; 1103 case SDL_GAMEPAD_BUTTON_LEFT_PADDLE2: 1104 return "Press the lower paddle under your left hand"; 1105 case SDL_GAMEPAD_BUTTON_TOUCHPAD: 1106 return "Press down on the touchpad"; 1107 case SDL_GAMEPAD_BUTTON_MISC2: 1108 case SDL_GAMEPAD_BUTTON_MISC3: 1109 case SDL_GAMEPAD_BUTTON_MISC4: 1110 case SDL_GAMEPAD_BUTTON_MISC5: 1111 case SDL_GAMEPAD_BUTTON_MISC6: 1112 return "Press any additional button not already bound"; 1113 case SDL_GAMEPAD_ELEMENT_AXIS_LEFTX_NEGATIVE: 1114 return "Move the left thumbstick to the left"; 1115 case SDL_GAMEPAD_ELEMENT_AXIS_LEFTX_POSITIVE: 1116 return "Move the left thumbstick to the right"; 1117 case SDL_GAMEPAD_ELEMENT_AXIS_LEFTY_NEGATIVE: 1118 return "Move the left thumbstick up"; 1119 case SDL_GAMEPAD_ELEMENT_AXIS_LEFTY_POSITIVE: 1120 return "Move the left thumbstick down"; 1121 case SDL_GAMEPAD_ELEMENT_AXIS_RIGHTX_NEGATIVE: 1122 return "Move the right thumbstick to the left"; 1123 case SDL_GAMEPAD_ELEMENT_AXIS_RIGHTX_POSITIVE: 1124 return "Move the right thumbstick to the right"; 1125 case SDL_GAMEPAD_ELEMENT_AXIS_RIGHTY_NEGATIVE: 1126 return "Move the right thumbstick up"; 1127 case SDL_GAMEPAD_ELEMENT_AXIS_RIGHTY_POSITIVE: 1128 return "Move the right thumbstick down"; 1129 case SDL_GAMEPAD_ELEMENT_AXIS_LEFT_TRIGGER: 1130 return "Pull the left trigger (LT/L2)"; 1131 case SDL_GAMEPAD_ELEMENT_AXIS_RIGHT_TRIGGER: 1132 return "Pull the right trigger (RT/R2)"; 1133 case SDL_GAMEPAD_ELEMENT_NAME: 1134 return "Type the name of your controller"; 1135 case SDL_GAMEPAD_ELEMENT_TYPE: 1136 return "Select the type of your controller"; 1137 default: 1138 return ""; 1139 } 1140} 1141 1142static int FindController(SDL_JoystickID id) 1143{ 1144 int i; 1145 1146 for (i = 0; i < num_controllers; ++i) { 1147 if (id == controllers[i].id) { 1148 return i; 1149 } 1150 } 1151 return -1; 1152} 1153 1154static void SetController(SDL_JoystickID id) 1155{ 1156 int i = FindController(id); 1157 1158 if (i < 0 && num_controllers > 0) { 1159 i = 0; 1160 } 1161 1162 if (i >= 0) { 1163 controller = &controllers[i]; 1164 } else { 1165 controller = NULL; 1166 } 1167 1168 RefreshControllerName(); 1169} 1170 1171static void AddController(SDL_JoystickID id, bool verbose) 1172{ 1173 Controller *new_controllers; 1174 Controller *new_controller; 1175 SDL_Joystick *joystick; 1176 1177 if (FindController(id) >= 0) { 1178 /* We already have this controller */ 1179 return; 1180 } 1181 1182 new_controllers = (Controller *)SDL_realloc(controllers, (num_controllers + 1) * sizeof(*controllers)); 1183 if (!new_controllers) { 1184 return; 1185 } 1186 1187 controller = NULL; 1188 controllers = new_controllers; 1189 new_controller = &new_controllers[num_controllers++]; 1190 SDL_zerop(new_controller); 1191 new_controller->id = id; 1192 1193 new_controller->joystick = SDL_OpenJoystick(id); 1194 if (new_controller->joystick) { 1195 new_controller->num_axes = SDL_GetNumJoystickAxes(new_controller->joystick); 1196 new_controller->axis_state = (AxisState *)SDL_calloc(new_controller->num_axes, sizeof(*new_controller->axis_state)); 1197 new_controller->imu_state = (IMUState *)SDL_calloc(1, sizeof(*new_controller->imu_state)); 1198 ResetIMUState(new_controller->imu_state); 1199 } 1200 1201 joystick = new_controller->joystick; 1202 if (joystick) { 1203 if (verbose && !SDL_IsGamepad(id)) { 1204 const char *name = SDL_GetJoystickName(joystick); 1205 const char *path = SDL_GetJoystickPath(joystick); 1206 char guid[33]; 1207 SDL_Log("Opened joystick %s%s%s", name, path ? ", " : "", path ? path : ""); 1208 SDL_GUIDToString(SDL_GetJoystickGUID(joystick), guid, sizeof(guid)); 1209 SDL_Log("No gamepad mapping for %s", guid); 1210 } 1211 } else { 1212 SDL_Log("Couldn't open joystick: %s", SDL_GetError()); 1213 } 1214 1215 if (mapping_controller) { 1216 SetController(mapping_controller); 1217 } else { 1218 SetController(id); 1219 } 1220} 1221 1222static void DelController(SDL_JoystickID id) 1223{ 1224 int i = FindController(id); 1225 1226 if (i < 0) { 1227 return; 1228 } 1229 1230 if (display_mode == CONTROLLER_MODE_BINDING && id == controller->id) { 1231 SetDisplayMode(CONTROLLER_MODE_TESTING); 1232 } 1233 1234 /* Reset trigger state */ 1235 if (controllers[i].trigger_effect != 0) { 1236 controllers[i].trigger_effect = -1; 1237 CyclePS5TriggerEffect(&controllers[i]); 1238 } 1239 SDL_assert(controllers[i].gamepad == NULL); 1240 SDL_free(controllers[i].axis_state); 1241 SDL_free(controllers[i].imu_state); 1242 if (controllers[i].joystick) { 1243 SDL_CloseJoystick(controllers[i].joystick); 1244 } 1245 1246 --num_controllers; 1247 if (i < num_controllers) { 1248 SDL_memcpy(&controllers[i], &controllers[i + 1], (num_controllers - i) * sizeof(*controllers)); 1249 } 1250 1251 if (mapping_controller) { 1252 SetController(mapping_controller); 1253 } else { 1254 SetController(id); 1255 } 1256} 1257 1258static void HandleGamepadRemapped(SDL_JoystickID id) 1259{ 1260 char *mapping; 1261 int i = FindController(id); 1262 1263 SDL_assert(i >= 0); 1264 if (i < 0) { 1265 return; 1266 } 1267 1268 if (!controllers[i].gamepad) { 1269 /* Failed to open this controller */ 1270 return; 1271 } 1272 1273 /* Get the current mapping */ 1274 mapping = SDL_GetGamepadMapping(controllers[i].gamepad); 1275 1276 /* Make sure the mapping has a valid name */ 1277 if (mapping && !MappingHasName(mapping)) { 1278 mapping = SetMappingName(mapping, SDL_GetJoystickName(controllers[i].joystick)); 1279 } 1280 1281 SDL_free(controllers[i].mapping); 1282 controllers[i].mapping = mapping; 1283 controllers[i].has_bindings = MappingHasBindings(mapping); 1284} 1285 1286static void HandleGamepadAdded(SDL_JoystickID id, bool verbose) 1287{ 1288 SDL_Gamepad *gamepad; 1289 Uint16 firmware_version; 1290 SDL_SensorType sensors[] = { 1291 SDL_SENSOR_ACCEL, 1292 SDL_SENSOR_GYRO, 1293 SDL_SENSOR_ACCEL_L, 1294 SDL_SENSOR_GYRO_L, 1295 SDL_SENSOR_ACCEL_R, 1296 SDL_SENSOR_GYRO_R 1297 }; 1298 int i; 1299 1300 i = FindController(id); 1301 if (i < 0) { 1302 return; 1303 } 1304 SDL_Log("Gamepad %" SDL_PRIu32 " added", id); 1305 1306 SDL_assert(!controllers[i].gamepad); 1307 controllers[i].gamepad = SDL_OpenGamepad(id); 1308 1309 gamepad = controllers[i].gamepad; 1310 if (gamepad) { 1311 if (verbose) { 1312 SDL_PropertiesID props = SDL_GetGamepadProperties(gamepad); 1313 const char *name = SDL_GetGamepadName(gamepad); 1314 const char *path = SDL_GetGamepadPath(gamepad); 1315 SDL_GUID guid = SDL_GetGamepadGUIDForID(id); 1316 char guid_string[33]; 1317 SDL_GUIDToString(guid, guid_string, sizeof(guid_string)); 1318 SDL_Log("Opened gamepad %s, guid %s%s%s", name, guid_string, path ? ", " : "", path ? path : ""); 1319 1320 firmware_version = SDL_GetGamepadFirmwareVersion(gamepad); 1321 if (firmware_version) { 1322 SDL_Log("Firmware version: 0x%x (%d)", firmware_version, firmware_version); 1323 } 1324 1325 if (SDL_GetBooleanProperty(props, SDL_PROP_GAMEPAD_CAP_PLAYER_LED_BOOLEAN, false)) { 1326 SDL_Log("Has player LED"); 1327 } 1328 1329 if (SDL_GetBooleanProperty(props, SDL_PROP_GAMEPAD_CAP_RUMBLE_BOOLEAN, false)) { 1330 SDL_Log("Rumble supported"); 1331 } 1332 1333 if (SDL_GetBooleanProperty(props, SDL_PROP_GAMEPAD_CAP_TRIGGER_RUMBLE_BOOLEAN, false)) { 1334 SDL_Log("Trigger rumble supported"); 1335 } 1336 1337 if (SDL_GetGamepadPlayerIndex(gamepad) >= 0) { 1338 SDL_Log("Player index: %d", SDL_GetGamepadPlayerIndex(gamepad)); 1339 } 1340 1341 switch (SDL_GetJoystickTypeForID(id)) { 1342 case SDL_JOYSTICK_TYPE_WHEEL: 1343 SDL_Log("Controller is a wheel"); 1344 break; 1345 case SDL_JOYSTICK_TYPE_ARCADE_STICK: 1346 SDL_Log("Controller is an arcade stick"); 1347 break; 1348 case SDL_JOYSTICK_TYPE_FLIGHT_STICK: 1349 SDL_Log("Controller is a flight stick"); 1350 break; 1351 case SDL_JOYSTICK_TYPE_DANCE_PAD: 1352 SDL_Log("Controller is a dance pad"); 1353 break; 1354 case SDL_JOYSTICK_TYPE_GUITAR: 1355 SDL_Log("Controller is a guitar"); 1356 break; 1357 case SDL_JOYSTICK_TYPE_DRUM_KIT: 1358 SDL_Log("Controller is a drum kit"); 1359 break; 1360 case SDL_JOYSTICK_TYPE_ARCADE_PAD: 1361 SDL_Log("Controller is an arcade pad"); 1362 break; 1363 case SDL_JOYSTICK_TYPE_THROTTLE: 1364 SDL_Log("Controller is a throttle"); 1365 break; 1366 default: 1367 break; 1368 } 1369 } 1370 1371 for (i = 0; i < SDL_arraysize(sensors); ++i) { 1372 SDL_SensorType sensor = sensors[i]; 1373 1374 if (SDL_GamepadHasSensor(gamepad, sensor)) { 1375 if (verbose) { 1376 SDL_Log("Enabling %s at %.2f Hz", GetSensorName(sensor), SDL_GetGamepadSensorDataRate(gamepad, sensor)); 1377 } 1378 SDL_SetGamepadSensorEnabled(gamepad, sensor, true); 1379 } 1380 } 1381 1382 if (verbose) { 1383 char *mapping = SDL_GetGamepadMapping(gamepad); 1384 if (mapping) { 1385 SDL_Log("Mapping: %s", mapping); 1386 SDL_free(mapping); 1387 } 1388 } 1389 } else { 1390 SDL_Log("Couldn't open gamepad: %s", SDL_GetError()); 1391 } 1392 1393 HandleGamepadRemapped(id); 1394 SetController(id); 1395} 1396 1397static void HandleGamepadRemoved(SDL_JoystickID id) 1398{ 1399 int i = FindController(id); 1400 1401 SDL_assert(i >= 0); 1402 if (i < 0) { 1403 return; 1404 } 1405 SDL_Log("Gamepad %" SDL_PRIu32 " removed", id); 1406 1407 if (controllers[i].mapping) { 1408 SDL_free(controllers[i].mapping); 1409 controllers[i].mapping = NULL; 1410 } 1411 if (controllers[i].gamepad) { 1412 SDL_CloseGamepad(controllers[i].gamepad); 1413 controllers[i].gamepad = NULL; 1414 } 1415} 1416static void HandleGamepadAccelerometerEvent(SDL_Event *event) 1417{ 1418 controller->imu_state->accelerometer_packet_number++; 1419 SDL_memcpy(controller->imu_state->accel_data, event->gsensor.data, sizeof(controller->imu_state->accel_data)); 1420} 1421 1422static void HandleGamepadGyroEvent(SDL_Event *event) 1423{ 1424 controller->imu_state->gyro_packet_number++; 1425 SDL_memcpy(controller->imu_state->gyro_data, event->gsensor.data, sizeof(controller->imu_state->gyro_data)); 1426} 1427 1428/* Two strategies for evaluating polling rate - one based on a fixed packet count, and one using a fixed time window. 1429 * Smaller values in either will give you a more responsive polling rate estimate, but this may fluctuate more. 1430 * Larger values in either will give you a more stable average but they will require more time to evaluate. 1431 * Generally, wired connections tend to give much more stable 1432 */ 1433/* #define SDL_USE_FIXED_PACKET_COUNT_FOR_ESTIMATION */ 1434#define SDL_GAMEPAD_IMU_MIN_POLLING_RATE_ESTIMATION_COUNT 2048 1435#define SDL_GAMEPAD_IMU_MIN_POLLING_RATE_ESTIMATION_TIME_NS (SDL_NS_PER_SECOND * 2) 1436 1437 1438static void EstimatePacketRate(void) 1439{ 1440 Uint64 now_ns = SDL_GetTicksNS(); 1441 if (controller->imu_state->imu_packet_counter == 0) { 1442 controller->imu_state->starting_time_stamp_ns = now_ns; 1443 } 1444 1445 /* Require a significant sample size before averaging rate. */ 1446#ifdef SDL_USE_FIXED_PACKET_COUNT_FOR_ESTIMATION 1447 if (controller->imu_state->imu_packet_counter >= SDL_GAMEPAD_IMU_MIN_POLLING_RATE_ESTIMATION_COUNT) { 1448 Uint64 deltatime_ns = now_ns - controller->imu_state->starting_time_stamp_ns; 1449 controller->imu_state->imu_estimated_sensor_rate = (Uint16)((controller->imu_state->imu_packet_counter * SDL_NS_PER_SECOND) / deltatime_ns); 1450 controller->imu_state->imu_packet_counter = 0; 1451 } 1452#else 1453 Uint64 deltatime_ns = now_ns - controller->imu_state->starting_time_stamp_ns; 1454 if (deltatime_ns >= SDL_GAMEPAD_IMU_MIN_POLLING_RATE_ESTIMATION_TIME_NS) { 1455 controller->imu_state->imu_estimated_sensor_rate = (Uint16)((controller->imu_state->imu_packet_counter * SDL_NS_PER_SECOND) / deltatime_ns); 1456 controller->imu_state->imu_packet_counter = 0; 1457 } 1458#endif 1459 else { 1460 ++controller->imu_state->imu_packet_counter; 1461 } 1462} 1463 1464static void UpdateGamepadOrientation( Uint64 delta_time_ns ) 1465{ 1466 if (!controller || !controller->imu_state) 1467 return; 1468 1469 SampleGyroPacketForDrift(controller->imu_state); 1470 ApplyDriftSolution(controller->imu_state->gyro_data, controller->imu_state->gyro_drift_solution); 1471 UpdateGyroRotation(controller->imu_state, delta_time_ns); 1472} 1473 1474static void HandleGamepadSensorEvent( SDL_Event* event ) 1475{ 1476 if (!controller) 1477 return; 1478 1479 if (controller->id != event->gsensor.which) 1480 return; 1481 1482 if (event->gsensor.sensor == SDL_SENSOR_GYRO) { 1483 HandleGamepadGyroEvent(event); 1484 } else if (event->gsensor.sensor == SDL_SENSOR_ACCEL) { 1485 HandleGamepadAccelerometerEvent(event); 1486 } 1487 1488 /* 1489 This is where we can update the quaternion because we need to have a drift solution, which requires both 1490 accelerometer and gyro events are received before progressing. 1491 */ 1492 if ( controller->imu_state->accelerometer_packet_number == controller->imu_state->gyro_packet_number ) { 1493 EstimatePacketRate(); 1494 Uint64 sensorTimeStampDelta_ns = event->gsensor.sensor_timestamp - controller->imu_state->last_sensor_time_stamp_ns ; 1495 UpdateGamepadOrientation(sensorTimeStampDelta_ns); 1496 1497 float display_euler_angles[3]; 1498 QuaternionToYXZ(controller->imu_state->integrated_rotation, &display_euler_angles[0], &display_euler_angles[1], &display_euler_angles[2]); 1499 1500 /* Show how far we are through the current phase. When off, just default to zero progress */ 1501 Uint64 now = SDL_GetTicksNS(); 1502 Uint64 duration = 0; 1503 if (controller->imu_state->calibration_phase == GYRO_CALIBRATION_PHASE_NOISE_PROFILING) { 1504 duration = SDL_GAMEPAD_IMU_NOISE_PROFILING_PHASE_DURATION_NS; 1505 } else if (controller->imu_state->calibration_phase == GYRO_CALIBRATION_PHASE_DRIFT_PROFILING) { 1506 duration = SDL_GAMEPAD_IMU_CALIBRATION_PHASE_DURATION_NS; 1507 } 1508 1509 Uint64 delta_ns = now - controller->imu_state->calibration_phase_start_time_ticks_ns; 1510 float drift_calibration_progress_fraction = duration > 0.0f ? ((float)delta_ns / (float)duration) : 0.0f; 1511 1512 int reported_polling_rate_hz = sensorTimeStampDelta_ns > 0 ? (int)(SDL_NS_PER_SECOND / sensorTimeStampDelta_ns) : 0; 1513 1514 /* Send the results to the frontend */ 1515 SetGamepadDisplayIMUValues(gyro_elements, 1516 controller->imu_state->gyro_drift_solution, 1517 display_euler_angles, 1518 &controller->imu_state->integrated_rotation, 1519 reported_polling_rate_hz, 1520 controller->imu_state->imu_estimated_sensor_rate, 1521 controller->imu_state->calibration_phase, 1522 drift_calibration_progress_fraction, 1523 controller->imu_state->accelerometer_length_squared, 1524 controller->imu_state->accelerometer_tolerance_squared 1525 ); 1526 1527 /* 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) */ 1528 SetGamepadDisplayGyroDriftCorrection(gamepad_elements, controller->imu_state->gyro_drift_solution); 1529 1530 controller->imu_state->last_sensor_time_stamp_ns = event->gsensor.sensor_timestamp; 1531 } 1532} 1533 1534static Uint16 ConvertAxisToRumble(Sint16 axisval) 1535{ 1536 /* Only start rumbling if the axis is past the halfway point */ 1537 const Sint16 half_axis = (Sint16)SDL_ceil(SDL_JOYSTICK_AXIS_MAX / 2.0f); 1538 if (axisval > half_axis) { 1539 return (Uint16)(axisval - half_axis) * 4; 1540 } else { 1541 return 0; 1542 } 1543} 1544 1545static bool ShowingFront(void) 1546{ 1547 bool showing_front = true; 1548 int i; 1549 1550 /* Show the back of the gamepad if the paddles are being held or bound */ 1551 for (i = SDL_GAMEPAD_BUTTON_RIGHT_PADDLE1; i <= SDL_GAMEPAD_BUTTON_LEFT_PADDLE2; ++i) { 1552 if (SDL_GetGamepadButton(controller->gamepad, (SDL_GamepadButton)i) || 1553 binding_element == i) { 1554 showing_front = false; 1555 break; 1556 } 1557 } 1558 if ((SDL_GetModState() & SDL_KMOD_SHIFT) && binding_element != SDL_GAMEPAD_ELEMENT_NAME) { 1559 showing_front = false; 1560 } 1561 return showing_front; 1562} 1563 1564static void SDLCALL VirtualGamepadSetPlayerIndex(void *userdata, int player_index) 1565{ 1566 SDL_Log("Virtual Gamepad: player index set to %d", player_index); 1567} 1568 1569static bool SDLCALL VirtualGamepadRumble(void *userdata, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble) 1570{ 1571 SDL_Log("Virtual Gamepad: rumble set to %d/%d", low_frequency_rumble, high_frequency_rumble); 1572 return true; 1573} 1574 1575static bool SDLCALL VirtualGamepadRumbleTriggers(void *userdata, Uint16 left_rumble, Uint16 right_rumble) 1576{ 1577 SDL_Log("Virtual Gamepad: trigger rumble set to %d/%d", left_rumble, right_rumble); 1578 return true; 1579} 1580 1581static bool SDLCALL VirtualGamepadSetLED(void *userdata, Uint8 red, Uint8 green, Uint8 blue) 1582{ 1583 SDL_Log("Virtual Gamepad: LED set to RGB %d,%d,%d", red, green, blue); 1584 return true; 1585} 1586 1587static void OpenVirtualGamepad(void) 1588{ 1589 SDL_VirtualJoystickTouchpadDesc virtual_touchpad = { 1, { 0, 0, 0 } }; 1590 SDL_VirtualJoystickSensorDesc virtual_sensors[] = { 1591 { SDL_SENSOR_ACCEL, 0.0f }, 1592 { SDL_SENSOR_GYRO, 0.0f } 1593 }; 1594 SDL_VirtualJoystickDesc desc; 1595 SDL_JoystickID virtual_id; 1596 1597 if (virtual_joystick) { 1598 return; 1599 } 1600 1601 SDL_INIT_INTERFACE(&desc); 1602 desc.type = SDL_JOYSTICK_TYPE_GAMEPAD; 1603 desc.naxes = SDL_GAMEPAD_AXIS_COUNT; 1604 desc.nbuttons = SDL_GAMEPAD_BUTTON_COUNT; 1605 desc.ntouchpads = 1; 1606 desc.touchpads = &virtual_touchpad; 1607 desc.nsensors = SDL_arraysize(virtual_sensors); 1608 desc.sensors = virtual_sensors; 1609 desc.SetPlayerIndex = VirtualGamepadSetPlayerIndex; 1610 desc.Rumble = VirtualGamepadRumble; 1611 desc.RumbleTriggers = VirtualGamepadRumbleTriggers; 1612 desc.SetLED = VirtualGamepadSetLED; 1613 1614 virtual_id = SDL_AttachVirtualJoystick(&desc); 1615 if (virtual_id == 0) { 1616 SDL_Log("Couldn't attach virtual device: %s", SDL_GetError()); 1617 } else { 1618 virtual_joystick = SDL_OpenJoystick(virtual_id); 1619 if (!virtual_joystick) { 1620 SDL_Log("Couldn't open virtual device: %s", SDL_GetError()); 1621 } 1622 } 1623} 1624 1625static void CloseVirtualGamepad(void) 1626{ 1627 int i; 1628 SDL_JoystickID *joysticks = SDL_GetJoysticks(NULL); 1629 if (joysticks) { 1630 for (i = 0; joysticks[i]; ++i) { 1631 SDL_JoystickID instance_id = joysticks[i]; 1632 if (SDL_IsJoystickVirtual(instance_id)) { 1633 SDL_DetachVirtualJoystick(instance_id); 1634 } 1635 } 1636 SDL_free(joysticks); 1637 } 1638 1639 if (virtual_joystick) { 1640 SDL_CloseJoystick(virtual_joystick); 1641 virtual_joystick = NULL; 1642 } 1643} 1644 1645static void VirtualGamepadMouseMotion(float x, float y) 1646{ 1647 if (virtual_button_active != SDL_GAMEPAD_BUTTON_INVALID) { 1648 if (virtual_axis_active != SDL_GAMEPAD_AXIS_INVALID) { 1649 const float MOVING_DISTANCE = 2.0f; 1650 if (SDL_fabs(x - virtual_axis_start_x) >= MOVING_DISTANCE || 1651 SDL_fabs(y - virtual_axis_start_y) >= MOVING_DISTANCE) { 1652 SDL_SetJoystickVirtualButton(virtual_joystick, virtual_button_active, false); 1653 virtual_button_active = SDL_GAMEPAD_BUTTON_INVALID; 1654 } 1655 } 1656 } 1657 1658 if (virtual_axis_active != SDL_GAMEPAD_AXIS_INVALID) { 1659 if (virtual_axis_active == SDL_GAMEPAD_AXIS_LEFT_TRIGGER || 1660 virtual_axis_active == SDL_GAMEPAD_AXIS_RIGHT_TRIGGER) { 1661 int range = (SDL_JOYSTICK_AXIS_MAX - SDL_JOYSTICK_AXIS_MIN); 1662 float distance = SDL_clamp((y - virtual_axis_start_y) / GetGamepadImageAxisHeight(image), 0.0f, 1.0f); 1663 Sint16 value = (Sint16)(SDL_JOYSTICK_AXIS_MIN + (distance * range)); 1664 SDL_SetJoystickVirtualAxis(virtual_joystick, virtual_axis_active, value); 1665 } else { 1666 float distanceX = SDL_clamp((x - virtual_axis_start_x) / GetGamepadImageAxisWidth(image), -1.0f, 1.0f); 1667 float distanceY = SDL_clamp((y - virtual_axis_start_y) / GetGamepadImageAxisHeight(image), -1.0f, 1.0f); 1668 Sint16 valueX, valueY; 1669 1670 if (distanceX >= 0) { 1671 valueX = (Sint16)(distanceX * SDL_JOYSTICK_AXIS_MAX); 1672 } else { 1673 valueX = (Sint16)(distanceX * -SDL_JOYSTICK_AXIS_MIN); 1674 } 1675 if (distanceY >= 0) { 1676 valueY = (Sint16)(distanceY * SDL_JOYSTICK_AXIS_MAX); 1677 } else { 1678 valueY = (Sint16)(distanceY * -SDL_JOYSTICK_AXIS_MIN); 1679 } 1680 SDL_SetJoystickVirtualAxis(virtual_joystick, virtual_axis_active, valueX); 1681 SDL_SetJoystickVirtualAxis(virtual_joystick, virtual_axis_active + 1, valueY); 1682 } 1683 } 1684 1685 if (virtual_touchpad_active) { 1686 SDL_FRect touchpad; 1687 GetGamepadTouchpadArea(image, &touchpad); 1688 virtual_touchpad_x = (x - touchpad.x) / touchpad.w; 1689 virtual_touchpad_y = (y - touchpad.y) / touchpad.h; 1690 SDL_SetJoystickVirtualTouchpad(virtual_joystick, 0, 0, true, virtual_touchpad_x, virtual_touchpad_y, 1.0f); 1691 } 1692} 1693 1694static void VirtualGamepadMouseDown(float x, float y) 1695{ 1696 int element = GetGamepadImageElementAt(image, x, y); 1697 1698 if (element == SDL_GAMEPAD_ELEMENT_INVALID) { 1699 SDL_FPoint point; 1700 point.x = x; 1701 point.y = y; 1702 SDL_FRect touchpad; 1703 GetGamepadTouchpadArea(image, &touchpad); 1704 if (SDL_PointInRectFloat(&point, &touchpad)) { 1705 virtual_touchpad_active = true; 1706 virtual_touchpad_x = (x - touchpad.x) / touchpad.w; 1707 virtual_touchpad_y = (y - touchpad.y) / touchpad.h; 1708 SDL_SetJoystickVirtualTouchpad(virtual_joystick, 0, 0, true, virtual_touchpad_x, virtual_touchpad_y, 1.0f); 1709 } 1710 return; 1711 } 1712 1713 if (element < SDL_GAMEPAD_BUTTON_COUNT) { 1714 virtual_button_active = (SDL_GamepadButton)element; 1715 SDL_SetJoystickVirtualButton(virtual_joystick, virtual_button_active, true); 1716 } else { 1717 switch (element) { 1718 case SDL_GAMEPAD_ELEMENT_AXIS_LEFTX_NEGATIVE: 1719 case SDL_GAMEPAD_ELEMENT_AXIS_LEFTX_POSITIVE: 1720 case SDL_GAMEPAD_ELEMENT_AXIS_LEFTY_NEGATIVE: 1721 case SDL_GAMEPAD_ELEMENT_AXIS_LEFTY_POSITIVE: 1722 virtual_axis_active = SDL_GAMEPAD_AXIS_LEFTX; 1723 break; 1724 case SDL_GAMEPAD_ELEMENT_AXIS_RIGHTX_NEGATIVE: 1725 case SDL_GAMEPAD_ELEMENT_AXIS_RIGHTX_POSITIVE: 1726 case SDL_GAMEPAD_ELEMENT_AXIS_RIGHTY_NEGATIVE: 1727 case SDL_GAMEPAD_ELEMENT_AXIS_RIGHTY_POSITIVE: 1728 virtual_axis_active = SDL_GAMEPAD_AXIS_RIGHTX; 1729 break; 1730 case SDL_GAMEPAD_ELEMENT_AXIS_LEFT_TRIGGER: 1731 virtual_axis_active = SDL_GAMEPAD_AXIS_LEFT_TRIGGER; 1732 break; 1733 case SDL_GAMEPAD_ELEMENT_AXIS_RIGHT_TRIGGER: 1734 virtual_axis_active = SDL_GAMEPAD_AXIS_RIGHT_TRIGGER; 1735 break; 1736 } 1737 virtual_axis_start_x = x; 1738 virtual_axis_start_y = y; 1739 } 1740} 1741 1742static void VirtualGamepadMouseUp(float x, float y) 1743{ 1744 if (virtual_button_active != SDL_GAMEPAD_BUTTON_INVALID) { 1745 SDL_SetJoystickVirtualButton(virtual_joystick, virtual_button_active, false); 1746 virtual_button_active = SDL_GAMEPAD_BUTTON_INVALID; 1747 } 1748 1749 if (virtual_axis_active != SDL_GAMEPAD_AXIS_INVALID) { 1750 if (virtual_axis_active == SDL_GAMEPAD_AXIS_LEFT_TRIGGER || 1751 virtual_axis_active == SDL_GAMEPAD_AXIS_RIGHT_TRIGGER) { 1752 SDL_SetJoystickVirtualAxis(virtual_joystick, virtual_axis_active, SDL_JOYSTICK_AXIS_MIN); 1753 } else { 1754 SDL_SetJoystickVirtualAxis(virtual_joystick, virtual_axis_active, 0); 1755 SDL_SetJoystickVirtualAxis(virtual_joystick, virtual_axis_active + 1, 0); 1756 } 1757 virtual_axis_active = SDL_GAMEPAD_AXIS_INVALID; 1758 } 1759 1760 if (virtual_touchpad_active) { 1761 SDL_SetJoystickVirtualTouchpad(virtual_joystick, 0, 0, false, virtual_touchpad_x, virtual_touchpad_y, 0.0f); 1762 virtual_touchpad_active = false; 1763 } 1764} 1765 1766static void DrawGamepadWaiting(SDL_Renderer *renderer) 1767{ 1768 const char *text = "Waiting for gamepad, press A to add a virtual controller"; 1769 float x, y; 1770 1771 x = SCREEN_WIDTH / 2 - (FONT_CHARACTER_SIZE * SDL_strlen(text)) / 2; 1772 y = TITLE_HEIGHT / 2 - FONT_CHARACTER_SIZE / 2; 1773 SDLTest_DrawString(renderer, x, y, text); 1774} 1775 1776static void DrawGamepadInfo(SDL_Renderer *renderer) 1777{ 1778 const char *type; 1779 const char *serial; 1780 char text[128]; 1781 float x, y; 1782 1783 if (title_highlighted) { 1784 Uint8 r, g, b, a; 1785 1786 SDL_GetRenderDrawColor(renderer, &r, &g, &b, &a); 1787 1788 if (title_pressed) { 1789 SDL_SetRenderDrawColor(renderer, PRESSED_COLOR); 1790 } else { 1791 SDL_SetRenderDrawColor(renderer, HIGHLIGHT_COLOR); 1792 } 1793 SDL_RenderFillRect(renderer, &title_area); 1794 1795 SDL_SetRenderDrawColor(renderer, r, g, b, a); 1796 } 1797 1798 if (type_highlighted) { 1799 Uint8 r, g, b, a; 1800 1801 SDL_GetRenderDrawColor(renderer, &r, &g, &b, &a); 1802 1803 if (type_pressed) { 1804 SDL_SetRenderDrawColor(renderer, PRESSED_COLOR); 1805 } else { 1806 SDL_SetRenderDrawColor(renderer, HIGHLIGHT_COLOR); 1807 } 1808 SDL_RenderFillRect(renderer, &type_area); 1809 1810 SDL_SetRenderDrawColor(renderer, r, g, b, a); 1811 } 1812 1813 if (controller->joystick) { 1814 SDL_snprintf(text, sizeof(text), "(%" SDL_PRIu32 ")", SDL_GetJoystickID(controller->joystick)); 1815 x = SCREEN_WIDTH - (FONT_CHARACTER_SIZE * SDL_strlen(text)) - 8.0f; 1816 y = 8.0f; 1817 SDLTest_DrawString(renderer, x, y, text); 1818 } 1819 1820 if (controller_name && *controller_name) { 1821 x = title_area.x + title_area.w / 2 - (FONT_CHARACTER_SIZE * SDL_strlen(controller_name)) / 2; 1822 y = title_area.y + title_area.h / 2 - FONT_CHARACTER_SIZE / 2; 1823 SDLTest_DrawString(renderer, x, y, controller_name); 1824 } 1825 1826 if (SDL_IsJoystickVirtual(controller->id)) { 1827 SDL_strlcpy(text, "Click on the gamepad image below to generate input", sizeof(text)); 1828 x = SCREEN_WIDTH / 2 - (FONT_CHARACTER_SIZE * SDL_strlen(text)) / 2; 1829 y = TITLE_HEIGHT / 2 - FONT_CHARACTER_SIZE / 2 + FONT_LINE_HEIGHT + 2.0f; 1830 SDLTest_DrawString(renderer, x, y, text); 1831 } 1832 1833 type = GetGamepadTypeString(SDL_GetGamepadType(controller->gamepad)); 1834 x = type_area.x + type_area.w / 2 - (FONT_CHARACTER_SIZE * SDL_strlen(type)) / 2; 1835 y = type_area.y + type_area.h / 2 - FONT_CHARACTER_SIZE / 2; 1836 SDLTest_DrawString(renderer, x, y, type); 1837 1838 if (display_mode == CONTROLLER_MODE_TESTING) { 1839 Uint64 steam_handle = SDL_GetGamepadSteamHandle(controller->gamepad); 1840 if (steam_handle) { 1841 SDL_snprintf(text, SDL_arraysize(text), "Steam: 0x%.16" SDL_PRIx64, steam_handle); 1842 y = SCREEN_HEIGHT - 2 * (8.0f + FONT_LINE_HEIGHT); 1843 x = SCREEN_WIDTH - 8.0f - (FONT_CHARACTER_SIZE * SDL_strlen(text)); 1844 SDLTest_DrawString(renderer, x, y, text); 1845 } 1846 1847 SDL_snprintf(text, SDL_arraysize(text), "VID: 0x%.4x PID: 0x%.4x", 1848 SDL_GetJoystickVendor(controller->joystick), 1849 SDL_GetJoystickProduct(controller->joystick)); 1850 y = SCREEN_HEIGHT - 8.0f - FONT_LINE_HEIGHT; 1851 x = SCREEN_WIDTH - 8.0f - (FONT_CHARACTER_SIZE * SDL_strlen(text)); 1852 SDLTest_DrawString(renderer, x, y, text); 1853 1854 serial = SDL_GetJoystickSerial(controller->joystick); 1855 if (serial && *serial) { 1856 SDL_snprintf(text, SDL_arraysize(text), "Serial: %s", serial); 1857 x = SCREEN_WIDTH / 2 - (FONT_CHARACTER_SIZE * SDL_strlen(text)) / 2; 1858 y = SCREEN_HEIGHT - 8.0f - FONT_LINE_HEIGHT; 1859 SDLTest_DrawString(renderer, x, y, text); 1860 } 1861 } 1862} 1863 1864static const char *GetButtonLabel(SDL_GamepadType type, SDL_GamepadButton button) 1865{ 1866 switch (SDL_GetGamepadButtonLabelForType(type, button)) { 1867 case SDL_GAMEPAD_BUTTON_LABEL_A: 1868 return "A"; 1869 case SDL_GAMEPAD_BUTTON_LABEL_B: 1870 return "B"; 1871 case SDL_GAMEPAD_BUTTON_LABEL_X: 1872 return "X"; 1873 case SDL_GAMEPAD_BUTTON_LABEL_Y: 1874 return "Y"; 1875 case SDL_GAMEPAD_BUTTON_LABEL_CROSS: 1876 return "Cross (X)"; 1877 case SDL_GAMEPAD_BUTTON_LABEL_CIRCLE: 1878 return "Circle"; 1879 case SDL_GAMEPAD_BUTTON_LABEL_SQUARE: 1880 return "Square"; 1881 case SDL_GAMEPAD_BUTTON_LABEL_TRIANGLE: 1882 return "Triangle"; 1883 default: 1884 return "UNKNOWN"; 1885 } 1886} 1887 1888static void DrawBindingTips(SDL_Renderer *renderer) 1889{ 1890 const char *text; 1891 SDL_FRect image_area, button_area; 1892 float x, y; 1893 1894 GetGamepadImageArea(image, &image_area); 1895 GetGamepadButtonArea(done_mapping_button, &button_area); 1896 x = image_area.x + image_area.w / 2; 1897 y = image_area.y + image_area.h; 1898 y += (button_area.y - y - FONT_CHARACTER_SIZE) / 2; 1899 1900 text = GetBindingInstruction(); 1901 1902 if (binding_element == SDL_GAMEPAD_ELEMENT_INVALID) { 1903 SDLTest_DrawString(renderer, x - (FONT_CHARACTER_SIZE * SDL_strlen(text)) / 2, y, text); 1904 } else { 1905 Uint8 r, g, b, a; 1906 SDL_FRect rect; 1907 SDL_GamepadButton action_forward = SDL_GAMEPAD_BUTTON_SOUTH; 1908 bool bound_forward = MappingHasElement(controller->mapping, action_forward); 1909 SDL_GamepadButton action_backward = SDL_GAMEPAD_BUTTON_EAST; 1910 bool bound_backward = MappingHasElement(controller->mapping, action_backward); 1911 SDL_GamepadButton action_delete = SDL_GAMEPAD_BUTTON_WEST; 1912 bool bound_delete = MappingHasElement(controller->mapping, action_delete); 1913 1914 y -= (FONT_CHARACTER_SIZE + BUTTON_MARGIN) / 2; 1915 1916 rect.w = 2.0f + (FONT_CHARACTER_SIZE * SDL_strlen(text)) + 2.0f; 1917 rect.h = 2.0f + FONT_CHARACTER_SIZE + 2.0f; 1918 rect.x = x - rect.w / 2; 1919 rect.y = y - 2.0f; 1920 1921 SDL_GetRenderDrawColor(renderer, &r, &g, &b, &a); 1922 SDL_SetRenderDrawColor(renderer, SELECTED_COLOR); 1923 SDL_RenderFillRect(renderer, &rect); 1924 SDL_SetRenderDrawColor(renderer, r, g, b, a); 1925 SDLTest_DrawString(renderer, x - (FONT_CHARACTER_SIZE * SDL_strlen(text)) / 2, y, text); 1926 1927 y += (FONT_CHARACTER_SIZE + BUTTON_MARGIN); 1928 1929 if (binding_element == SDL_GAMEPAD_ELEMENT_NAME) { 1930 text = "(press RETURN to complete)"; 1931 } else if (binding_element == SDL_GAMEPAD_ELEMENT_TYPE || 1932 binding_element == action_forward || 1933 binding_element == action_backward) { 1934 text = "(press ESC to cancel)"; 1935 } else { 1936 static char dynamic_text[128]; 1937 SDL_GamepadType type = GetGamepadImageType(image); 1938 if (binding_flow && bound_forward && bound_backward) { 1939 if (binding_element != action_delete && bound_delete) { 1940 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)); 1941 } else { 1942 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)); 1943 } 1944 text = dynamic_text; 1945 } else { 1946 text = "(press SPACE to delete and ESC to cancel)"; 1947 } 1948 } 1949 SDLTest_DrawString(renderer, x - (FONT_CHARACTER_SIZE * SDL_strlen(text)) / 2, y, text); 1950 } 1951} 1952 1953static void UpdateGamepadEffects(void) 1954{ 1955 if (display_mode != CONTROLLER_MODE_TESTING || !controller->gamepad) { 1956 return; 1957 } 1958 1959 /* Update LED based on left thumbstick position */ 1960 { 1961 Sint16 x = SDL_GetGamepadAxis(controller->gamepad, SDL_GAMEPAD_AXIS_LEFTX); 1962 Sint16 y = SDL_GetGamepadAxis(controller->gamepad, SDL_GAMEPAD_AXIS_LEFTY); 1963 1964 if (!set_LED) { 1965 set_LED = (x < -8000 || x > 8000 || y > 8000); 1966 } 1967 if (set_LED) { 1968 Uint8 r, g, b; 1969 1970 if (x < 0) { 1971 r = (Uint8)(((~x) * 255) / 32767); 1972 b = 0; 1973 } else { 1974 r = 0; 1975 b = (Uint8)(((int)(x)*255) / 32767); 1976 } 1977 if (y > 0) { 1978 g = (Uint8)(((int)(y)*255) / 32767); 1979 } else { 1980 g = 0; 1981 } 1982 1983 SDL_SetGamepadLED(controller->gamepad, r, g, b); 1984 } 1985 } 1986 1987 if (controller->trigger_effect == 0) { 1988 /* Update rumble based on trigger state */ 1989 { 1990 Sint16 left = SDL_GetGamepadAxis(controller->gamepad, SDL_GAMEPAD_AXIS_LEFT_TRIGGER); 1991 Sint16 right = SDL_GetGamepadAxis(controller->gamepad, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER); 1992 Uint16 low_frequency_rumble = ConvertAxisToRumble(left); 1993 Uint16 high_frequency_rumble = ConvertAxisToRumble(right); 1994 SDL_RumbleGamepad(controller->gamepad, low_frequency_rumble, high_frequency_rumble, 250); 1995 } 1996 1997 /* Update trigger rumble based on thumbstick state */ 1998 { 1999 Sint16 left = SDL_GetGamepadAxis(controller->gamepad, SDL_GAMEPAD_AXIS_LEFTY); 2000 Sint16 right = SDL_GetGamepadAxis(controller->gamepad, SDL_GAMEPAD_AXIS_RIGHTY); 2001 Uint16 left_rumble = ConvertAxisToRumble(~left); 2002 Uint16 right_rumble = ConvertAxisToRumble(~right); 2003 2004 SDL_RumbleGamepadTriggers(controller->gamepad, left_rumble, right_rumble, 250); 2005 } 2006 } 2007} 2008 2009SDL_AppResult SDLCALL SDL_AppEvent(void *appstate, SDL_Event *event) 2010{ 2011 SDL_ConvertEventToRenderCoordinates(screen, event); 2012 2013 switch (event->type) { 2014 case SDL_EVENT_JOYSTICK_ADDED: 2015 AddController(event->jdevice.which, true); 2016 break; 2017 2018 case SDL_EVENT_JOYSTICK_REMOVED: 2019 DelController(event->jdevice.which); 2020 break; 2021 2022 case SDL_EVENT_JOYSTICK_AXIS_MOTION: 2023 if (display_mode == CONTROLLER_MODE_TESTING) { 2024 if (event->jaxis.value <= (-SDL_JOYSTICK_AXIS_MAX / 2) || event->jaxis.value >= (SDL_JOYSTICK_AXIS_MAX / 2)) { 2025 SetController(event->jaxis.which); 2026 } 2027 } else if (display_mode == CONTROLLER_MODE_BINDING && 2028 event->jaxis.which == controller->id && 2029 event->jaxis.axis < controller->num_axes && 2030 binding_element != SDL_GAMEPAD_ELEMENT_INVALID) { 2031 const int MAX_ALLOWED_JITTER = SDL_JOYSTICK_AXIS_MAX / 80; /* ShanWan PS3 gamepad needed 96 */ 2032 AxisState *pAxisState = &controller->axis_state[event->jaxis.axis]; 2033 int nValue = event->jaxis.value; 2034 int nCurrentDistance, nFarthestDistance; 2035 if (!pAxisState->m_bMoving) { 2036 Sint16 nInitialValue; 2037 pAxisState->m_bMoving = SDL_GetJoystickAxisInitialState(controller->joystick, event->jaxis.axis, &nInitialValue); 2038 pAxisState->m_nLastValue = nValue; 2039 pAxisState->m_nStartingValue = nInitialValue; 2040 pAxisState->m_nFarthestValue = nInitialValue; 2041 } else if (SDL_abs(nValue - pAxisState->m_nLastValue) <= MAX_ALLOWED_JITTER) { 2042 break; 2043 } else { 2044 pAxisState->m_nLastValue = nValue; 2045 } 2046 nCurrentDistance = SDL_abs(nValue - pAxisState->m_nStartingValue); 2047 nFarthestDistance = SDL_abs(pAxisState->m_nFarthestValue - pAxisState->m_nStartingValue); 2048 if (nCurrentDistance > nFarthestDistance) { 2049 pAxisState->m_nFarthestValue = nValue; 2050 nFarthestDistance = SDL_abs(pAxisState->m_nFarthestValue - pAxisState->m_nStartingValue); 2051 } 2052 2053#ifdef DEBUG_AXIS_MAPPING 2054 SDL_Log("AXIS %d nValue %d nCurrentDistance %d nFarthestDistance %d", event->jaxis.axis, nValue, nCurrentDistance, nFarthestDistance); 2055#endif 2056 /* If we've gone out far enough and started to come back, let's bind this axis */ 2057 if (nFarthestDistance >= 16000 && nCurrentDistance <= 10000) { 2058 char binding[12]; 2059 int axis_min = StandardizeAxisValue(pAxisState->m_nStartingValue); 2060 int axis_max = StandardizeAxisValue(pAxisState->m_nFarthestValue); 2061 2062 if (axis_min == 0 && axis_max == SDL_JOYSTICK_AXIS_MIN) { 2063 /* The negative half axis */ 2064 (void)SDL_snprintf(binding, sizeof(binding), "-a%d", event->jaxis.axis); 2065 } else if (axis_min == 0 && axis_max == SDL_JOYSTICK_AXIS_MAX) { 2066 /* The positive half axis */ 2067 (void)SDL_snprintf(binding, sizeof(binding), "+a%d", event->jaxis.axis); 2068 } else { 2069 (void)SDL_snprintf(binding, sizeof(binding), "a%d", event->jaxis.axis); 2070 if (axis_min > axis_max) { 2071 /* Invert the axis */ 2072 SDL_strlcat(binding, "~", SDL_arraysize(binding)); 2073 } 2074 } 2075#ifdef DEBUG_AXIS_MAPPING 2076 SDL_Log("AXIS %d axis_min = %d, axis_max = %d, binding = %s", event->jaxis.axis, axis_min, axis_max, binding); 2077#endif 2078 CommitBindingElement(binding, false); 2079 } 2080 } 2081 break; 2082 2083 case SDL_EVENT_JOYSTICK_BUTTON_DOWN: 2084 if (display_mode == CONTROLLER_MODE_TESTING) { 2085 SetController(event->jbutton.which); 2086 } 2087 break; 2088 2089 case SDL_EVENT_JOYSTICK_BUTTON_UP: 2090 if (display_mode == CONTROLLER_MODE_BINDING && 2091 event->jbutton.which == controller->id && 2092 binding_element != SDL_GAMEPAD_ELEMENT_INVALID) { 2093 char binding[12]; 2094 2095 SDL_snprintf(binding, sizeof(binding), "b%d", event->jbutton.button); 2096 CommitBindingElement(binding, false); 2097 } 2098 break; 2099 2100 case SDL_EVENT_JOYSTICK_HAT_MOTION: 2101 if (display_mode == CONTROLLER_MODE_BINDING && 2102 event->jhat.which == controller->id && 2103 event->jhat.value != SDL_HAT_CENTERED && 2104 binding_element != SDL_GAMEPAD_ELEMENT_INVALID) { 2105 char binding[12]; 2106 2107 SDL_snprintf(binding, sizeof(binding), "h%d.%d", event->jhat.hat, event->jhat.value); 2108 CommitBindingElement(binding, false); 2109 } 2110 break; 2111 2112 case SDL_EVENT_GAMEPAD_ADDED: 2113 HandleGamepadAdded(event->gdevice.which, true); 2114 break; 2115 2116 case SDL_EVENT_GAMEPAD_REMOVED: 2117 HandleGamepadRemoved(event->gdevice.which); 2118 break; 2119 2120 case SDL_EVENT_GAMEPAD_REMAPPED: 2121 HandleGamepadRemapped(event->gdevice.which); 2122 break; 2123 2124 case SDL_EVENT_GAMEPAD_STEAM_HANDLE_UPDATED: 2125 RefreshControllerName(); 2126 break; 2127 2128#ifdef VERBOSE_TOUCHPAD 2129 case SDL_EVENT_GAMEPAD_TOUCHPAD_DOWN: 2130 case SDL_EVENT_GAMEPAD_TOUCHPAD_MOTION: 2131 case SDL_EVENT_GAMEPAD_TOUCHPAD_UP: 2132 SDL_Log("Gamepad %" SDL_PRIu32 " touchpad %" SDL_PRIs32 " finger %" SDL_PRIs32 " %s %.2f, %.2f, %.2f", 2133 event->gtouchpad.which, 2134 event->gtouchpad.touchpad, 2135 event->gtouchpad.finger, 2136 (event->type == SDL_EVENT_GAMEPAD_TOUCHPAD_DOWN ? "pressed at" : (event->type == SDL_EVENT_GAMEPAD_TOUCHPAD_UP ? "released at" : "moved to")), 2137 event->gtouchpad.x, 2138 event->gtouchpad.y, 2139 event->gtouchpad.pressure); 2140 break; 2141#endif /* VERBOSE_TOUCHPAD */ 2142 2143 2144 case SDL_EVENT_GAMEPAD_SENSOR_UPDATE: 2145#ifdef VERBOSE_SENSORS 2146 SDL_Log("Gamepad %" SDL_PRIu32 " sensor %s: %.2f, %.2f, %.2f (%" SDL_PRIu64 ")", 2147 event->gsensor.which, 2148 GetSensorName((SDL_SensorType) event->gsensor.sensor), 2149 event->gsensor.data[0], 2150 event->gsensor.data[1], 2151 event->gsensor.data[2], 2152 event->gsensor.sensor_timestamp); 2153#endif /* VERBOSE_SENSORS */ 2154 HandleGamepadSensorEvent(event); 2155 break; 2156 2157#ifdef VERBOSE_AXES 2158 case SDL_EVENT_GAMEPAD_AXIS_MOTION: 2159 if (display_mode == CONTROLLER_MODE_TESTING) { 2160 if (event->gaxis.value <= (-SDL_JOYSTICK_AXIS_MAX / 2) || event->gaxis.value >= (SDL_JOYSTICK_AXIS_MAX / 2)) { 2161 SetController(event->gaxis.which); 2162 } 2163 } 2164 SDL_Log("Gamepad %" SDL_PRIu32 " axis %s changed to %d", 2165 event->gaxis.which, 2166 SDL_GetGamepadStringForAxis((SDL_GamepadAxis) event->gaxis.axis), 2167 event->gaxis.value); 2168 break; 2169#endif /* VERBOSE_AXES */ 2170 2171 case SDL_EVENT_GAMEPAD_BUTTON_DOWN: 2172 case SDL_EVENT_GAMEPAD_BUTTON_UP: 2173 if (display_mode == CONTROLLER_MODE_TESTING) { 2174 if (event->type == SDL_EVENT_GAMEPAD_BUTTON_DOWN) { 2175 SetController(event->gbutton.which); 2176 } 2177 } 2178#ifdef VERBOSE_BUTTONS 2179 SDL_Log("Gamepad %" SDL_PRIu32 " button %s %s", 2180 event->gbutton.which, 2181 SDL_GetGamepadStringForButton((SDL_GamepadButton) event->gbutton.button), 2182 event->gbutton.state ? "pressed" : "released"); 2183#endif /* VERBOSE_BUTTONS */ 2184 2185 if (display_mode == CONTROLLER_MODE_TESTING) { 2186 if (event->type == SDL_EVENT_GAMEPAD_BUTTON_DOWN && 2187 controller && SDL_GetGamepadType(controller->gamepad) == SDL_GAMEPAD_TYPE_PS5) { 2188 /* Cycle PS5 audio routing when the microphone button is pressed */ 2189 if (event->gbutton.button == SDL_GAMEPAD_BUTTON_MISC1) { 2190 CyclePS5AudioRoute(controller); 2191 } 2192 2193 /* Cycle PS5 trigger effects when the triangle button is pressed */ 2194 if (event->gbutton.button == SDL_GAMEPAD_BUTTON_NORTH) { 2195 CyclePS5TriggerEffect(controller); 2196 } 2197 } 2198 } 2199 break; 2200 2201 case SDL_EVENT_MOUSE_BUTTON_DOWN: 2202 if (virtual_joystick && controller && controller->joystick == virtual_joystick) { 2203 VirtualGamepadMouseDown(event->button.x, event->button.y); 2204 } 2205 UpdateButtonHighlights(event->button.x, event->button.y, event->button.down); 2206 break; 2207 2208 case SDL_EVENT_MOUSE_BUTTON_UP: 2209 if (virtual_joystick && controller && controller->joystick == virtual_joystick) { 2210 VirtualGamepadMouseUp(event->button.x, event->button.y); 2211 } 2212 2213 if (display_mode == CONTROLLER_MODE_TESTING) { 2214 if (controller && GamepadButtonContains(GetGyroResetButton(gyro_elements), event->button.x, event->button.y)) { 2215 ResetGyroOrientation(controller->imu_state); 2216 } else if (controller && GamepadButtonContains(GetGyroCalibrateButton(gyro_elements), event->button.x, event->button.y)) { 2217 BeginNoiseCalibrationPhase(controller->imu_state); 2218 } else if (GamepadButtonContains(setup_mapping_button, event->button.x, event->button.y)) { 2219 SetDisplayMode(CONTROLLER_MODE_BINDING); 2220 } 2221 } else if (display_mode == CONTROLLER_MODE_BINDING) { 2222 if (GamepadButtonContains(done_mapping_button, event->button.x, event->button.y)) { 2223 if (controller->mapping) { 2224 SDL_Log("Mapping complete:"); 2225 SDL_Log("%s", controller->mapping); 2226 } 2227 SetDisplayMode(CONTROLLER_MODE_TESTING); 2228 } else if (GamepadButtonContains(cancel_button, event->button.x, event->button.y)) { 2229 CancelMapping(); 2230 } else if (GamepadButtonContains(clear_button, event->button.x, event->button.y)) { 2231 ClearMapping(); 2232 } else if (controller->has_bindings && 2233 GamepadButtonContains(copy_button, event->button.x, event->button.y)) { 2234 CopyMapping(); 2235 } else if (GamepadButtonContains(paste_button, event->button.x, event->button.y)) { 2236 PasteMapping(); 2237 } else if (title_pressed) { 2238 SetCurrentBindingElement(SDL_GAMEPAD_ELEMENT_NAME, false); 2239 } else if (type_pressed) { 2240 SetCurrentBindingElement(SDL_GAMEPAD_ELEMENT_TYPE, false); 2241 } else if (binding_element == SDL_GAMEPAD_ELEMENT_TYPE) { 2242 int type = GetGamepadTypeDisplayAt(gamepad_type, event->button.x, event->button.y); 2243 if (type != SDL_GAMEPAD_TYPE_UNSELECTED) { 2244 CommitGamepadType((SDL_GamepadType)type); 2245 StopBinding(); 2246 } 2247 } else { 2248 int gamepad_element = SDL_GAMEPAD_ELEMENT_INVALID; 2249 char *joystick_element; 2250 2251 if (controller->joystick != virtual_joystick) { 2252 gamepad_element = GetGamepadImageElementAt(image, event->button.x, event->button.y); 2253 } 2254 if (gamepad_element == SDL_GAMEPAD_ELEMENT_INVALID) { 2255 gamepad_element = GetGamepadDisplayElementAt(gamepad_elements, controller->gamepad, event->button.x, event->button.y); 2256 } 2257 if (gamepad_element != SDL_GAMEPAD_ELEMENT_INVALID) { 2258 /* Set this to false if you don't want to start the binding flow at this point */ 2259 const bool should_start_flow = true; 2260 SetCurrentBindingElement(gamepad_element, should_start_flow); 2261 } 2262 2263 joystick_element = GetJoystickDisplayElementAt(joystick_elements, controller->joystick, event->button.x, event->button.y); 2264 if (joystick_element) { 2265 CommitBindingElement(joystick_element, true); 2266 SDL_free(joystick_element); 2267 } 2268 } 2269 } 2270 UpdateButtonHighlights(event->button.x, event->button.y, event->button.down); 2271 break; 2272 2273 case SDL_EVENT_MOUSE_MOTION: 2274 if (virtual_joystick && controller && controller->joystick == virtual_joystick) { 2275 VirtualGamepadMouseMotion(event->motion.x, event->motion.y); 2276 } 2277 UpdateButtonHighlights(event->motion.x, event->motion.y, event->motion.state ? true : false); 2278 break; 2279 2280 case SDL_EVENT_KEY_DOWN: 2281 if (display_mode == CONTROLLER_MODE_TESTING) { 2282 if (event->key.key >= SDLK_0 && event->key.key <= SDLK_9) { 2283 if (controller && controller->gamepad) { 2284 int player_index = (event->key.key - SDLK_0); 2285 2286 SDL_SetGamepadPlayerIndex(controller->gamepad, player_index); 2287 } 2288 break; 2289 } else if (event->key.key == SDLK_A) { 2290 OpenVirtualGamepad(); 2291 } else if (event->key.key == SDLK_D) { 2292 CloseVirtualGamepad(); 2293 } else if (event->key.key == SDLK_R && (event->key.mod & SDL_KMOD_CTRL)) { 2294 SDL_ReloadGamepadMappings(); 2295 } else if (event->key.key == SDLK_ESCAPE) { 2296 done = true; 2297 } else if (event->key.key == SDLK_SPACE) { 2298 if (controller && controller->imu_state) { 2299 ResetGyroOrientation(controller->imu_state); 2300 } 2301 } 2302 } else if (display_mode == CONTROLLER_MODE_BINDING) { 2303 if (event->key.key == SDLK_C && (event->key.mod & SDL_KMOD_CTRL)) { 2304 if (binding_element == SDL_GAMEPAD_ELEMENT_NAME) { 2305 CopyControllerName(); 2306 } else { 2307 CopyMapping(); 2308 } 2309 } else if (event->key.key == SDLK_V && (event->key.mod & SDL_KMOD_CTRL)) { 2310 if (binding_element == SDL_GAMEPAD_ELEMENT_NAME) { 2311 ClearControllerName(); 2312 PasteControllerName(); 2313 } else { 2314 PasteMapping(); 2315 } 2316 } else if (event->key.key == SDLK_X && (event->key.mod & SDL_KMOD_CTRL)) { 2317 if (binding_element == SDL_GAMEPAD_ELEMENT_NAME) { 2318 CopyControllerName(); 2319 ClearControllerName(); 2320 } else { 2321 CopyMapping(); 2322 ClearMapping(); 2323 } 2324 } else if (event->key.key == SDLK_SPACE) { 2325 if (binding_element != SDL_GAMEPAD_ELEMENT_NAME) { 2326 ClearBinding(); 2327 } 2328 } else if (event->key.key == SDLK_BACKSPACE) { 2329 if (binding_element == SDL_GAMEPAD_ELEMENT_NAME) { 2330 BackspaceControllerName(); 2331 } 2332 } else if (event->key.key == SDLK_RETURN) { 2333 if (binding_element == SDL_GAMEPAD_ELEMENT_NAME) { 2334 StopBinding(); 2335 } 2336 } else if (event->key.key == SDLK_ESCAPE) { 2337 if (binding_element != SDL_GAMEPAD_ELEMENT_INVALID) { 2338 StopBinding(); 2339 } else { 2340 CancelMapping(); 2341 } 2342 } 2343 } 2344 break; 2345 case SDL_EVENT_TEXT_INPUT: 2346 if (display_mode == CONTROLLER_MODE_BINDING) { 2347 if (binding_element == SDL_GAMEPAD_ELEMENT_NAME) { 2348 AddControllerNameText(event->text.text); 2349 } 2350 } 2351 break; 2352 case SDL_EVENT_QUIT: 2353 done = true; 2354 break; 2355 default: 2356 break; 2357 } 2358 2359 if (done) { 2360 return SDL_APP_SUCCESS; 2361 } else { 2362 return SDL_APP_CONTINUE; 2363 } 2364} 2365 2366SDL_AppResult SDLCALL SDL_AppIterate(void *appstate) 2367{ 2368 /* If we have a virtual controller, send virtual sensor readings */ 2369 if (virtual_joystick) { 2370 float accel_data[3] = { 0.0f, SDL_STANDARD_GRAVITY, 0.0f }; 2371 float gyro_data[3] = { 0.01f, -0.01f, 0.0f }; 2372 Uint64 sensor_timestamp = SDL_GetTicksNS(); 2373 SDL_SendJoystickVirtualSensorData(virtual_joystick, SDL_SENSOR_ACCEL, sensor_timestamp, accel_data, SDL_arraysize(accel_data)); 2374 SDL_SendJoystickVirtualSensorData(virtual_joystick, SDL_SENSOR_GYRO, sensor_timestamp, gyro_data, SDL_arraysize(gyro_data)); 2375 } 2376 2377 /* Wait 30 ms for joystick events to stop coming in, 2378 in case a gamepad sends multiple events for a single control (e.g. axis and button for trigger) 2379 */ 2380 if (binding_advance_time && SDL_GetTicks() > (binding_advance_time + 30)) { 2381 if (binding_flow) { 2382 SetNextBindingElement(); 2383 } else { 2384 StopBinding(); 2385 } 2386 } 2387 2388 /* blank screen, set up for drawing this frame. */ 2389 SDL_SetRenderDrawColor(screen, 0xFF, 0xFF, 0xFF, SDL_ALPHA_OPAQUE); 2390 SDL_RenderClear(screen); 2391 SDL_SetRenderDrawColor(screen, 0x10, 0x10, 0x10, SDL_ALPHA_OPAQUE); 2392 2393 if (controller) { 2394 SetGamepadImageShowingFront(image, ShowingFront()); 2395 UpdateGamepadImageFromGamepad(image, controller->gamepad); 2396 if (display_mode == CONTROLLER_MODE_BINDING && 2397 binding_element != SDL_GAMEPAD_ELEMENT_INVALID) { 2398 SetGamepadImageElement(image, binding_element, true); 2399 } 2400 RenderGamepadImage(image); 2401 2402 if (binding_element == SDL_GAMEPAD_ELEMENT_TYPE) { 2403 SetGamepadTypeDisplayRealType(gamepad_type, SDL_GetRealGamepadType(controller->gamepad)); 2404 RenderGamepadTypeDisplay(gamepad_type); 2405 } else { 2406 RenderGamepadDisplay(gamepad_elements, controller->gamepad); 2407 } 2408 RenderJoystickDisplay(joystick_elements, controller->joystick); 2409 2410 if (display_mode == CONTROLLER_MODE_TESTING) { 2411 RenderGamepadButton(setup_mapping_button); 2412 RenderGyroDisplay(gyro_elements, gamepad_elements, controller->gamepad); 2413 } else if (display_mode == CONTROLLER_MODE_BINDING) { 2414 DrawBindingTips(screen); 2415 RenderGamepadButton(done_mapping_button); 2416 RenderGamepadButton(cancel_button); 2417 RenderGamepadButton(clear_button); 2418 if (controller->has_bindings) { 2419 RenderGamepadButton(copy_button); 2420 } 2421 RenderGamepadButton(paste_button); 2422 } 2423 2424 DrawGamepadInfo(screen); 2425 2426 UpdateGamepadEffects(); 2427 } else { 2428 DrawGamepadWaiting(screen); 2429 } 2430 SDL_Delay(16); 2431 SDL_RenderPresent(screen); 2432 2433 return SDL_APP_CONTINUE; 2434} 2435 2436SDL_AppResult SDLCALL SDL_AppInit(void **appstate, int argc, char *argv[]) 2437{ 2438 bool show_mappings = false; 2439 int i; 2440 float content_scale; 2441 int screen_width, screen_height; 2442 SDL_FRect area; 2443 int gamepad_index = -1; 2444 2445 /* Initialize test framework */ 2446 state = SDLTest_CommonCreateState(argv, 0); 2447 if (!state) { 2448 return SDL_APP_FAILURE; 2449 } 2450 2451 SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI, "1"); 2452 SDL_SetHint(SDL_HINT_JOYSTICK_ENHANCED_REPORTS, "auto"); 2453 SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_STEAM, "1"); 2454 SDL_SetHint(SDL_HINT_JOYSTICK_ROG_CHAKRAM, "1"); 2455 SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1"); 2456 SDL_SetHint(SDL_HINT_JOYSTICK_LINUX_DEADZONES, "1"); 2457 2458 /* Enable input debug logging */ 2459 SDL_SetLogPriority(SDL_LOG_CATEGORY_INPUT, SDL_LOG_PRIORITY_DEBUG); 2460 2461 /* Parse commandline */ 2462 for (i = 1; i < argc;) { 2463 int consumed; 2464 2465 consumed = SDLTest_CommonArg(state, i); 2466 if (!consumed) { 2467 if (SDL_strcmp(argv[i], "--mappings") == 0) { 2468 show_mappings = true; 2469 consumed = 1; 2470 } else if (SDL_strcmp(argv[i], "--virtual") == 0) { 2471 OpenVirtualGamepad(); 2472 consumed = 1; 2473 } else if (gamepad_index < 0) { 2474 char *endptr = NULL; 2475 gamepad_index = (int)SDL_strtol(argv[i], &endptr, 0); 2476 if (endptr != argv[i] && *endptr == '\0' && gamepad_index >= 0) { 2477 consumed = 1; 2478 } 2479 } 2480 } 2481 if (consumed <= 0) { 2482 static const char *options[] = { "[--mappings]", "[--virtual]", "[index]", NULL }; 2483 SDLTest_CommonLogUsage(state, argv[0], options); 2484 return SDL_APP_FAILURE; 2485 } 2486 2487 i += consumed; 2488 } 2489 if (gamepad_index < 0) { 2490 gamepad_index = 0; 2491 } 2492 2493 /* Initialize SDL (Note: video is required to start event loop) */ 2494 if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK | SDL_INIT_GAMEPAD)) { 2495 SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't initialize SDL: %s", SDL_GetError()); 2496 return SDL_APP_FAILURE; 2497 } 2498 2499 SDL_AddGamepadMappingsFromFile("gamecontrollerdb.txt"); 2500 2501 if (show_mappings) { 2502 int count = 0; 2503 char **mappings = SDL_GetGamepadMappings(&count); 2504 int map_i; 2505 SDL_Log("Supported mappings:"); 2506 for (map_i = 0; map_i < count; ++map_i) { 2507 SDL_Log("\t%s", mappings[map_i]); 2508 } 2509 SDL_Log("%s", ""); 2510 SDL_free(mappings); 2511 } 2512 2513 /* Create a window to display gamepad state */ 2514 content_scale = SDL_GetDisplayContentScale(SDL_GetPrimaryDisplay()); 2515 if (content_scale == 0.0f) { 2516 content_scale = 1.0f; 2517 } 2518 screen_width = (int)SDL_ceilf(SCREEN_WIDTH * content_scale); 2519 screen_height = (int)SDL_ceilf(SCREEN_HEIGHT * content_scale); 2520 window = SDL_CreateWindow("SDL Controller Test", screen_width, screen_height, SDL_WINDOW_HIGH_PIXEL_DENSITY); 2521 if (!window) { 2522 SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't create window: %s", SDL_GetError()); 2523 return SDL_APP_FAILURE; 2524 } 2525 2526 screen = SDL_CreateRenderer(window, NULL); 2527 if (!screen) { 2528 SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't create renderer: %s", SDL_GetError()); 2529 SDL_DestroyWindow(window); 2530 return SDL_APP_FAILURE; 2531 } 2532 2533 SDL_SetRenderDrawColor(screen, 0x00, 0x00, 0x00, SDL_ALPHA_OPAQUE); 2534 SDL_RenderClear(screen); 2535 SDL_RenderPresent(screen); 2536 2537 /* scale for platforms that don't give you the window size you asked for. */ 2538 SDL_SetRenderLogicalPresentation(screen, (int)SCREEN_WIDTH, (int)SCREEN_HEIGHT, 2539 SDL_LOGICAL_PRESENTATION_LETTERBOX); 2540 2541 2542 title_area.w = GAMEPAD_WIDTH; 2543 title_area.h = FONT_CHARACTER_SIZE + 2 * BUTTON_MARGIN; 2544 title_area.x = PANEL_WIDTH + PANEL_SPACING; 2545 title_area.y = TITLE_HEIGHT / 2 - title_area.h / 2; 2546 2547 type_area.w = PANEL_WIDTH - 2 * BUTTON_MARGIN; 2548 type_area.h = FONT_CHARACTER_SIZE + 2 * BUTTON_MARGIN; 2549 type_area.x = BUTTON_MARGIN; 2550 type_area.y = TITLE_HEIGHT / 2 - type_area.h / 2; 2551 2552 image = CreateGamepadImage(screen); 2553 if (!image) { 2554 SDL_DestroyRenderer(screen); 2555 SDL_DestroyWindow(window); 2556 return SDL_APP_FAILURE; 2557 } 2558 SetGamepadImagePosition(image, PANEL_WIDTH + PANEL_SPACING, TITLE_HEIGHT); 2559 2560 gamepad_elements = CreateGamepadDisplay(screen); 2561 area.x = 0; 2562 area.y = TITLE_HEIGHT; 2563 area.w = PANEL_WIDTH; 2564 area.h = GAMEPAD_HEIGHT; 2565 SetGamepadDisplayArea(gamepad_elements, &area); 2566 2567 gyro_elements = CreateGyroDisplay(screen); 2568 const float vidReservedHeight = 24.0f; 2569 /* Bottom right of the screen */ 2570 area.w = SCREEN_WIDTH * 0.375f; 2571 area.h = SCREEN_HEIGHT * 0.475f; 2572 area.x = SCREEN_WIDTH - area.w; 2573 area.y = SCREEN_HEIGHT - area.h - vidReservedHeight; 2574 2575 SetGyroDisplayArea(gyro_elements, &area); 2576 InitCirclePoints3D(); 2577 2578 gamepad_type = CreateGamepadTypeDisplay(screen); 2579 area.x = 0; 2580 area.y = TITLE_HEIGHT; 2581 area.w = PANEL_WIDTH; 2582 area.h = GAMEPAD_HEIGHT; 2583 SetGamepadTypeDisplayArea(gamepad_type, &area); 2584 2585 joystick_elements = CreateJoystickDisplay(screen); 2586 area.x = PANEL_WIDTH + PANEL_SPACING + GAMEPAD_WIDTH + PANEL_SPACING; 2587 area.y = TITLE_HEIGHT; 2588 area.w = PANEL_WIDTH; 2589 area.h = GAMEPAD_HEIGHT; 2590 SetJoystickDisplayArea(joystick_elements, &area); 2591 2592 setup_mapping_button = CreateGamepadButton(screen, "Setup Mapping"); 2593 area.w = SDL_max(MINIMUM_BUTTON_WIDTH, GetGamepadButtonLabelWidth(setup_mapping_button) + 2 * BUTTON_PADDING); 2594 area.h = GetGamepadButtonLabelHeight(setup_mapping_button) + 2 * BUTTON_PADDING; 2595 area.x = BUTTON_MARGIN; 2596 area.y = SCREEN_HEIGHT - BUTTON_MARGIN - area.h; 2597 SetGamepadButtonArea(setup_mapping_button, &area); 2598 2599 cancel_button = CreateGamepadButton(screen, "Cancel"); 2600 area.w = SDL_max(MINIMUM_BUTTON_WIDTH, GetGamepadButtonLabelWidth(cancel_button) + 2 * BUTTON_PADDING); 2601 area.h = GetGamepadButtonLabelHeight(cancel_button) + 2 * BUTTON_PADDING; 2602 area.x = BUTTON_MARGIN; 2603 area.y = SCREEN_HEIGHT - BUTTON_MARGIN - area.h; 2604 SetGamepadButtonArea(cancel_button, &area); 2605 2606 clear_button = CreateGamepadButton(screen, "Clear"); 2607 area.x += area.w + BUTTON_PADDING; 2608 area.w = SDL_max(MINIMUM_BUTTON_WIDTH, GetGamepadButtonLabelWidth(clear_button) + 2 * BUTTON_PADDING); 2609 area.h = GetGamepadButtonLabelHeight(clear_button) + 2 * BUTTON_PADDING; 2610 area.y = SCREEN_HEIGHT - BUTTON_MARGIN - area.h; 2611 SetGamepadButtonArea(clear_button, &area); 2612 2613 copy_button = CreateGamepadButton(screen, "Copy"); 2614 area.x += area.w + BUTTON_PADDING; 2615 area.w = SDL_max(MINIMUM_BUTTON_WIDTH, GetGamepadButtonLabelWidth(copy_button) + 2 * BUTTON_PADDING); 2616 area.h = GetGamepadButtonLabelHeight(copy_button) + 2 * BUTTON_PADDING; 2617 area.y = SCREEN_HEIGHT - BUTTON_MARGIN - area.h; 2618 SetGamepadButtonArea(copy_button, &area); 2619 2620 paste_button = CreateGamepadButton(screen, "Paste"); 2621 area.x += area.w + BUTTON_PADDING; 2622 area.w = SDL_max(MINIMUM_BUTTON_WIDTH, GetGamepadButtonLabelWidth(paste_button) + 2 * BUTTON_PADDING); 2623 area.h = GetGamepadButtonLabelHeight(paste_button) + 2 * BUTTON_PADDING; 2624 area.y = SCREEN_HEIGHT - BUTTON_MARGIN - area.h; 2625 SetGamepadButtonArea(paste_button, &area); 2626 2627 done_mapping_button = CreateGamepadButton(screen, "Done"); 2628 area.w = SDL_max(MINIMUM_BUTTON_WIDTH, GetGamepadButtonLabelWidth(done_mapping_button) + 2 * BUTTON_PADDING); 2629 area.h = GetGamepadButtonLabelHeight(done_mapping_button) + 2 * BUTTON_PADDING; 2630 area.x = SCREEN_WIDTH / 2 - area.w / 2; 2631 area.y = SCREEN_HEIGHT - BUTTON_MARGIN - area.h; 2632 SetGamepadButtonArea(done_mapping_button, &area); 2633 2634 /* Process the initial gamepad list */ 2635 SDL_AppIterate(NULL); 2636 2637 if (gamepad_index < num_controllers) { 2638 SetController(controllers[gamepad_index].id); 2639 } else if (num_controllers > 0) { 2640 SetController(controllers[0].id); 2641 } 2642 2643 return SDL_APP_CONTINUE; 2644} 2645 2646void SDLCALL SDL_AppQuit(void *appstate, SDL_AppResult result) 2647{ 2648 CloseVirtualGamepad(); 2649 while (num_controllers > 0) { 2650 HandleGamepadRemoved(controllers[0].id); 2651 DelController(controllers[0].id); 2652 } 2653 SDL_free(controllers); 2654 SDL_free(controller_name); 2655 DestroyGamepadImage(image); 2656 DestroyGamepadDisplay(gamepad_elements); 2657 DestroyGyroDisplay(gyro_elements); 2658 DestroyGamepadTypeDisplay(gamepad_type); 2659 DestroyJoystickDisplay(joystick_elements); 2660 DestroyGamepadButton(setup_mapping_button); 2661 DestroyGamepadButton(done_mapping_button); 2662 DestroyGamepadButton(cancel_button); 2663 DestroyGamepadButton(clear_button); 2664 DestroyGamepadButton(copy_button); 2665 DestroyGamepadButton(paste_button); 2666 SDLTest_CleanupTextDrawing(); 2667 SDL_DestroyRenderer(screen); 2668 SDL_DestroyWindow(window); 2669 SDL_Quit(); 2670 SDLTest_CommonDestroyState(state); 2671} 2672
[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.