12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669 |
- /*
- Copyright (C) 1997-2025 Sam Lantinga <[email protected]>
- This software is provided 'as-is', without any express or implied
- warranty. In no event will the authors be held liable for any damages
- arising from the use of this software.
- Permission is granted to anyone to use this software for any purpose,
- including commercial applications, and to alter it and redistribute it
- freely.
- */
- /* Simple program to test the SDL controller routines */
- #define SDL_MAIN_USE_CALLBACKS
- #include <SDL3/SDL.h>
- #include <SDL3/SDL_main.h>
- #include <SDL3/SDL_test.h>
- #include <SDL3/SDL_test_font.h>
- #ifdef SDL_PLATFORM_EMSCRIPTEN
- #include <emscripten/emscripten.h>
- #endif
- #include "gamepadutils.h"
- #include "testutils.h"
- #if 0
- #define DEBUG_AXIS_MAPPING
- #endif
- #define TITLE_HEIGHT 48.0f
- #define PANEL_SPACING 25.0f
- #define PANEL_WIDTH 250.0f
- #define GAMEPAD_WIDTH 512.0f
- #define GAMEPAD_HEIGHT 560.0f
- #define BUTTON_MARGIN 16.0f
- #define SCREEN_WIDTH (PANEL_WIDTH + PANEL_SPACING + GAMEPAD_WIDTH + PANEL_SPACING + PANEL_WIDTH)
- #define SCREEN_HEIGHT (TITLE_HEIGHT + GAMEPAD_HEIGHT)
- typedef struct
- {
- bool m_bMoving;
- int m_nLastValue;
- int m_nStartingValue;
- int m_nFarthestValue;
- } AxisState;
- struct Quaternion
- {
- float x, y, z, w;
- };
- static Quaternion quat_identity = { 0.0f, 0.0f, 0.0f, 1.0f };
- Quaternion QuaternionFromEuler(float pitch, float yaw, float roll)
- {
- float cx = SDL_cosf(pitch * 0.5f);
- float sx = SDL_sinf(pitch * 0.5f);
- float cy = SDL_cosf(yaw * 0.5f);
- float sy = SDL_sinf(yaw * 0.5f);
- float cz = SDL_cosf(roll * 0.5f);
- float sz = SDL_sinf(roll * 0.5f);
- Quaternion q;
- q.w = cx * cy * cz + sx * sy * sz;
- q.x = sx * cy * cz - cx * sy * sz;
- q.y = cx * sy * cz + sx * cy * sz;
- q.z = cx * cy * sz - sx * sy * cz;
- return q;
- }
- #define RAD_TO_DEG (180.0f / SDL_PI_F)
- /* Decomposes quaternion into Yaw (Y), Pitch (X), Roll (Z) using Y-X-Z order in a left-handed system */
- void QuaternionToYXZ(Quaternion q, float *pitch, float *yaw, float *roll)
- {
- /* Precalculate repeated expressions */
- float qxx = q.x * q.x;
- float qyy = q.y * q.y;
- float qzz = q.z * q.z;
- float qxy = q.x * q.y;
- float qxz = q.x * q.z;
- float qyz = q.y * q.z;
- float qwx = q.w * q.x;
- float qwy = q.w * q.y;
- float qwz = q.w * q.z;
- /* Yaw (around Y) */
- if (yaw) {
- *yaw = SDL_atan2f(2.0f * (qwy + qxz), 1.0f - 2.0f * (qyy + qzz)) * RAD_TO_DEG;
- }
- /* Pitch (around X) */
- float sinp = 2.0f * (qwx - qyz);
- if (pitch) {
- if (SDL_fabsf(sinp) >= 1.0f) {
- *pitch = SDL_copysignf(90.0f, sinp); /* Clamp to avoid domain error */
- } else {
- *pitch = SDL_asinf(sinp) * RAD_TO_DEG;
- }
- }
- /* Roll (around Z) */
- if (roll) {
- *roll = SDL_atan2f(2.0f * (qwz + qxy), 1.0f - 2.0f * (qxx + qzz)) * RAD_TO_DEG;
- }
- }
- Quaternion MultiplyQuaternion(Quaternion a, Quaternion b)
- {
- Quaternion q;
- q.x = a.x * b.w + a.y * b.z - a.z * b.y + a.w * b.x;
- q.y = -a.x * b.z + a.y * b.w + a.z * b.x + a.w * b.y;
- q.z = a.x * b.y - a.y * b.x + a.z * b.w + a.w * b.z;
- q.w = -a.x * b.x - a.y * b.y - a.z * b.z + a.w * b.w;
- return q;
- }
- void NormalizeQuaternion(Quaternion *q)
- {
- float mag = SDL_sqrtf(q->x * q->x + q->y * q->y + q->z * q->z + q->w * q->w);
- if (mag > 0.0f) {
- q->x /= mag;
- q->y /= mag;
- q->z /= mag;
- q->w /= mag;
- }
- }
- float Normalize180(float angle)
- {
- angle = SDL_fmodf(angle + 180.0f, 360.0f);
- if (angle < 0.0f) {
- angle += 360.0f;
- }
- return angle - 180.0f;
- }
- typedef struct
- {
- Uint64 gyro_packet_number;
- Uint64 accelerometer_packet_number;
- /* When both gyro and accelerometer events have been processed, we can increment this and use it to calculate polling rate over time.*/
- Uint64 imu_packet_counter;
- Uint64 starting_time_stamp_ns; /* Use this to help estimate how many packets are received over a duration */
- Uint16 imu_estimated_sensor_rate; /* in Hz, used to estimate how many packets are received over a duration */
- 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 */
- /* Fresh data copied from sensor events. */
- float accel_data[3]; /* Meters per second squared, i.e. 9.81f means 9.81 meters per second squared */
- float gyro_data[3]; /* Degrees per second, i.e. 100.0f means 100 degrees per second */
- float last_accel_data[3];/* Needed to detect motion (and inhibit drift calibration) */
- float accelerometer_length_squared; /* The current length squared from last packet to this packet */
- float accelerometer_tolerance_squared; /* In phase one of calibration we calculate this as the largest accelerometer_length_squared over the time period */
- float gyro_drift_accumulator[3];
- EGyroCalibrationPhase calibration_phase; /* [ GYRO_CALIBRATION_PHASE_OFF, GYRO_CALIBRATION_PHASE_NOISE_PROFILING, GYRO_CALIBRATION_PHASE_DRIFT_PROFILING,GYRO_CALIBRATION_PHASE_COMPLETE ] */
- 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. */
- int gyro_drift_sample_count;
- float gyro_drift_solution[3]; /* Non zero if calibration is complete. */
- Quaternion integrated_rotation; /* Used to help test whether the time stamps and gyro degrees per second are set up correctly by the HID implementation */
- } IMUState;
- /* First stage of calibration - get the noise profile of the accelerometer */
- void BeginNoiseCalibrationPhase(IMUState *imustate)
- {
- imustate->accelerometer_tolerance_squared = ACCELEROMETER_NOISE_THRESHOLD;
- imustate->calibration_phase = GYRO_CALIBRATION_PHASE_NOISE_PROFILING;
- imustate->calibration_phase_start_time_ticks_ns = SDL_GetTicksNS();
- }
- /* Reset the Drift calculation state */
- void BeginDriftCalibrationPhase(IMUState *imustate)
- {
- imustate->calibration_phase = GYRO_CALIBRATION_PHASE_DRIFT_PROFILING;
- imustate->calibration_phase_start_time_ticks_ns = SDL_GetTicksNS();
- imustate->gyro_drift_sample_count = 0;
- SDL_zeroa(imustate->gyro_drift_solution);
- SDL_zeroa(imustate->gyro_drift_accumulator);
- }
- /* Initial/full reset of state */
- void ResetIMUState(IMUState *imustate)
- {
- imustate->gyro_packet_number = 0;
- imustate->accelerometer_packet_number = 0;
- imustate->starting_time_stamp_ns = SDL_GetTicksNS();
- imustate->integrated_rotation = quat_identity;
- imustate->accelerometer_length_squared = 0.0f;
- imustate->accelerometer_tolerance_squared = ACCELEROMETER_NOISE_THRESHOLD;
- imustate->calibration_phase = GYRO_CALIBRATION_PHASE_OFF;
- imustate->calibration_phase_start_time_ticks_ns = SDL_GetTicksNS();
- imustate->integrated_rotation = quat_identity;
- SDL_zeroa(imustate->last_accel_data);
- SDL_zeroa(imustate->gyro_drift_solution);
- SDL_zeroa(imustate->gyro_drift_accumulator);
- }
- void ResetGyroOrientation(IMUState *imustate)
- {
- imustate->integrated_rotation = quat_identity;
- }
- /* More time = more accurate drift correction*/
- #define SDL_GAMEPAD_IMU_NOISE_SETTLING_PERIOD_NS ( SDL_NS_PER_SECOND / 2)
- #define SDL_GAMEPAD_IMU_NOISE_EVALUATION_PERIOD_NS (4 * SDL_NS_PER_SECOND)
- #define SDL_GAMEPAD_IMU_NOISE_PROFILING_PHASE_DURATION_NS (SDL_GAMEPAD_IMU_NOISE_SETTLING_PERIOD_NS + SDL_GAMEPAD_IMU_NOISE_EVALUATION_PERIOD_NS)
- #define SDL_GAMEPAD_IMU_CALIBRATION_PHASE_DURATION_NS (5 * SDL_NS_PER_SECOND)
- /*
- * Find the maximum accelerometer noise over the duration of the GYRO_CALIBRATION_PHASE_NOISE_PROFILING phase.
- */
- void CalibrationPhase_NoiseProfiling(IMUState *imustate)
- {
- /* 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. */
- if (imustate->accelerometer_length_squared > ACCELEROMETER_MAX_NOISE_G_SQ) {
- BeginNoiseCalibrationPhase(imustate);
- return;
- }
- Uint64 now = SDL_GetTicksNS();
- Uint64 delta_ns = now - imustate->calibration_phase_start_time_ticks_ns;
- /* 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 */
- if (delta_ns > SDL_GAMEPAD_IMU_NOISE_SETTLING_PERIOD_NS) {
- /* Get the largest noise spike in the period of evaluation */
- if (imustate->accelerometer_length_squared > imustate->accelerometer_tolerance_squared) {
- imustate->accelerometer_tolerance_squared = imustate->accelerometer_length_squared;
- }
- }
- /* Switch phase if we go over the time limit */
- if (delta_ns >= SDL_GAMEPAD_IMU_NOISE_PROFILING_PHASE_DURATION_NS) {
- BeginDriftCalibrationPhase(imustate);
- }
- }
- /*
- * Average drift _per packet_ as opposed to _per second_
- * This reduces a small amount of overhead when applying the drift correction.
- */
- void FinalizeDriftSolution(IMUState *imustate)
- {
- if (imustate->gyro_drift_sample_count >= 0) {
- imustate->gyro_drift_solution[0] = imustate->gyro_drift_accumulator[0] / (float)imustate->gyro_drift_sample_count;
- imustate->gyro_drift_solution[1] = imustate->gyro_drift_accumulator[1] / (float)imustate->gyro_drift_sample_count;
- imustate->gyro_drift_solution[2] = imustate->gyro_drift_accumulator[2] / (float)imustate->gyro_drift_sample_count;
- }
- imustate->calibration_phase = GYRO_CALIBRATION_PHASE_COMPLETE;
- ResetGyroOrientation(imustate);
- }
- void CalibrationPhase_DriftProfiling(IMUState *imustate)
- {
- /* Ideal threshold will vary considerably depending on IMU. PS5 needs a low value (0.05f). Nintendo Switch needs a higher value (0.15f). */
- if (imustate->accelerometer_length_squared > imustate->accelerometer_tolerance_squared) {
- /* Reset the drift calibration if the accelerometer has moved significantly */
- BeginDriftCalibrationPhase(imustate);
- } else {
- /* Sensor is stationary enough to evaluate for drift.*/
- ++imustate->gyro_drift_sample_count;
- imustate->gyro_drift_accumulator[0] += imustate->gyro_data[0];
- imustate->gyro_drift_accumulator[1] += imustate->gyro_data[1];
- imustate->gyro_drift_accumulator[2] += imustate->gyro_data[2];
- /* Finish phase if we go over the time limit */
- Uint64 now = SDL_GetTicksNS();
- Uint64 delta_ns = now - imustate->calibration_phase_start_time_ticks_ns;
- if (delta_ns >= SDL_GAMEPAD_IMU_CALIBRATION_PHASE_DURATION_NS) {
- FinalizeDriftSolution(imustate);
- }
- }
- }
- /* Sample gyro packet in order to calculate drift*/
- void SampleGyroPacketForDrift(IMUState *imustate)
- {
- /* Get the length squared difference of the last accelerometer data vs. the new one */
- float accelerometer_difference[3];
- accelerometer_difference[0] = imustate->accel_data[0] - imustate->last_accel_data[0];
- accelerometer_difference[1] = imustate->accel_data[1] - imustate->last_accel_data[1];
- accelerometer_difference[2] = imustate->accel_data[2] - imustate->last_accel_data[2];
- SDL_memcpy(imustate->last_accel_data, imustate->accel_data, sizeof(imustate->last_accel_data));
- imustate->accelerometer_length_squared = accelerometer_difference[0] * accelerometer_difference[0] + accelerometer_difference[1] * accelerometer_difference[1] + accelerometer_difference[2] * accelerometer_difference[2];
- if (imustate->calibration_phase == GYRO_CALIBRATION_PHASE_NOISE_PROFILING)
- CalibrationPhase_NoiseProfiling(imustate);
- if (imustate->calibration_phase == GYRO_CALIBRATION_PHASE_DRIFT_PROFILING)
- CalibrationPhase_DriftProfiling(imustate);
- }
- void ApplyDriftSolution(float *gyro_data, const float *drift_solution)
- {
- gyro_data[0] -= drift_solution[0];
- gyro_data[1] -= drift_solution[1];
- gyro_data[2] -= drift_solution[2];
- }
- void UpdateGyroRotation(IMUState *imustate, Uint64 sensorTimeStampDelta_ns)
- {
- float sensorTimeDeltaTimeSeconds = SDL_NS_TO_SECONDS((float)sensorTimeStampDelta_ns);
- /* Integrate speeds to get Rotational Displacement*/
- float pitch = imustate->gyro_data[0] * sensorTimeDeltaTimeSeconds;
- float yaw = imustate->gyro_data[1] * sensorTimeDeltaTimeSeconds;
- float roll = imustate->gyro_data[2] * sensorTimeDeltaTimeSeconds;
- /* Use quaternions to avoid gimbal lock*/
- Quaternion delta_rotation = QuaternionFromEuler(pitch, yaw, roll);
- imustate->integrated_rotation = MultiplyQuaternion(imustate->integrated_rotation, delta_rotation);
- NormalizeQuaternion(&imustate->integrated_rotation);
- }
- typedef struct
- {
- SDL_JoystickID id;
- SDL_Joystick *joystick;
- int num_axes;
- AxisState *axis_state;
- IMUState *imu_state;
- SDL_Gamepad *gamepad;
- char *mapping;
- bool has_bindings;
- int audio_route;
- int trigger_effect;
- } Controller;
- static SDLTest_CommonState *state;
- static SDL_Window *window = NULL;
- static SDL_Renderer *screen = NULL;
- static ControllerDisplayMode display_mode = CONTROLLER_MODE_TESTING;
- static GamepadImage *image = NULL;
- static GamepadDisplay *gamepad_elements = NULL;
- static GyroDisplay *gyro_elements = NULL;
- static GamepadTypeDisplay *gamepad_type = NULL;
- static JoystickDisplay *joystick_elements = NULL;
- static GamepadButton *setup_mapping_button = NULL;
- static GamepadButton *done_mapping_button = NULL;
- static GamepadButton *cancel_button = NULL;
- static GamepadButton *clear_button = NULL;
- static GamepadButton *copy_button = NULL;
- static GamepadButton *paste_button = NULL;
- static char *backup_mapping = NULL;
- static bool done = false;
- static bool set_LED = false;
- static int num_controllers = 0;
- static Controller *controllers;
- static Controller *controller;
- static SDL_JoystickID mapping_controller = 0;
- static int binding_element = SDL_GAMEPAD_ELEMENT_INVALID;
- static int last_binding_element = SDL_GAMEPAD_ELEMENT_INVALID;
- static bool binding_flow = false;
- static int binding_flow_direction = 0;
- static Uint64 binding_advance_time = 0;
- static SDL_FRect title_area;
- static bool title_highlighted;
- static bool title_pressed;
- static SDL_FRect type_area;
- static bool type_highlighted;
- static bool type_pressed;
- static char *controller_name;
- static SDL_Joystick *virtual_joystick = NULL;
- static SDL_GamepadAxis virtual_axis_active = SDL_GAMEPAD_AXIS_INVALID;
- static float virtual_axis_start_x;
- static float virtual_axis_start_y;
- static SDL_GamepadButton virtual_button_active = SDL_GAMEPAD_BUTTON_INVALID;
- static bool virtual_touchpad_active = false;
- static float virtual_touchpad_x;
- static float virtual_touchpad_y;
- static int s_arrBindingOrder[] = {
- /* Standard sequence */
- SDL_GAMEPAD_BUTTON_SOUTH,
- SDL_GAMEPAD_BUTTON_EAST,
- SDL_GAMEPAD_BUTTON_WEST,
- SDL_GAMEPAD_BUTTON_NORTH,
- SDL_GAMEPAD_BUTTON_DPAD_LEFT,
- SDL_GAMEPAD_BUTTON_DPAD_RIGHT,
- SDL_GAMEPAD_BUTTON_DPAD_UP,
- SDL_GAMEPAD_BUTTON_DPAD_DOWN,
- SDL_GAMEPAD_ELEMENT_AXIS_LEFTX_NEGATIVE,
- SDL_GAMEPAD_ELEMENT_AXIS_LEFTX_POSITIVE,
- SDL_GAMEPAD_ELEMENT_AXIS_LEFTY_NEGATIVE,
- SDL_GAMEPAD_ELEMENT_AXIS_LEFTY_POSITIVE,
- SDL_GAMEPAD_BUTTON_LEFT_STICK,
- SDL_GAMEPAD_ELEMENT_AXIS_RIGHTX_NEGATIVE,
- SDL_GAMEPAD_ELEMENT_AXIS_RIGHTX_POSITIVE,
- SDL_GAMEPAD_ELEMENT_AXIS_RIGHTY_NEGATIVE,
- SDL_GAMEPAD_ELEMENT_AXIS_RIGHTY_POSITIVE,
- SDL_GAMEPAD_BUTTON_RIGHT_STICK,
- SDL_GAMEPAD_BUTTON_LEFT_SHOULDER,
- SDL_GAMEPAD_ELEMENT_AXIS_LEFT_TRIGGER,
- SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER,
- SDL_GAMEPAD_ELEMENT_AXIS_RIGHT_TRIGGER,
- SDL_GAMEPAD_BUTTON_BACK,
- SDL_GAMEPAD_BUTTON_START,
- SDL_GAMEPAD_BUTTON_GUIDE,
- SDL_GAMEPAD_BUTTON_MISC1,
- SDL_GAMEPAD_ELEMENT_INVALID,
- /* Paddle sequence */
- SDL_GAMEPAD_BUTTON_RIGHT_PADDLE1,
- SDL_GAMEPAD_BUTTON_LEFT_PADDLE1,
- SDL_GAMEPAD_BUTTON_RIGHT_PADDLE2,
- SDL_GAMEPAD_BUTTON_LEFT_PADDLE2,
- SDL_GAMEPAD_ELEMENT_INVALID,
- };
- static const char *GetSensorName(SDL_SensorType sensor)
- {
- switch (sensor) {
- case SDL_SENSOR_ACCEL:
- return "accelerometer";
- case SDL_SENSOR_GYRO:
- return "gyro";
- case SDL_SENSOR_ACCEL_L:
- return "accelerometer (L)";
- case SDL_SENSOR_GYRO_L:
- return "gyro (L)";
- case SDL_SENSOR_ACCEL_R:
- return "accelerometer (R)";
- case SDL_SENSOR_GYRO_R:
- return "gyro (R)";
- default:
- return "UNKNOWN";
- }
- }
- /* PS5 trigger effect documentation:
- https://controllers.fandom.com/wiki/Sony_DualSense#FFB_Trigger_Modes
- */
- typedef struct
- {
- Uint8 ucEnableBits1; /* 0 */
- Uint8 ucEnableBits2; /* 1 */
- Uint8 ucRumbleRight; /* 2 */
- Uint8 ucRumbleLeft; /* 3 */
- Uint8 ucHeadphoneVolume; /* 4 */
- Uint8 ucSpeakerVolume; /* 5 */
- Uint8 ucMicrophoneVolume; /* 6 */
- Uint8 ucAudioEnableBits; /* 7 */
- Uint8 ucMicLightMode; /* 8 */
- Uint8 ucAudioMuteBits; /* 9 */
- Uint8 rgucRightTriggerEffect[11]; /* 10 */
- Uint8 rgucLeftTriggerEffect[11]; /* 21 */
- Uint8 rgucUnknown1[6]; /* 32 */
- Uint8 ucLedFlags; /* 38 */
- Uint8 rgucUnknown2[2]; /* 39 */
- Uint8 ucLedAnim; /* 41 */
- Uint8 ucLedBrightness; /* 42 */
- Uint8 ucPadLights; /* 43 */
- Uint8 ucLedRed; /* 44 */
- Uint8 ucLedGreen; /* 45 */
- Uint8 ucLedBlue; /* 46 */
- } DS5EffectsState_t;
- static void CyclePS5AudioRoute(Controller *device)
- {
- DS5EffectsState_t effects;
- device->audio_route = (device->audio_route + 1) % 4;
- SDL_zero(effects);
- switch (device->audio_route) {
- case 0:
- /* Audio disabled */
- effects.ucEnableBits1 |= (0x80 | 0x20 | 0x10); /* Modify audio route and speaker / headphone volume */
- effects.ucSpeakerVolume = 0; /* Minimum volume */
- effects.ucHeadphoneVolume = 0; /* Minimum volume */
- effects.ucAudioEnableBits = 0x00; /* Output to headphones */
- break;
- case 1:
- /* Headphones */
- effects.ucEnableBits1 |= (0x80 | 0x10); /* Modify audio route and headphone volume */
- effects.ucHeadphoneVolume = 50; /* 50% volume - don't blast into the ears */
- effects.ucAudioEnableBits = 0x00; /* Output to headphones */
- break;
- case 2:
- /* Speaker */
- effects.ucEnableBits1 |= (0x80 | 0x20); /* Modify audio route and speaker volume */
- effects.ucSpeakerVolume = 100; /* Maximum volume */
- effects.ucAudioEnableBits = 0x30; /* Output to speaker */
- break;
- case 3:
- /* Both */
- effects.ucEnableBits1 |= (0x80 | 0x20 | 0x10); /* Modify audio route and speaker / headphone volume */
- effects.ucSpeakerVolume = 100; /* Maximum volume */
- effects.ucHeadphoneVolume = 50; /* 50% volume - don't blast into the ears */
- effects.ucAudioEnableBits = 0x20; /* Output to both speaker and headphones */
- break;
- }
- SDL_SendGamepadEffect(device->gamepad, &effects, sizeof(effects));
- }
- static void CyclePS5TriggerEffect(Controller *device)
- {
- DS5EffectsState_t effects;
- Uint8 trigger_effects[3][11] = {
- /* Clear trigger effect */
- { 0x05, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
- /* Constant resistance across entire trigger pull */
- { 0x01, 0, 110, 0, 0, 0, 0, 0, 0, 0, 0 },
- /* Resistance and vibration when trigger is pulled */
- { 0x06, 15, 63, 128, 0, 0, 0, 0, 0, 0, 0 },
- };
- device->trigger_effect = (device->trigger_effect + 1) % SDL_arraysize(trigger_effects);
- SDL_zero(effects);
- effects.ucEnableBits1 |= (0x04 | 0x08); /* Modify right and left trigger effect respectively */
- SDL_memcpy(effects.rgucRightTriggerEffect, trigger_effects[device->trigger_effect], sizeof(trigger_effects[0]));
- SDL_memcpy(effects.rgucLeftTriggerEffect, trigger_effects[device->trigger_effect], sizeof(trigger_effects[0]));
- SDL_SendGamepadEffect(device->gamepad, &effects, sizeof(effects));
- }
- static void ClearButtonHighlights(void)
- {
- title_highlighted = false;
- title_pressed = false;
- type_highlighted = false;
- type_pressed = false;
- ClearGamepadImage(image);
- SetGamepadDisplayHighlight(gamepad_elements, SDL_GAMEPAD_ELEMENT_INVALID, false);
- SetGamepadTypeDisplayHighlight(gamepad_type, SDL_GAMEPAD_TYPE_UNSELECTED, false);
- SetGamepadButtonHighlight(GetGyroResetButton( gyro_elements ), false, false);
- SetGamepadButtonHighlight(GetGyroCalibrateButton(gyro_elements), false, false);
- SetGamepadButtonHighlight(setup_mapping_button, false, false);
- SetGamepadButtonHighlight(done_mapping_button, false, false);
- SetGamepadButtonHighlight(cancel_button, false, false);
- SetGamepadButtonHighlight(clear_button, false, false);
- SetGamepadButtonHighlight(copy_button, false, false);
- SetGamepadButtonHighlight(paste_button, false, false);
- }
- static void UpdateButtonHighlights(float x, float y, bool button_down)
- {
- ClearButtonHighlights();
- SetGamepadButtonHighlight(GetGyroResetButton(gyro_elements), GamepadButtonContains(GetGyroResetButton(gyro_elements), x, y), button_down);
- SetGamepadButtonHighlight(GetGyroCalibrateButton(gyro_elements), GamepadButtonContains(GetGyroCalibrateButton(gyro_elements), x, y), button_down);
- if (display_mode == CONTROLLER_MODE_TESTING) {
- SetGamepadButtonHighlight(setup_mapping_button, GamepadButtonContains(setup_mapping_button, x, y), button_down);
- } else if (display_mode == CONTROLLER_MODE_BINDING) {
- SDL_FPoint point;
- int gamepad_highlight_element = SDL_GAMEPAD_ELEMENT_INVALID;
- char *joystick_highlight_element;
- point.x = x;
- point.y = y;
- if (SDL_PointInRectFloat(&point, &title_area)) {
- title_highlighted = true;
- title_pressed = button_down;
- } else {
- title_highlighted = false;
- title_pressed = false;
- }
- if (SDL_PointInRectFloat(&point, &type_area)) {
- type_highlighted = true;
- type_pressed = button_down;
- } else {
- type_highlighted = false;
- type_pressed = false;
- }
- if (controller->joystick != virtual_joystick) {
- gamepad_highlight_element = GetGamepadImageElementAt(image, x, y);
- }
- if (gamepad_highlight_element == SDL_GAMEPAD_ELEMENT_INVALID) {
- gamepad_highlight_element = GetGamepadDisplayElementAt(gamepad_elements, controller->gamepad, x, y);
- }
- SetGamepadDisplayHighlight(gamepad_elements, gamepad_highlight_element, button_down);
- if (binding_element == SDL_GAMEPAD_ELEMENT_TYPE) {
- int gamepad_highlight_type = GetGamepadTypeDisplayAt(gamepad_type, x, y);
- SetGamepadTypeDisplayHighlight(gamepad_type, gamepad_highlight_type, button_down);
- }
- joystick_highlight_element = GetJoystickDisplayElementAt(joystick_elements, controller->joystick, x, y);
- SetJoystickDisplayHighlight(joystick_elements, joystick_highlight_element, button_down);
- SDL_free(joystick_highlight_element);
- SetGamepadButtonHighlight(done_mapping_button, GamepadButtonContains(done_mapping_button, x, y), button_down);
- SetGamepadButtonHighlight(cancel_button, GamepadButtonContains(cancel_button, x, y), button_down);
- SetGamepadButtonHighlight(clear_button, GamepadButtonContains(clear_button, x, y), button_down);
- SetGamepadButtonHighlight(copy_button, GamepadButtonContains(copy_button, x, y), button_down);
- SetGamepadButtonHighlight(paste_button, GamepadButtonContains(paste_button, x, y), button_down);
- }
- }
- static int StandardizeAxisValue(int nValue)
- {
- if (nValue > SDL_JOYSTICK_AXIS_MAX / 2) {
- return SDL_JOYSTICK_AXIS_MAX;
- } else if (nValue < SDL_JOYSTICK_AXIS_MIN / 2) {
- return SDL_JOYSTICK_AXIS_MIN;
- } else {
- return 0;
- }
- }
- static void RefreshControllerName(void)
- {
- const char *name = NULL;
- SDL_free(controller_name);
- controller_name = NULL;
- if (controller) {
- if (controller->gamepad) {
- name = SDL_GetGamepadName(controller->gamepad);
- } else {
- name = SDL_GetJoystickName(controller->joystick);
- }
- }
- if (name) {
- controller_name = SDL_strdup(name);
- } else {
- controller_name = SDL_strdup("");
- }
- }
- static void SetAndFreeGamepadMapping(char *mapping)
- {
- SDL_SetGamepadMapping(controller->id, mapping);
- SDL_free(mapping);
- }
- static void SetCurrentBindingElement(int element, bool flow)
- {
- int i;
- if (binding_element == SDL_GAMEPAD_ELEMENT_NAME) {
- RefreshControllerName();
- }
- if (element == SDL_GAMEPAD_ELEMENT_INVALID) {
- binding_flow_direction = 0;
- last_binding_element = SDL_GAMEPAD_ELEMENT_INVALID;
- } else {
- last_binding_element = binding_element;
- }
- binding_element = element;
- binding_flow = flow || (element == SDL_GAMEPAD_BUTTON_SOUTH);
- binding_advance_time = 0;
- for (i = 0; i < controller->num_axes; ++i) {
- controller->axis_state[i].m_nFarthestValue = controller->axis_state[i].m_nStartingValue;
- }
- SetGamepadDisplaySelected(gamepad_elements, element);
- }
- static void SetNextBindingElement(void)
- {
- int i;
- if (binding_element == SDL_GAMEPAD_ELEMENT_INVALID) {
- return;
- }
- for (i = 0; i < SDL_arraysize(s_arrBindingOrder); ++i) {
- if (binding_element == s_arrBindingOrder[i]) {
- binding_flow_direction = 1;
- SetCurrentBindingElement(s_arrBindingOrder[i + 1], true);
- return;
- }
- }
- SetCurrentBindingElement(SDL_GAMEPAD_ELEMENT_INVALID, false);
- }
- static void SetPrevBindingElement(void)
- {
- int i;
- if (binding_element == SDL_GAMEPAD_ELEMENT_INVALID) {
- return;
- }
- for (i = 1; i < SDL_arraysize(s_arrBindingOrder); ++i) {
- if (binding_element == s_arrBindingOrder[i]) {
- binding_flow_direction = -1;
- SetCurrentBindingElement(s_arrBindingOrder[i - 1], true);
- return;
- }
- }
- SetCurrentBindingElement(SDL_GAMEPAD_ELEMENT_INVALID, false);
- }
- static void StopBinding(void)
- {
- SetCurrentBindingElement(SDL_GAMEPAD_ELEMENT_INVALID, false);
- }
- typedef struct
- {
- int axis;
- int direction;
- } AxisInfo;
- static bool ParseAxisInfo(const char *description, AxisInfo *info)
- {
- if (!description) {
- return false;
- }
- if (*description == '-') {
- info->direction = -1;
- ++description;
- } else if (*description == '+') {
- info->direction = 1;
- ++description;
- } else {
- info->direction = 0;
- }
- if (description[0] == 'a' && SDL_isdigit(description[1])) {
- ++description;
- info->axis = SDL_atoi(description);
- return true;
- }
- return false;
- }
- static void CommitBindingElement(const char *binding, bool force)
- {
- char *mapping;
- int direction = 1;
- bool ignore_binding = false;
- if (binding_element == SDL_GAMEPAD_ELEMENT_INVALID) {
- return;
- }
- if (controller->mapping) {
- mapping = SDL_strdup(controller->mapping);
- } else {
- mapping = NULL;
- }
- /* If the controller generates multiple events for a single element, pick the best one */
- if (!force && binding_advance_time) {
- char *current = GetElementBinding(mapping, binding_element);
- bool native_button = (binding_element < SDL_GAMEPAD_BUTTON_COUNT);
- bool native_axis = (binding_element >= SDL_GAMEPAD_BUTTON_COUNT &&
- binding_element <= SDL_GAMEPAD_ELEMENT_AXIS_MAX);
- bool native_trigger = (binding_element == SDL_GAMEPAD_ELEMENT_AXIS_LEFT_TRIGGER ||
- binding_element == SDL_GAMEPAD_ELEMENT_AXIS_RIGHT_TRIGGER);
- bool native_dpad = (binding_element == SDL_GAMEPAD_BUTTON_DPAD_UP ||
- binding_element == SDL_GAMEPAD_BUTTON_DPAD_DOWN ||
- binding_element == SDL_GAMEPAD_BUTTON_DPAD_LEFT ||
- binding_element == SDL_GAMEPAD_BUTTON_DPAD_RIGHT);
- if (native_button) {
- bool current_button = (current && *current == 'b');
- bool proposed_button = (binding && *binding == 'b');
- if (current_button && !proposed_button) {
- ignore_binding = true;
- }
- /* Use the lower index button (we map from lower to higher button index) */
- if (current_button && proposed_button && current[1] < binding[1]) {
- ignore_binding = true;
- }
- }
- if (native_axis) {
- AxisInfo current_axis_info = { 0, 0 };
- AxisInfo proposed_axis_info = { 0, 0 };
- bool current_axis = ParseAxisInfo(current, ¤t_axis_info);
- bool proposed_axis = ParseAxisInfo(binding, &proposed_axis_info);
- if (current_axis) {
- /* Ignore this unless the proposed binding extends the existing axis */
- ignore_binding = true;
- if (native_trigger &&
- ((*current == '-' && *binding == '+' &&
- SDL_strcmp(current + 1, binding + 1) == 0) ||
- (*current == '+' && *binding == '-' &&
- SDL_strcmp(current + 1, binding + 1) == 0))) {
- /* Merge two half axes into a whole axis for a trigger */
- ++binding;
- ignore_binding = false;
- }
- /* Use the lower index axis (we map from lower to higher axis index) */
- if (proposed_axis && proposed_axis_info.axis < current_axis_info.axis) {
- ignore_binding = false;
- }
- }
- }
- if (native_dpad) {
- bool current_hat = (current && *current == 'h');
- bool proposed_hat = (binding && *binding == 'h');
- if (current_hat && !proposed_hat) {
- ignore_binding = true;
- }
- /* Use the lower index hat (we map from lower to higher hat index) */
- if (current_hat && proposed_hat && current[1] < binding[1]) {
- ignore_binding = true;
- }
- }
- SDL_free(current);
- }
- if (!ignore_binding && binding_flow && !force) {
- int existing = GetElementForBinding(mapping, binding);
- if (existing != SDL_GAMEPAD_ELEMENT_INVALID) {
- SDL_GamepadButton action_forward = SDL_GAMEPAD_BUTTON_SOUTH;
- SDL_GamepadButton action_backward = SDL_GAMEPAD_BUTTON_EAST;
- SDL_GamepadButton action_delete = SDL_GAMEPAD_BUTTON_WEST;
- if (binding_element == action_forward) {
- /* Bind it! */
- } else if (binding_element == action_backward) {
- if (existing == action_forward) {
- bool bound_backward = MappingHasElement(controller->mapping, action_backward);
- if (bound_backward) {
- /* Just move on to the next one */
- ignore_binding = true;
- SetNextBindingElement();
- } else {
- /* You can't skip the backward action, go back and start over */
- ignore_binding = true;
- SetPrevBindingElement();
- }
- } else if (existing == action_backward && binding_flow_direction == -1) {
- /* Keep going backwards */
- ignore_binding = true;
- SetPrevBindingElement();
- } else {
- /* Bind it! */
- }
- } else if (existing == action_forward) {
- /* Just move on to the next one */
- ignore_binding = true;
- SetNextBindingElement();
- } else if (existing == action_backward) {
- ignore_binding = true;
- SetPrevBindingElement();
- } else if (existing == binding_element) {
- /* We're rebinding the same thing, just move to the next one */
- ignore_binding = true;
- SetNextBindingElement();
- } else if (existing == action_delete) {
- /* Clear the current binding and move to the next one */
- binding = NULL;
- direction = 1;
- force = true;
- } else if (binding_element != action_forward &&
- binding_element != action_backward) {
- /* Actually, we'll just clear the existing binding */
- /*ignore_binding = true;*/
- }
- }
- }
- if (ignore_binding) {
- SDL_free(mapping);
- return;
- }
- mapping = ClearMappingBinding(mapping, binding);
- mapping = SetElementBinding(mapping, binding_element, binding);
- SetAndFreeGamepadMapping(mapping);
- if (force) {
- if (binding_flow) {
- if (direction > 0) {
- SetNextBindingElement();
- } else if (direction < 0) {
- SetPrevBindingElement();
- }
- } else {
- StopBinding();
- }
- } else {
- /* Wait to see if any more bindings come in */
- binding_advance_time = SDL_GetTicks() + 30;
- }
- }
- static void ClearBinding(void)
- {
- CommitBindingElement(NULL, true);
- }
- static void SetDisplayMode(ControllerDisplayMode mode)
- {
- float x, y;
- SDL_MouseButtonFlags button_state;
- if (mode == CONTROLLER_MODE_BINDING) {
- /* Make a backup of the current mapping */
- if (controller->mapping) {
- backup_mapping = SDL_strdup(controller->mapping);
- }
- mapping_controller = controller->id;
- if (MappingHasBindings(backup_mapping)) {
- SetCurrentBindingElement(SDL_GAMEPAD_ELEMENT_INVALID, false);
- } else {
- SetCurrentBindingElement(SDL_GAMEPAD_BUTTON_SOUTH, true);
- }
- } else {
- if (backup_mapping) {
- SDL_free(backup_mapping);
- backup_mapping = NULL;
- }
- mapping_controller = 0;
- StopBinding();
- }
- display_mode = mode;
- SetGamepadImageDisplayMode(image, mode);
- SetGamepadDisplayDisplayMode(gamepad_elements, mode);
- button_state = SDL_GetMouseState(&x, &y);
- SDL_RenderCoordinatesFromWindow(screen, x, y, &x, &y);
- UpdateButtonHighlights(x, y, button_state ? true : false);
- }
- static void CancelMapping(void)
- {
- SetAndFreeGamepadMapping(backup_mapping);
- backup_mapping = NULL;
- SetDisplayMode(CONTROLLER_MODE_TESTING);
- }
- static void ClearMapping(void)
- {
- SetAndFreeGamepadMapping(NULL);
- SetCurrentBindingElement(SDL_GAMEPAD_ELEMENT_INVALID, false);
- }
- static void CopyMapping(void)
- {
- if (controller && controller->mapping) {
- SDL_SetClipboardText(controller->mapping);
- }
- }
- static void PasteMapping(void)
- {
- if (controller) {
- char *mapping = SDL_GetClipboardText();
- if (MappingHasBindings(mapping)) {
- StopBinding();
- SDL_SetGamepadMapping(controller->id, mapping);
- RefreshControllerName();
- } else {
- /* Not a valid mapping, ignore it */
- }
- SDL_free(mapping);
- }
- }
- static void CommitControllerName(void)
- {
- char *mapping = NULL;
- if (controller->mapping) {
- mapping = SDL_strdup(controller->mapping);
- } else {
- mapping = NULL;
- }
- mapping = SetMappingName(mapping, controller_name);
- SetAndFreeGamepadMapping(mapping);
- }
- static void AddControllerNameText(const char *text)
- {
- size_t current_length = (controller_name ? SDL_strlen(controller_name) : 0);
- size_t text_length = SDL_strlen(text);
- size_t size = current_length + text_length + 1;
- char *name = (char *)SDL_realloc(controller_name, size);
- if (name) {
- SDL_memcpy(&name[current_length], text, text_length + 1);
- controller_name = name;
- }
- CommitControllerName();
- }
- static void BackspaceControllerName(void)
- {
- size_t length = (controller_name ? SDL_strlen(controller_name) : 0);
- if (length > 0) {
- controller_name[length - 1] = '\0';
- }
- CommitControllerName();
- }
- static void ClearControllerName(void)
- {
- if (controller_name) {
- *controller_name = '\0';
- }
- CommitControllerName();
- }
- static void CopyControllerName(void)
- {
- SDL_SetClipboardText(controller_name);
- }
- static void PasteControllerName(void)
- {
- SDL_free(controller_name);
- controller_name = SDL_GetClipboardText();
- CommitControllerName();
- }
- static void CommitGamepadType(SDL_GamepadType type)
- {
- char *mapping = NULL;
- if (controller->mapping) {
- mapping = SDL_strdup(controller->mapping);
- } else {
- mapping = NULL;
- }
- mapping = SetMappingType(mapping, type);
- SetAndFreeGamepadMapping(mapping);
- }
- static const char *GetBindingInstruction(void)
- {
- switch (binding_element) {
- case SDL_GAMEPAD_ELEMENT_INVALID:
- return "Select an element to bind from the list on the left";
- case SDL_GAMEPAD_BUTTON_SOUTH:
- case SDL_GAMEPAD_BUTTON_EAST:
- case SDL_GAMEPAD_BUTTON_WEST:
- case SDL_GAMEPAD_BUTTON_NORTH:
- switch (SDL_GetGamepadButtonLabelForType(GetGamepadImageType(image), (SDL_GamepadButton)binding_element)) {
- case SDL_GAMEPAD_BUTTON_LABEL_A:
- return "Press the A button";
- case SDL_GAMEPAD_BUTTON_LABEL_B:
- return "Press the B button";
- case SDL_GAMEPAD_BUTTON_LABEL_X:
- return "Press the X button";
- case SDL_GAMEPAD_BUTTON_LABEL_Y:
- return "Press the Y button";
- case SDL_GAMEPAD_BUTTON_LABEL_CROSS:
- return "Press the Cross (X) button";
- case SDL_GAMEPAD_BUTTON_LABEL_CIRCLE:
- return "Press the Circle button";
- case SDL_GAMEPAD_BUTTON_LABEL_SQUARE:
- return "Press the Square button";
- case SDL_GAMEPAD_BUTTON_LABEL_TRIANGLE:
- return "Press the Triangle button";
- default:
- return "";
- }
- break;
- case SDL_GAMEPAD_BUTTON_BACK:
- return "Press the left center button (Back/View/Share)";
- case SDL_GAMEPAD_BUTTON_GUIDE:
- return "Press the center button (Home/Guide)";
- case SDL_GAMEPAD_BUTTON_START:
- return "Press the right center button (Start/Menu/Options)";
- case SDL_GAMEPAD_BUTTON_LEFT_STICK:
- return "Press the left thumbstick button (LSB/L3)";
- case SDL_GAMEPAD_BUTTON_RIGHT_STICK:
- return "Press the right thumbstick button (RSB/R3)";
- case SDL_GAMEPAD_BUTTON_LEFT_SHOULDER:
- return "Press the left shoulder button (LB/L1)";
- case SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER:
- return "Press the right shoulder button (RB/R1)";
- case SDL_GAMEPAD_BUTTON_DPAD_UP:
- return "Press the D-Pad up";
- case SDL_GAMEPAD_BUTTON_DPAD_DOWN:
- return "Press the D-Pad down";
- case SDL_GAMEPAD_BUTTON_DPAD_LEFT:
- return "Press the D-Pad left";
- case SDL_GAMEPAD_BUTTON_DPAD_RIGHT:
- return "Press the D-Pad right";
- case SDL_GAMEPAD_BUTTON_MISC1:
- return "Press the bottom center button (Share/Capture)";
- case SDL_GAMEPAD_BUTTON_RIGHT_PADDLE1:
- return "Press the upper paddle under your right hand";
- case SDL_GAMEPAD_BUTTON_LEFT_PADDLE1:
- return "Press the upper paddle under your left hand";
- case SDL_GAMEPAD_BUTTON_RIGHT_PADDLE2:
- return "Press the lower paddle under your right hand";
- case SDL_GAMEPAD_BUTTON_LEFT_PADDLE2:
- return "Press the lower paddle under your left hand";
- case SDL_GAMEPAD_BUTTON_TOUCHPAD:
- return "Press down on the touchpad";
- case SDL_GAMEPAD_BUTTON_MISC2:
- case SDL_GAMEPAD_BUTTON_MISC3:
- case SDL_GAMEPAD_BUTTON_MISC4:
- case SDL_GAMEPAD_BUTTON_MISC5:
- case SDL_GAMEPAD_BUTTON_MISC6:
- return "Press any additional button not already bound";
- case SDL_GAMEPAD_ELEMENT_AXIS_LEFTX_NEGATIVE:
- return "Move the left thumbstick to the left";
- case SDL_GAMEPAD_ELEMENT_AXIS_LEFTX_POSITIVE:
- return "Move the left thumbstick to the right";
- case SDL_GAMEPAD_ELEMENT_AXIS_LEFTY_NEGATIVE:
- return "Move the left thumbstick up";
- case SDL_GAMEPAD_ELEMENT_AXIS_LEFTY_POSITIVE:
- return "Move the left thumbstick down";
- case SDL_GAMEPAD_ELEMENT_AXIS_RIGHTX_NEGATIVE:
- return "Move the right thumbstick to the left";
- case SDL_GAMEPAD_ELEMENT_AXIS_RIGHTX_POSITIVE:
- return "Move the right thumbstick to the right";
- case SDL_GAMEPAD_ELEMENT_AXIS_RIGHTY_NEGATIVE:
- return "Move the right thumbstick up";
- case SDL_GAMEPAD_ELEMENT_AXIS_RIGHTY_POSITIVE:
- return "Move the right thumbstick down";
- case SDL_GAMEPAD_ELEMENT_AXIS_LEFT_TRIGGER:
- return "Pull the left trigger (LT/L2)";
- case SDL_GAMEPAD_ELEMENT_AXIS_RIGHT_TRIGGER:
- return "Pull the right trigger (RT/R2)";
- case SDL_GAMEPAD_ELEMENT_NAME:
- return "Type the name of your controller";
- case SDL_GAMEPAD_ELEMENT_TYPE:
- return "Select the type of your controller";
- default:
- return "";
- }
- }
- static int FindController(SDL_JoystickID id)
- {
- int i;
- for (i = 0; i < num_controllers; ++i) {
- if (id == controllers[i].id) {
- return i;
- }
- }
- return -1;
- }
- static void SetController(SDL_JoystickID id)
- {
- int i = FindController(id);
- if (i < 0 && num_controllers > 0) {
- i = 0;
- }
- if (i >= 0) {
- controller = &controllers[i];
- } else {
- controller = NULL;
- }
- RefreshControllerName();
- }
- static void AddController(SDL_JoystickID id, bool verbose)
- {
- Controller *new_controllers;
- Controller *new_controller;
- SDL_Joystick *joystick;
- if (FindController(id) >= 0) {
- /* We already have this controller */
- return;
- }
- new_controllers = (Controller *)SDL_realloc(controllers, (num_controllers + 1) * sizeof(*controllers));
- if (!new_controllers) {
- return;
- }
- controller = NULL;
- controllers = new_controllers;
- new_controller = &new_controllers[num_controllers++];
- SDL_zerop(new_controller);
- new_controller->id = id;
- new_controller->joystick = SDL_OpenJoystick(id);
- if (new_controller->joystick) {
- new_controller->num_axes = SDL_GetNumJoystickAxes(new_controller->joystick);
- new_controller->axis_state = (AxisState *)SDL_calloc(new_controller->num_axes, sizeof(*new_controller->axis_state));
- new_controller->imu_state = (IMUState *)SDL_calloc(1, sizeof(*new_controller->imu_state));
- ResetIMUState(new_controller->imu_state);
- }
- joystick = new_controller->joystick;
- if (joystick) {
- if (verbose && !SDL_IsGamepad(id)) {
- const char *name = SDL_GetJoystickName(joystick);
- const char *path = SDL_GetJoystickPath(joystick);
- char guid[33];
- SDL_Log("Opened joystick %s%s%s", name, path ? ", " : "", path ? path : "");
- SDL_GUIDToString(SDL_GetJoystickGUID(joystick), guid, sizeof(guid));
- SDL_Log("No gamepad mapping for %s", guid);
- }
- } else {
- SDL_Log("Couldn't open joystick: %s", SDL_GetError());
- }
- if (mapping_controller) {
- SetController(mapping_controller);
- } else {
- SetController(id);
- }
- }
- static void DelController(SDL_JoystickID id)
- {
- int i = FindController(id);
- if (i < 0) {
- return;
- }
- if (display_mode == CONTROLLER_MODE_BINDING && id == controller->id) {
- SetDisplayMode(CONTROLLER_MODE_TESTING);
- }
- /* Reset trigger state */
- if (controllers[i].trigger_effect != 0) {
- controllers[i].trigger_effect = -1;
- CyclePS5TriggerEffect(&controllers[i]);
- }
- SDL_assert(controllers[i].gamepad == NULL);
- if (controllers[i].axis_state) {
- SDL_free(controllers[i].axis_state);
- }
- if (controllers[i].imu_state) {
- SDL_free(controllers[i].imu_state);
- }
- if (controllers[i].joystick) {
- SDL_CloseJoystick(controllers[i].joystick);
- }
- --num_controllers;
- if (i < num_controllers) {
- SDL_memcpy(&controllers[i], &controllers[i + 1], (num_controllers - i) * sizeof(*controllers));
- }
- if (mapping_controller) {
- SetController(mapping_controller);
- } else {
- SetController(id);
- }
- }
- static void HandleGamepadRemapped(SDL_JoystickID id)
- {
- char *mapping;
- int i = FindController(id);
- SDL_assert(i >= 0);
- if (i < 0) {
- return;
- }
- if (!controllers[i].gamepad) {
- /* Failed to open this controller */
- return;
- }
- /* Get the current mapping */
- mapping = SDL_GetGamepadMapping(controllers[i].gamepad);
- /* Make sure the mapping has a valid name */
- if (mapping && !MappingHasName(mapping)) {
- mapping = SetMappingName(mapping, SDL_GetJoystickName(controllers[i].joystick));
- }
- SDL_free(controllers[i].mapping);
- controllers[i].mapping = mapping;
- controllers[i].has_bindings = MappingHasBindings(mapping);
- }
- static void HandleGamepadAdded(SDL_JoystickID id, bool verbose)
- {
- SDL_Gamepad *gamepad;
- Uint16 firmware_version;
- SDL_SensorType sensors[] = {
- SDL_SENSOR_ACCEL,
- SDL_SENSOR_GYRO,
- SDL_SENSOR_ACCEL_L,
- SDL_SENSOR_GYRO_L,
- SDL_SENSOR_ACCEL_R,
- SDL_SENSOR_GYRO_R
- };
- int i;
- i = FindController(id);
- if (i < 0) {
- return;
- }
- SDL_Log("Gamepad %" SDL_PRIu32 " added", id);
- SDL_assert(!controllers[i].gamepad);
- controllers[i].gamepad = SDL_OpenGamepad(id);
- gamepad = controllers[i].gamepad;
- if (gamepad) {
- if (verbose) {
- SDL_PropertiesID props = SDL_GetGamepadProperties(gamepad);
- const char *name = SDL_GetGamepadName(gamepad);
- const char *path = SDL_GetGamepadPath(gamepad);
- SDL_GUID guid = SDL_GetGamepadGUIDForID(id);
- char guid_string[33];
- SDL_GUIDToString(guid, guid_string, sizeof(guid_string));
- SDL_Log("Opened gamepad %s, guid %s%s%s", name, guid_string, path ? ", " : "", path ? path : "");
- firmware_version = SDL_GetGamepadFirmwareVersion(gamepad);
- if (firmware_version) {
- SDL_Log("Firmware version: 0x%x (%d)", firmware_version, firmware_version);
- }
- if (SDL_GetBooleanProperty(props, SDL_PROP_GAMEPAD_CAP_PLAYER_LED_BOOLEAN, false)) {
- SDL_Log("Has player LED");
- }
- if (SDL_GetBooleanProperty(props, SDL_PROP_GAMEPAD_CAP_RUMBLE_BOOLEAN, false)) {
- SDL_Log("Rumble supported");
- }
- if (SDL_GetBooleanProperty(props, SDL_PROP_GAMEPAD_CAP_TRIGGER_RUMBLE_BOOLEAN, false)) {
- SDL_Log("Trigger rumble supported");
- }
- if (SDL_GetGamepadPlayerIndex(gamepad) >= 0) {
- SDL_Log("Player index: %d", SDL_GetGamepadPlayerIndex(gamepad));
- }
- switch (SDL_GetJoystickTypeForID(id)) {
- case SDL_JOYSTICK_TYPE_WHEEL:
- SDL_Log("Controller is a wheel");
- break;
- case SDL_JOYSTICK_TYPE_ARCADE_STICK:
- SDL_Log("Controller is an arcade stick");
- break;
- case SDL_JOYSTICK_TYPE_FLIGHT_STICK:
- SDL_Log("Controller is a flight stick");
- break;
- case SDL_JOYSTICK_TYPE_DANCE_PAD:
- SDL_Log("Controller is a dance pad");
- break;
- case SDL_JOYSTICK_TYPE_GUITAR:
- SDL_Log("Controller is a guitar");
- break;
- case SDL_JOYSTICK_TYPE_DRUM_KIT:
- SDL_Log("Controller is a drum kit");
- break;
- case SDL_JOYSTICK_TYPE_ARCADE_PAD:
- SDL_Log("Controller is an arcade pad");
- break;
- case SDL_JOYSTICK_TYPE_THROTTLE:
- SDL_Log("Controller is a throttle");
- break;
- default:
- break;
- }
- }
- for (i = 0; i < SDL_arraysize(sensors); ++i) {
- SDL_SensorType sensor = sensors[i];
- if (SDL_GamepadHasSensor(gamepad, sensor)) {
- if (verbose) {
- SDL_Log("Enabling %s at %.2f Hz", GetSensorName(sensor), SDL_GetGamepadSensorDataRate(gamepad, sensor));
- }
- SDL_SetGamepadSensorEnabled(gamepad, sensor, true);
- }
- }
- if (verbose) {
- char *mapping = SDL_GetGamepadMapping(gamepad);
- if (mapping) {
- SDL_Log("Mapping: %s", mapping);
- SDL_free(mapping);
- }
- }
- } else {
- SDL_Log("Couldn't open gamepad: %s", SDL_GetError());
- }
- HandleGamepadRemapped(id);
- SetController(id);
- }
- static void HandleGamepadRemoved(SDL_JoystickID id)
- {
- int i = FindController(id);
- SDL_assert(i >= 0);
- if (i < 0) {
- return;
- }
- SDL_Log("Gamepad %" SDL_PRIu32 " removed", id);
- if (controllers[i].mapping) {
- SDL_free(controllers[i].mapping);
- controllers[i].mapping = NULL;
- }
- if (controllers[i].gamepad) {
- SDL_CloseGamepad(controllers[i].gamepad);
- controllers[i].gamepad = NULL;
- }
- }
- static void HandleGamepadAccelerometerEvent(SDL_Event *event)
- {
- controller->imu_state->accelerometer_packet_number++;
- SDL_memcpy(controller->imu_state->accel_data, event->gsensor.data, sizeof(controller->imu_state->accel_data));
- }
- static void HandleGamepadGyroEvent(SDL_Event *event)
- {
- controller->imu_state->gyro_packet_number++;
- SDL_memcpy(controller->imu_state->gyro_data, event->gsensor.data, sizeof(controller->imu_state->gyro_data));
- }
- /* Two strategies for evaluating polling rate - one based on a fixed packet count, and one using a fixed time window.
- * Smaller values in either will give you a more responsive polling rate estimate, but this may fluctuate more.
- * Larger values in either will give you a more stable average but they will require more time to evaluate.
- * Generally, wired connections tend to give much more stable
- */
- /* #define SDL_USE_FIXED_PACKET_COUNT_FOR_ESTIMATION */
- #define SDL_GAMEPAD_IMU_MIN_POLLING_RATE_ESTIMATION_COUNT 2048
- #define SDL_GAMEPAD_IMU_MIN_POLLING_RATE_ESTIMATION_TIME_NS (SDL_NS_PER_SECOND * 2)
- static void EstimatePacketRate()
- {
- Uint64 now_ns = SDL_GetTicksNS();
- if (controller->imu_state->imu_packet_counter == 0) {
- controller->imu_state->starting_time_stamp_ns = now_ns;
- }
- /* Require a significant sample size before averaging rate. */
- #ifdef SDL_USE_FIXED_PACKET_COUNT_FOR_ESTIMATION
- if (controller->imu_state->imu_packet_counter >= SDL_GAMEPAD_IMU_MIN_POLLING_RATE_ESTIMATION_COUNT) {
- Uint64 deltatime_ns = now_ns - controller->imu_state->starting_time_stamp_ns;
- controller->imu_state->imu_estimated_sensor_rate = (Uint16)((controller->imu_state->imu_packet_counter * SDL_NS_PER_SECOND) / deltatime_ns);
- controller->imu_state->imu_packet_counter = 0;
- }
- #else
- Uint64 deltatime_ns = now_ns - controller->imu_state->starting_time_stamp_ns;
- if (deltatime_ns >= SDL_GAMEPAD_IMU_MIN_POLLING_RATE_ESTIMATION_TIME_NS) {
- controller->imu_state->imu_estimated_sensor_rate = (Uint16)((controller->imu_state->imu_packet_counter * SDL_NS_PER_SECOND) / deltatime_ns);
- controller->imu_state->imu_packet_counter = 0;
- }
- #endif
- else {
- ++controller->imu_state->imu_packet_counter;
- }
- }
- static void UpdateGamepadOrientation( Uint64 delta_time_ns )
- {
- if (!controller || !controller->imu_state)
- return;
- SampleGyroPacketForDrift(controller->imu_state);
- ApplyDriftSolution(controller->imu_state->gyro_data, controller->imu_state->gyro_drift_solution);
- UpdateGyroRotation(controller->imu_state, delta_time_ns);
- }
- static void HandleGamepadSensorEvent( SDL_Event* event )
- {
- if (!controller)
- return;
- if (controller->id != event->gsensor.which)
- return;
- if (event->gsensor.sensor == SDL_SENSOR_GYRO) {
- HandleGamepadGyroEvent(event);
- } else if (event->gsensor.sensor == SDL_SENSOR_ACCEL) {
- HandleGamepadAccelerometerEvent(event);
- }
- /*
- This is where we can update the quaternion because we need to have a drift solution, which requires both
- accelerometer and gyro events are received before progressing.
- */
- if ( controller->imu_state->accelerometer_packet_number == controller->imu_state->gyro_packet_number ) {
- EstimatePacketRate();
- Uint64 sensorTimeStampDelta_ns = event->gsensor.sensor_timestamp - controller->imu_state->last_sensor_time_stamp_ns ;
- UpdateGamepadOrientation(sensorTimeStampDelta_ns);
- float display_euler_angles[3];
- QuaternionToYXZ(controller->imu_state->integrated_rotation, &display_euler_angles[0], &display_euler_angles[1], &display_euler_angles[2]);
- /* Show how far we are through the current phase. When off, just default to zero progress */
- Uint64 now = SDL_GetTicksNS();
- Uint64 duration = 0;
- if (controller->imu_state->calibration_phase == GYRO_CALIBRATION_PHASE_NOISE_PROFILING) {
- duration = SDL_GAMEPAD_IMU_NOISE_PROFILING_PHASE_DURATION_NS;
- } else if (controller->imu_state->calibration_phase == GYRO_CALIBRATION_PHASE_DRIFT_PROFILING) {
- duration = SDL_GAMEPAD_IMU_CALIBRATION_PHASE_DURATION_NS;
- }
- Uint64 delta_ns = now - controller->imu_state->calibration_phase_start_time_ticks_ns;
- float drift_calibration_progress_fraction = duration > 0.0f ? ((float)delta_ns / (float)duration) : 0.0f;
- int reported_polling_rate_hz = sensorTimeStampDelta_ns > 0 ? (int)(SDL_NS_PER_SECOND / sensorTimeStampDelta_ns) : 0;
- /* Send the results to the frontend */
- SetGamepadDisplayIMUValues(gyro_elements,
- controller->imu_state->gyro_drift_solution,
- display_euler_angles,
- &controller->imu_state->integrated_rotation,
- reported_polling_rate_hz,
- controller->imu_state->imu_estimated_sensor_rate,
- controller->imu_state->calibration_phase,
- drift_calibration_progress_fraction,
- controller->imu_state->accelerometer_length_squared,
- controller->imu_state->accelerometer_tolerance_squared
- );
- /* 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) */
- SetGamepadDisplayGyroDriftCorrection(gamepad_elements, controller->imu_state->gyro_drift_solution);
- controller->imu_state->last_sensor_time_stamp_ns = event->gsensor.sensor_timestamp;
- }
- }
- static Uint16 ConvertAxisToRumble(Sint16 axisval)
- {
- /* Only start rumbling if the axis is past the halfway point */
- const Sint16 half_axis = (Sint16)SDL_ceil(SDL_JOYSTICK_AXIS_MAX / 2.0f);
- if (axisval > half_axis) {
- return (Uint16)(axisval - half_axis) * 4;
- } else {
- return 0;
- }
- }
- static bool ShowingFront(void)
- {
- bool showing_front = true;
- int i;
- /* Show the back of the gamepad if the paddles are being held or bound */
- for (i = SDL_GAMEPAD_BUTTON_RIGHT_PADDLE1; i <= SDL_GAMEPAD_BUTTON_LEFT_PADDLE2; ++i) {
- if (SDL_GetGamepadButton(controller->gamepad, (SDL_GamepadButton)i) ||
- binding_element == i) {
- showing_front = false;
- break;
- }
- }
- if ((SDL_GetModState() & SDL_KMOD_SHIFT) && binding_element != SDL_GAMEPAD_ELEMENT_NAME) {
- showing_front = false;
- }
- return showing_front;
- }
- static void SDLCALL VirtualGamepadSetPlayerIndex(void *userdata, int player_index)
- {
- SDL_Log("Virtual Gamepad: player index set to %d", player_index);
- }
- static bool SDLCALL VirtualGamepadRumble(void *userdata, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)
- {
- SDL_Log("Virtual Gamepad: rumble set to %d/%d", low_frequency_rumble, high_frequency_rumble);
- return true;
- }
- static bool SDLCALL VirtualGamepadRumbleTriggers(void *userdata, Uint16 left_rumble, Uint16 right_rumble)
- {
- SDL_Log("Virtual Gamepad: trigger rumble set to %d/%d", left_rumble, right_rumble);
- return true;
- }
- static bool SDLCALL VirtualGamepadSetLED(void *userdata, Uint8 red, Uint8 green, Uint8 blue)
- {
- SDL_Log("Virtual Gamepad: LED set to RGB %d,%d,%d", red, green, blue);
- return true;
- }
- static void OpenVirtualGamepad(void)
- {
- SDL_VirtualJoystickTouchpadDesc virtual_touchpad = { 1, { 0, 0, 0 } };
- SDL_VirtualJoystickSensorDesc virtual_sensor = { SDL_SENSOR_ACCEL, 0.0f };
- SDL_VirtualJoystickDesc desc;
- SDL_JoystickID virtual_id;
- if (virtual_joystick) {
- return;
- }
- SDL_INIT_INTERFACE(&desc);
- desc.type = SDL_JOYSTICK_TYPE_GAMEPAD;
- desc.naxes = SDL_GAMEPAD_AXIS_COUNT;
- desc.nbuttons = SDL_GAMEPAD_BUTTON_COUNT;
- desc.ntouchpads = 1;
- desc.touchpads = &virtual_touchpad;
- desc.nsensors = 1;
- desc.sensors = &virtual_sensor;
- desc.SetPlayerIndex = VirtualGamepadSetPlayerIndex;
- desc.Rumble = VirtualGamepadRumble;
- desc.RumbleTriggers = VirtualGamepadRumbleTriggers;
- desc.SetLED = VirtualGamepadSetLED;
- virtual_id = SDL_AttachVirtualJoystick(&desc);
- if (virtual_id == 0) {
- SDL_Log("Couldn't attach virtual device: %s", SDL_GetError());
- } else {
- virtual_joystick = SDL_OpenJoystick(virtual_id);
- if (!virtual_joystick) {
- SDL_Log("Couldn't open virtual device: %s", SDL_GetError());
- }
- }
- }
- static void CloseVirtualGamepad(void)
- {
- int i;
- SDL_JoystickID *joysticks = SDL_GetJoysticks(NULL);
- if (joysticks) {
- for (i = 0; joysticks[i]; ++i) {
- SDL_JoystickID instance_id = joysticks[i];
- if (SDL_IsJoystickVirtual(instance_id)) {
- SDL_DetachVirtualJoystick(instance_id);
- }
- }
- SDL_free(joysticks);
- }
- if (virtual_joystick) {
- SDL_CloseJoystick(virtual_joystick);
- virtual_joystick = NULL;
- }
- }
- static void VirtualGamepadMouseMotion(float x, float y)
- {
- if (virtual_button_active != SDL_GAMEPAD_BUTTON_INVALID) {
- if (virtual_axis_active != SDL_GAMEPAD_AXIS_INVALID) {
- const float MOVING_DISTANCE = 2.0f;
- if (SDL_fabs(x - virtual_axis_start_x) >= MOVING_DISTANCE ||
- SDL_fabs(y - virtual_axis_start_y) >= MOVING_DISTANCE) {
- SDL_SetJoystickVirtualButton(virtual_joystick, virtual_button_active, false);
- virtual_button_active = SDL_GAMEPAD_BUTTON_INVALID;
- }
- }
- }
- if (virtual_axis_active != SDL_GAMEPAD_AXIS_INVALID) {
- if (virtual_axis_active == SDL_GAMEPAD_AXIS_LEFT_TRIGGER ||
- virtual_axis_active == SDL_GAMEPAD_AXIS_RIGHT_TRIGGER) {
- int range = (SDL_JOYSTICK_AXIS_MAX - SDL_JOYSTICK_AXIS_MIN);
- float distance = SDL_clamp((y - virtual_axis_start_y) / GetGamepadImageAxisHeight(image), 0.0f, 1.0f);
- Sint16 value = (Sint16)(SDL_JOYSTICK_AXIS_MIN + (distance * range));
- SDL_SetJoystickVirtualAxis(virtual_joystick, virtual_axis_active, value);
- } else {
- float distanceX = SDL_clamp((x - virtual_axis_start_x) / GetGamepadImageAxisWidth(image), -1.0f, 1.0f);
- float distanceY = SDL_clamp((y - virtual_axis_start_y) / GetGamepadImageAxisHeight(image), -1.0f, 1.0f);
- Sint16 valueX, valueY;
- if (distanceX >= 0) {
- valueX = (Sint16)(distanceX * SDL_JOYSTICK_AXIS_MAX);
- } else {
- valueX = (Sint16)(distanceX * -SDL_JOYSTICK_AXIS_MIN);
- }
- if (distanceY >= 0) {
- valueY = (Sint16)(distanceY * SDL_JOYSTICK_AXIS_MAX);
- } else {
- valueY = (Sint16)(distanceY * -SDL_JOYSTICK_AXIS_MIN);
- }
- SDL_SetJoystickVirtualAxis(virtual_joystick, virtual_axis_active, valueX);
- SDL_SetJoystickVirtualAxis(virtual_joystick, virtual_axis_active + 1, valueY);
- }
- }
- if (virtual_touchpad_active) {
- SDL_FRect touchpad;
- GetGamepadTouchpadArea(image, &touchpad);
- virtual_touchpad_x = (x - touchpad.x) / touchpad.w;
- virtual_touchpad_y = (y - touchpad.y) / touchpad.h;
- SDL_SetJoystickVirtualTouchpad(virtual_joystick, 0, 0, true, virtual_touchpad_x, virtual_touchpad_y, 1.0f);
- }
- }
- static void VirtualGamepadMouseDown(float x, float y)
- {
- int element = GetGamepadImageElementAt(image, x, y);
- if (element == SDL_GAMEPAD_ELEMENT_INVALID) {
- SDL_FPoint point;
- point.x = x;
- point.y = y;
- SDL_FRect touchpad;
- GetGamepadTouchpadArea(image, &touchpad);
- if (SDL_PointInRectFloat(&point, &touchpad)) {
- virtual_touchpad_active = true;
- virtual_touchpad_x = (x - touchpad.x) / touchpad.w;
- virtual_touchpad_y = (y - touchpad.y) / touchpad.h;
- SDL_SetJoystickVirtualTouchpad(virtual_joystick, 0, 0, true, virtual_touchpad_x, virtual_touchpad_y, 1.0f);
- }
- return;
- }
- if (element < SDL_GAMEPAD_BUTTON_COUNT) {
- virtual_button_active = (SDL_GamepadButton)element;
- SDL_SetJoystickVirtualButton(virtual_joystick, virtual_button_active, true);
- } else {
- switch (element) {
- case SDL_GAMEPAD_ELEMENT_AXIS_LEFTX_NEGATIVE:
- case SDL_GAMEPAD_ELEMENT_AXIS_LEFTX_POSITIVE:
- case SDL_GAMEPAD_ELEMENT_AXIS_LEFTY_NEGATIVE:
- case SDL_GAMEPAD_ELEMENT_AXIS_LEFTY_POSITIVE:
- virtual_axis_active = SDL_GAMEPAD_AXIS_LEFTX;
- break;
- case SDL_GAMEPAD_ELEMENT_AXIS_RIGHTX_NEGATIVE:
- case SDL_GAMEPAD_ELEMENT_AXIS_RIGHTX_POSITIVE:
- case SDL_GAMEPAD_ELEMENT_AXIS_RIGHTY_NEGATIVE:
- case SDL_GAMEPAD_ELEMENT_AXIS_RIGHTY_POSITIVE:
- virtual_axis_active = SDL_GAMEPAD_AXIS_RIGHTX;
- break;
- case SDL_GAMEPAD_ELEMENT_AXIS_LEFT_TRIGGER:
- virtual_axis_active = SDL_GAMEPAD_AXIS_LEFT_TRIGGER;
- break;
- case SDL_GAMEPAD_ELEMENT_AXIS_RIGHT_TRIGGER:
- virtual_axis_active = SDL_GAMEPAD_AXIS_RIGHT_TRIGGER;
- break;
- }
- virtual_axis_start_x = x;
- virtual_axis_start_y = y;
- }
- }
- static void VirtualGamepadMouseUp(float x, float y)
- {
- if (virtual_button_active != SDL_GAMEPAD_BUTTON_INVALID) {
- SDL_SetJoystickVirtualButton(virtual_joystick, virtual_button_active, false);
- virtual_button_active = SDL_GAMEPAD_BUTTON_INVALID;
- }
- if (virtual_axis_active != SDL_GAMEPAD_AXIS_INVALID) {
- if (virtual_axis_active == SDL_GAMEPAD_AXIS_LEFT_TRIGGER ||
- virtual_axis_active == SDL_GAMEPAD_AXIS_RIGHT_TRIGGER) {
- SDL_SetJoystickVirtualAxis(virtual_joystick, virtual_axis_active, SDL_JOYSTICK_AXIS_MIN);
- } else {
- SDL_SetJoystickVirtualAxis(virtual_joystick, virtual_axis_active, 0);
- SDL_SetJoystickVirtualAxis(virtual_joystick, virtual_axis_active + 1, 0);
- }
- virtual_axis_active = SDL_GAMEPAD_AXIS_INVALID;
- }
- if (virtual_touchpad_active) {
- SDL_SetJoystickVirtualTouchpad(virtual_joystick, 0, 0, false, virtual_touchpad_x, virtual_touchpad_y, 0.0f);
- virtual_touchpad_active = false;
- }
- }
- static void DrawGamepadWaiting(SDL_Renderer *renderer)
- {
- const char *text = "Waiting for gamepad, press A to add a virtual controller";
- float x, y;
- x = SCREEN_WIDTH / 2 - (FONT_CHARACTER_SIZE * SDL_strlen(text)) / 2;
- y = TITLE_HEIGHT / 2 - FONT_CHARACTER_SIZE / 2;
- SDLTest_DrawString(renderer, x, y, text);
- }
- static void DrawGamepadInfo(SDL_Renderer *renderer)
- {
- const char *type;
- const char *serial;
- char text[128];
- float x, y;
- if (title_highlighted) {
- Uint8 r, g, b, a;
- SDL_GetRenderDrawColor(renderer, &r, &g, &b, &a);
- if (title_pressed) {
- SDL_SetRenderDrawColor(renderer, PRESSED_COLOR);
- } else {
- SDL_SetRenderDrawColor(renderer, HIGHLIGHT_COLOR);
- }
- SDL_RenderFillRect(renderer, &title_area);
- SDL_SetRenderDrawColor(renderer, r, g, b, a);
- }
- if (type_highlighted) {
- Uint8 r, g, b, a;
- SDL_GetRenderDrawColor(renderer, &r, &g, &b, &a);
- if (type_pressed) {
- SDL_SetRenderDrawColor(renderer, PRESSED_COLOR);
- } else {
- SDL_SetRenderDrawColor(renderer, HIGHLIGHT_COLOR);
- }
- SDL_RenderFillRect(renderer, &type_area);
- SDL_SetRenderDrawColor(renderer, r, g, b, a);
- }
- if (controller->joystick) {
- SDL_snprintf(text, sizeof(text), "(%" SDL_PRIu32 ")", SDL_GetJoystickID(controller->joystick));
- x = SCREEN_WIDTH - (FONT_CHARACTER_SIZE * SDL_strlen(text)) - 8.0f;
- y = 8.0f;
- SDLTest_DrawString(renderer, x, y, text);
- }
- if (controller_name && *controller_name) {
- x = title_area.x + title_area.w / 2 - (FONT_CHARACTER_SIZE * SDL_strlen(controller_name)) / 2;
- y = title_area.y + title_area.h / 2 - FONT_CHARACTER_SIZE / 2;
- SDLTest_DrawString(renderer, x, y, controller_name);
- }
- if (SDL_IsJoystickVirtual(controller->id)) {
- SDL_strlcpy(text, "Click on the gamepad image below to generate input", sizeof(text));
- x = SCREEN_WIDTH / 2 - (FONT_CHARACTER_SIZE * SDL_strlen(text)) / 2;
- y = TITLE_HEIGHT / 2 - FONT_CHARACTER_SIZE / 2 + FONT_LINE_HEIGHT + 2.0f;
- SDLTest_DrawString(renderer, x, y, text);
- }
- type = GetGamepadTypeString(SDL_GetGamepadType(controller->gamepad));
- x = type_area.x + type_area.w / 2 - (FONT_CHARACTER_SIZE * SDL_strlen(type)) / 2;
- y = type_area.y + type_area.h / 2 - FONT_CHARACTER_SIZE / 2;
- SDLTest_DrawString(renderer, x, y, type);
- if (display_mode == CONTROLLER_MODE_TESTING) {
- Uint64 steam_handle = SDL_GetGamepadSteamHandle(controller->gamepad);
- if (steam_handle) {
- SDL_snprintf(text, SDL_arraysize(text), "Steam: 0x%.16" SDL_PRIx64, steam_handle);
- y = SCREEN_HEIGHT - 2 * (8.0f + FONT_LINE_HEIGHT);
- x = SCREEN_WIDTH - 8.0f - (FONT_CHARACTER_SIZE * SDL_strlen(text));
- SDLTest_DrawString(renderer, x, y, text);
- }
- SDL_snprintf(text, SDL_arraysize(text), "VID: 0x%.4x PID: 0x%.4x",
- SDL_GetJoystickVendor(controller->joystick),
- SDL_GetJoystickProduct(controller->joystick));
- y = SCREEN_HEIGHT - 8.0f - FONT_LINE_HEIGHT;
- x = SCREEN_WIDTH - 8.0f - (FONT_CHARACTER_SIZE * SDL_strlen(text));
- SDLTest_DrawString(renderer, x, y, text);
- serial = SDL_GetJoystickSerial(controller->joystick);
- if (serial && *serial) {
- SDL_snprintf(text, SDL_arraysize(text), "Serial: %s", serial);
- x = SCREEN_WIDTH / 2 - (FONT_CHARACTER_SIZE * SDL_strlen(text)) / 2;
- y = SCREEN_HEIGHT - 8.0f - FONT_LINE_HEIGHT;
- SDLTest_DrawString(renderer, x, y, text);
- }
- }
- }
- static const char *GetButtonLabel(SDL_GamepadType type, SDL_GamepadButton button)
- {
- switch (SDL_GetGamepadButtonLabelForType(type, button)) {
- case SDL_GAMEPAD_BUTTON_LABEL_A:
- return "A";
- case SDL_GAMEPAD_BUTTON_LABEL_B:
- return "B";
- case SDL_GAMEPAD_BUTTON_LABEL_X:
- return "X";
- case SDL_GAMEPAD_BUTTON_LABEL_Y:
- return "Y";
- case SDL_GAMEPAD_BUTTON_LABEL_CROSS:
- return "Cross (X)";
- case SDL_GAMEPAD_BUTTON_LABEL_CIRCLE:
- return "Circle";
- case SDL_GAMEPAD_BUTTON_LABEL_SQUARE:
- return "Square";
- case SDL_GAMEPAD_BUTTON_LABEL_TRIANGLE:
- return "Triangle";
- default:
- return "UNKNOWN";
- }
- }
- static void DrawBindingTips(SDL_Renderer *renderer)
- {
- const char *text;
- SDL_FRect image_area, button_area;
- float x, y;
- GetGamepadImageArea(image, &image_area);
- GetGamepadButtonArea(done_mapping_button, &button_area);
- x = image_area.x + image_area.w / 2;
- y = image_area.y + image_area.h;
- y += (button_area.y - y - FONT_CHARACTER_SIZE) / 2;
- text = GetBindingInstruction();
- if (binding_element == SDL_GAMEPAD_ELEMENT_INVALID) {
- SDLTest_DrawString(renderer, x - (FONT_CHARACTER_SIZE * SDL_strlen(text)) / 2, y, text);
- } else {
- Uint8 r, g, b, a;
- SDL_FRect rect;
- SDL_GamepadButton action_forward = SDL_GAMEPAD_BUTTON_SOUTH;
- bool bound_forward = MappingHasElement(controller->mapping, action_forward);
- SDL_GamepadButton action_backward = SDL_GAMEPAD_BUTTON_EAST;
- bool bound_backward = MappingHasElement(controller->mapping, action_backward);
- SDL_GamepadButton action_delete = SDL_GAMEPAD_BUTTON_WEST;
- bool bound_delete = MappingHasElement(controller->mapping, action_delete);
- y -= (FONT_CHARACTER_SIZE + BUTTON_MARGIN) / 2;
- rect.w = 2.0f + (FONT_CHARACTER_SIZE * SDL_strlen(text)) + 2.0f;
- rect.h = 2.0f + FONT_CHARACTER_SIZE + 2.0f;
- rect.x = x - rect.w / 2;
- rect.y = y - 2.0f;
- SDL_GetRenderDrawColor(renderer, &r, &g, &b, &a);
- SDL_SetRenderDrawColor(renderer, SELECTED_COLOR);
- SDL_RenderFillRect(renderer, &rect);
- SDL_SetRenderDrawColor(renderer, r, g, b, a);
- SDLTest_DrawString(renderer, x - (FONT_CHARACTER_SIZE * SDL_strlen(text)) / 2, y, text);
- y += (FONT_CHARACTER_SIZE + BUTTON_MARGIN);
- if (binding_element == SDL_GAMEPAD_ELEMENT_NAME) {
- text = "(press RETURN to complete)";
- } else if (binding_element == SDL_GAMEPAD_ELEMENT_TYPE ||
- binding_element == action_forward ||
- binding_element == action_backward) {
- text = "(press ESC to cancel)";
- } else {
- static char dynamic_text[128];
- SDL_GamepadType type = GetGamepadImageType(image);
- if (binding_flow && bound_forward && bound_backward) {
- if (binding_element != action_delete && bound_delete) {
- SDL_snprintf(dynamic_text, sizeof(dynamic_text), "(press %s to skip, %s to go back, %s to delete, and ESC to cancel)", GetButtonLabel(type, action_forward), GetButtonLabel(type, action_backward), GetButtonLabel(type, action_delete));
- } else {
- SDL_snprintf(dynamic_text, sizeof(dynamic_text), "(press %s to skip, %s to go back, SPACE to delete, and ESC to cancel)", GetButtonLabel(type, action_forward), GetButtonLabel(type, action_backward));
- }
- text = dynamic_text;
- } else {
- text = "(press SPACE to delete and ESC to cancel)";
- }
- }
- SDLTest_DrawString(renderer, x - (FONT_CHARACTER_SIZE * SDL_strlen(text)) / 2, y, text);
- }
- }
- static void UpdateGamepadEffects(void)
- {
- if (display_mode != CONTROLLER_MODE_TESTING || !controller->gamepad) {
- return;
- }
- /* Update LED based on left thumbstick position */
- {
- Sint16 x = SDL_GetGamepadAxis(controller->gamepad, SDL_GAMEPAD_AXIS_LEFTX);
- Sint16 y = SDL_GetGamepadAxis(controller->gamepad, SDL_GAMEPAD_AXIS_LEFTY);
- if (!set_LED) {
- set_LED = (x < -8000 || x > 8000 || y > 8000);
- }
- if (set_LED) {
- Uint8 r, g, b;
- if (x < 0) {
- r = (Uint8)(((~x) * 255) / 32767);
- b = 0;
- } else {
- r = 0;
- b = (Uint8)(((int)(x)*255) / 32767);
- }
- if (y > 0) {
- g = (Uint8)(((int)(y)*255) / 32767);
- } else {
- g = 0;
- }
- SDL_SetGamepadLED(controller->gamepad, r, g, b);
- }
- }
- if (controller->trigger_effect == 0) {
- /* Update rumble based on trigger state */
- {
- Sint16 left = SDL_GetGamepadAxis(controller->gamepad, SDL_GAMEPAD_AXIS_LEFT_TRIGGER);
- Sint16 right = SDL_GetGamepadAxis(controller->gamepad, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER);
- Uint16 low_frequency_rumble = ConvertAxisToRumble(left);
- Uint16 high_frequency_rumble = ConvertAxisToRumble(right);
- SDL_RumbleGamepad(controller->gamepad, low_frequency_rumble, high_frequency_rumble, 250);
- }
- /* Update trigger rumble based on thumbstick state */
- {
- Sint16 left = SDL_GetGamepadAxis(controller->gamepad, SDL_GAMEPAD_AXIS_LEFTY);
- Sint16 right = SDL_GetGamepadAxis(controller->gamepad, SDL_GAMEPAD_AXIS_RIGHTY);
- Uint16 left_rumble = ConvertAxisToRumble(~left);
- Uint16 right_rumble = ConvertAxisToRumble(~right);
- SDL_RumbleGamepadTriggers(controller->gamepad, left_rumble, right_rumble, 250);
- }
- }
- }
- SDL_AppResult SDLCALL SDL_AppEvent(void *appstate, SDL_Event *event)
- {
- SDL_ConvertEventToRenderCoordinates(screen, event);
- switch (event->type) {
- case SDL_EVENT_JOYSTICK_ADDED:
- AddController(event->jdevice.which, true);
- break;
- case SDL_EVENT_JOYSTICK_REMOVED:
- DelController(event->jdevice.which);
- break;
- case SDL_EVENT_JOYSTICK_AXIS_MOTION:
- if (display_mode == CONTROLLER_MODE_TESTING) {
- if (event->jaxis.value <= (-SDL_JOYSTICK_AXIS_MAX / 2) || event->jaxis.value >= (SDL_JOYSTICK_AXIS_MAX / 2)) {
- SetController(event->jaxis.which);
- }
- } else if (display_mode == CONTROLLER_MODE_BINDING &&
- event->jaxis.which == controller->id &&
- event->jaxis.axis < controller->num_axes &&
- binding_element != SDL_GAMEPAD_ELEMENT_INVALID) {
- const int MAX_ALLOWED_JITTER = SDL_JOYSTICK_AXIS_MAX / 80; /* ShanWan PS3 gamepad needed 96 */
- AxisState *pAxisState = &controller->axis_state[event->jaxis.axis];
- int nValue = event->jaxis.value;
- int nCurrentDistance, nFarthestDistance;
- if (!pAxisState->m_bMoving) {
- Sint16 nInitialValue;
- pAxisState->m_bMoving = SDL_GetJoystickAxisInitialState(controller->joystick, event->jaxis.axis, &nInitialValue);
- pAxisState->m_nLastValue = nValue;
- pAxisState->m_nStartingValue = nInitialValue;
- pAxisState->m_nFarthestValue = nInitialValue;
- } else if (SDL_abs(nValue - pAxisState->m_nLastValue) <= MAX_ALLOWED_JITTER) {
- break;
- } else {
- pAxisState->m_nLastValue = nValue;
- }
- nCurrentDistance = SDL_abs(nValue - pAxisState->m_nStartingValue);
- nFarthestDistance = SDL_abs(pAxisState->m_nFarthestValue - pAxisState->m_nStartingValue);
- if (nCurrentDistance > nFarthestDistance) {
- pAxisState->m_nFarthestValue = nValue;
- nFarthestDistance = SDL_abs(pAxisState->m_nFarthestValue - pAxisState->m_nStartingValue);
- }
- #ifdef DEBUG_AXIS_MAPPING
- SDL_Log("AXIS %d nValue %d nCurrentDistance %d nFarthestDistance %d", event->jaxis.axis, nValue, nCurrentDistance, nFarthestDistance);
- #endif
- /* If we've gone out far enough and started to come back, let's bind this axis */
- if (nFarthestDistance >= 16000 && nCurrentDistance <= 10000) {
- char binding[12];
- int axis_min = StandardizeAxisValue(pAxisState->m_nStartingValue);
- int axis_max = StandardizeAxisValue(pAxisState->m_nFarthestValue);
- if (axis_min == 0 && axis_max == SDL_JOYSTICK_AXIS_MIN) {
- /* The negative half axis */
- (void)SDL_snprintf(binding, sizeof(binding), "-a%d", event->jaxis.axis);
- } else if (axis_min == 0 && axis_max == SDL_JOYSTICK_AXIS_MAX) {
- /* The positive half axis */
- (void)SDL_snprintf(binding, sizeof(binding), "+a%d", event->jaxis.axis);
- } else {
- (void)SDL_snprintf(binding, sizeof(binding), "a%d", event->jaxis.axis);
- if (axis_min > axis_max) {
- /* Invert the axis */
- SDL_strlcat(binding, "~", SDL_arraysize(binding));
- }
- }
- #ifdef DEBUG_AXIS_MAPPING
- SDL_Log("AXIS %d axis_min = %d, axis_max = %d, binding = %s", event->jaxis.axis, axis_min, axis_max, binding);
- #endif
- CommitBindingElement(binding, false);
- }
- }
- break;
- case SDL_EVENT_JOYSTICK_BUTTON_DOWN:
- if (display_mode == CONTROLLER_MODE_TESTING) {
- SetController(event->jbutton.which);
- }
- break;
- case SDL_EVENT_JOYSTICK_BUTTON_UP:
- if (display_mode == CONTROLLER_MODE_BINDING &&
- event->jbutton.which == controller->id &&
- binding_element != SDL_GAMEPAD_ELEMENT_INVALID) {
- char binding[12];
- SDL_snprintf(binding, sizeof(binding), "b%d", event->jbutton.button);
- CommitBindingElement(binding, false);
- }
- break;
- case SDL_EVENT_JOYSTICK_HAT_MOTION:
- if (display_mode == CONTROLLER_MODE_BINDING &&
- event->jhat.which == controller->id &&
- event->jhat.value != SDL_HAT_CENTERED &&
- binding_element != SDL_GAMEPAD_ELEMENT_INVALID) {
- char binding[12];
- SDL_snprintf(binding, sizeof(binding), "h%d.%d", event->jhat.hat, event->jhat.value);
- CommitBindingElement(binding, false);
- }
- break;
- case SDL_EVENT_GAMEPAD_ADDED:
- HandleGamepadAdded(event->gdevice.which, true);
- break;
- case SDL_EVENT_GAMEPAD_REMOVED:
- HandleGamepadRemoved(event->gdevice.which);
- break;
- case SDL_EVENT_GAMEPAD_REMAPPED:
- HandleGamepadRemapped(event->gdevice.which);
- break;
- case SDL_EVENT_GAMEPAD_STEAM_HANDLE_UPDATED:
- RefreshControllerName();
- break;
- #ifdef VERBOSE_TOUCHPAD
- case SDL_EVENT_GAMEPAD_TOUCHPAD_DOWN:
- case SDL_EVENT_GAMEPAD_TOUCHPAD_MOTION:
- case SDL_EVENT_GAMEPAD_TOUCHPAD_UP:
- SDL_Log("Gamepad %" SDL_PRIu32 " touchpad %" SDL_PRIs32 " finger %" SDL_PRIs32 " %s %.2f, %.2f, %.2f",
- event->gtouchpad.which,
- event->gtouchpad.touchpad,
- event->gtouchpad.finger,
- (event->type == SDL_EVENT_GAMEPAD_TOUCHPAD_DOWN ? "pressed at" : (event->type == SDL_EVENT_GAMEPAD_TOUCHPAD_UP ? "released at" : "moved to")),
- event->gtouchpad.x,
- event->gtouchpad.y,
- event->gtouchpad.pressure);
- break;
- #endif /* VERBOSE_TOUCHPAD */
- case SDL_EVENT_GAMEPAD_SENSOR_UPDATE:
- #ifdef VERBOSE_SENSORS
- SDL_Log("Gamepad %" SDL_PRIu32 " sensor %s: %.2f, %.2f, %.2f (%" SDL_PRIu64 ")",
- event->gsensor.which,
- GetSensorName((SDL_SensorType) event->gsensor.sensor),
- event->gsensor.data[0],
- event->gsensor.data[1],
- event->gsensor.data[2],
- event->gsensor.sensor_timestamp);
- #endif /* VERBOSE_SENSORS */
- HandleGamepadSensorEvent(event);
- break;
- #ifdef VERBOSE_AXES
- case SDL_EVENT_GAMEPAD_AXIS_MOTION:
- if (display_mode == CONTROLLER_MODE_TESTING) {
- if (event->gaxis.value <= (-SDL_JOYSTICK_AXIS_MAX / 2) || event->gaxis.value >= (SDL_JOYSTICK_AXIS_MAX / 2)) {
- SetController(event->gaxis.which);
- }
- }
- SDL_Log("Gamepad %" SDL_PRIu32 " axis %s changed to %d",
- event->gaxis.which,
- SDL_GetGamepadStringForAxis((SDL_GamepadAxis) event->gaxis.axis),
- event->gaxis.value);
- break;
- #endif /* VERBOSE_AXES */
- case SDL_EVENT_GAMEPAD_BUTTON_DOWN:
- case SDL_EVENT_GAMEPAD_BUTTON_UP:
- if (display_mode == CONTROLLER_MODE_TESTING) {
- if (event->type == SDL_EVENT_GAMEPAD_BUTTON_DOWN) {
- SetController(event->gbutton.which);
- }
- }
- #ifdef VERBOSE_BUTTONS
- SDL_Log("Gamepad %" SDL_PRIu32 " button %s %s",
- event->gbutton.which,
- SDL_GetGamepadStringForButton((SDL_GamepadButton) event->gbutton.button),
- event->gbutton.state ? "pressed" : "released");
- #endif /* VERBOSE_BUTTONS */
- if (display_mode == CONTROLLER_MODE_TESTING) {
- if (event->type == SDL_EVENT_GAMEPAD_BUTTON_DOWN &&
- controller && SDL_GetGamepadType(controller->gamepad) == SDL_GAMEPAD_TYPE_PS5) {
- /* Cycle PS5 audio routing when the microphone button is pressed */
- if (event->gbutton.button == SDL_GAMEPAD_BUTTON_MISC1) {
- CyclePS5AudioRoute(controller);
- }
- /* Cycle PS5 trigger effects when the triangle button is pressed */
- if (event->gbutton.button == SDL_GAMEPAD_BUTTON_NORTH) {
- CyclePS5TriggerEffect(controller);
- }
- }
- }
- break;
- case SDL_EVENT_MOUSE_BUTTON_DOWN:
- if (virtual_joystick && controller && controller->joystick == virtual_joystick) {
- VirtualGamepadMouseDown(event->button.x, event->button.y);
- }
- UpdateButtonHighlights(event->button.x, event->button.y, event->button.down);
- break;
- case SDL_EVENT_MOUSE_BUTTON_UP:
- if (virtual_joystick && controller && controller->joystick == virtual_joystick) {
- VirtualGamepadMouseUp(event->button.x, event->button.y);
- }
- if (display_mode == CONTROLLER_MODE_TESTING) {
- if (GamepadButtonContains(GetGyroResetButton(gyro_elements), event->button.x, event->button.y)) {
- ResetGyroOrientation(controller->imu_state);
- } else if (GamepadButtonContains(GetGyroCalibrateButton(gyro_elements), event->button.x, event->button.y)) {
- BeginNoiseCalibrationPhase(controller->imu_state);
- } else if (GamepadButtonContains(setup_mapping_button, event->button.x, event->button.y)) {
- SetDisplayMode(CONTROLLER_MODE_BINDING);
- }
- } else if (display_mode == CONTROLLER_MODE_BINDING) {
- if (GamepadButtonContains(done_mapping_button, event->button.x, event->button.y)) {
- if (controller->mapping) {
- SDL_Log("Mapping complete:");
- SDL_Log("%s", controller->mapping);
- }
- SetDisplayMode(CONTROLLER_MODE_TESTING);
- } else if (GamepadButtonContains(cancel_button, event->button.x, event->button.y)) {
- CancelMapping();
- } else if (GamepadButtonContains(clear_button, event->button.x, event->button.y)) {
- ClearMapping();
- } else if (controller->has_bindings &&
- GamepadButtonContains(copy_button, event->button.x, event->button.y)) {
- CopyMapping();
- } else if (GamepadButtonContains(paste_button, event->button.x, event->button.y)) {
- PasteMapping();
- } else if (title_pressed) {
- SetCurrentBindingElement(SDL_GAMEPAD_ELEMENT_NAME, false);
- } else if (type_pressed) {
- SetCurrentBindingElement(SDL_GAMEPAD_ELEMENT_TYPE, false);
- } else if (binding_element == SDL_GAMEPAD_ELEMENT_TYPE) {
- int type = GetGamepadTypeDisplayAt(gamepad_type, event->button.x, event->button.y);
- if (type != SDL_GAMEPAD_TYPE_UNSELECTED) {
- CommitGamepadType((SDL_GamepadType)type);
- StopBinding();
- }
- } else {
- int gamepad_element = SDL_GAMEPAD_ELEMENT_INVALID;
- char *joystick_element;
- if (controller->joystick != virtual_joystick) {
- gamepad_element = GetGamepadImageElementAt(image, event->button.x, event->button.y);
- }
- if (gamepad_element == SDL_GAMEPAD_ELEMENT_INVALID) {
- gamepad_element = GetGamepadDisplayElementAt(gamepad_elements, controller->gamepad, event->button.x, event->button.y);
- }
- if (gamepad_element != SDL_GAMEPAD_ELEMENT_INVALID) {
- /* Set this to false if you don't want to start the binding flow at this point */
- const bool should_start_flow = true;
- SetCurrentBindingElement(gamepad_element, should_start_flow);
- }
- joystick_element = GetJoystickDisplayElementAt(joystick_elements, controller->joystick, event->button.x, event->button.y);
- if (joystick_element) {
- CommitBindingElement(joystick_element, true);
- SDL_free(joystick_element);
- }
- }
- }
- UpdateButtonHighlights(event->button.x, event->button.y, event->button.down);
- break;
- case SDL_EVENT_MOUSE_MOTION:
- if (virtual_joystick && controller && controller->joystick == virtual_joystick) {
- VirtualGamepadMouseMotion(event->motion.x, event->motion.y);
- }
- UpdateButtonHighlights(event->motion.x, event->motion.y, event->motion.state ? true : false);
- break;
- case SDL_EVENT_KEY_DOWN:
- if (display_mode == CONTROLLER_MODE_TESTING) {
- if (event->key.key >= SDLK_0 && event->key.key <= SDLK_9) {
- if (controller && controller->gamepad) {
- int player_index = (event->key.key - SDLK_0);
- SDL_SetGamepadPlayerIndex(controller->gamepad, player_index);
- }
- break;
- } else if (event->key.key == SDLK_A) {
- OpenVirtualGamepad();
- } else if (event->key.key == SDLK_D) {
- CloseVirtualGamepad();
- } else if (event->key.key == SDLK_R && (event->key.mod & SDL_KMOD_CTRL)) {
- SDL_ReloadGamepadMappings();
- } else if (event->key.key == SDLK_ESCAPE) {
- done = true;
- } else if (event->key.key == SDLK_SPACE) {
- if (controller && controller->imu_state) {
- ResetGyroOrientation(controller->imu_state);
- }
- }
- } else if (display_mode == CONTROLLER_MODE_BINDING) {
- if (event->key.key == SDLK_C && (event->key.mod & SDL_KMOD_CTRL)) {
- if (binding_element == SDL_GAMEPAD_ELEMENT_NAME) {
- CopyControllerName();
- } else {
- CopyMapping();
- }
- } else if (event->key.key == SDLK_V && (event->key.mod & SDL_KMOD_CTRL)) {
- if (binding_element == SDL_GAMEPAD_ELEMENT_NAME) {
- ClearControllerName();
- PasteControllerName();
- } else {
- PasteMapping();
- }
- } else if (event->key.key == SDLK_X && (event->key.mod & SDL_KMOD_CTRL)) {
- if (binding_element == SDL_GAMEPAD_ELEMENT_NAME) {
- CopyControllerName();
- ClearControllerName();
- } else {
- CopyMapping();
- ClearMapping();
- }
- } else if (event->key.key == SDLK_SPACE) {
- if (binding_element != SDL_GAMEPAD_ELEMENT_NAME) {
- ClearBinding();
- }
- } else if (event->key.key == SDLK_BACKSPACE) {
- if (binding_element == SDL_GAMEPAD_ELEMENT_NAME) {
- BackspaceControllerName();
- }
- } else if (event->key.key == SDLK_RETURN) {
- if (binding_element == SDL_GAMEPAD_ELEMENT_NAME) {
- StopBinding();
- }
- } else if (event->key.key == SDLK_ESCAPE) {
- if (binding_element != SDL_GAMEPAD_ELEMENT_INVALID) {
- StopBinding();
- } else {
- CancelMapping();
- }
- }
- }
- break;
- case SDL_EVENT_TEXT_INPUT:
- if (display_mode == CONTROLLER_MODE_BINDING) {
- if (binding_element == SDL_GAMEPAD_ELEMENT_NAME) {
- AddControllerNameText(event->text.text);
- }
- }
- break;
- case SDL_EVENT_QUIT:
- done = true;
- break;
- default:
- break;
- }
- if (done) {
- return SDL_APP_SUCCESS;
- } else {
- return SDL_APP_CONTINUE;
- }
- }
- SDL_AppResult SDLCALL SDL_AppIterate(void *appstate)
- {
- /* If we have a virtual controller, send a virtual accelerometer sensor reading */
- if (virtual_joystick) {
- float data[3] = { 0.0f, SDL_STANDARD_GRAVITY, 0.0f };
- SDL_SendJoystickVirtualSensorData(virtual_joystick, SDL_SENSOR_ACCEL, SDL_GetTicksNS(), data, SDL_arraysize(data));
- }
- /* Wait 30 ms for joystick events to stop coming in,
- in case a gamepad sends multiple events for a single control (e.g. axis and button for trigger)
- */
- if (binding_advance_time && SDL_GetTicks() > (binding_advance_time + 30)) {
- if (binding_flow) {
- SetNextBindingElement();
- } else {
- StopBinding();
- }
- }
- /* blank screen, set up for drawing this frame. */
- SDL_SetRenderDrawColor(screen, 0xFF, 0xFF, 0xFF, SDL_ALPHA_OPAQUE);
- SDL_RenderClear(screen);
- SDL_SetRenderDrawColor(screen, 0x10, 0x10, 0x10, SDL_ALPHA_OPAQUE);
- if (controller) {
- SetGamepadImageShowingFront(image, ShowingFront());
- UpdateGamepadImageFromGamepad(image, controller->gamepad);
- if (display_mode == CONTROLLER_MODE_BINDING &&
- binding_element != SDL_GAMEPAD_ELEMENT_INVALID) {
- SetGamepadImageElement(image, binding_element, true);
- }
- RenderGamepadImage(image);
- if (binding_element == SDL_GAMEPAD_ELEMENT_TYPE) {
- SetGamepadTypeDisplayRealType(gamepad_type, SDL_GetRealGamepadType(controller->gamepad));
- RenderGamepadTypeDisplay(gamepad_type);
- } else {
- RenderGamepadDisplay(gamepad_elements, controller->gamepad);
- }
- RenderJoystickDisplay(joystick_elements, controller->joystick);
- if (display_mode == CONTROLLER_MODE_TESTING) {
- RenderGamepadButton(setup_mapping_button);
- RenderGyroDisplay(gyro_elements, gamepad_elements, controller->gamepad);
- } else if (display_mode == CONTROLLER_MODE_BINDING) {
- DrawBindingTips(screen);
- RenderGamepadButton(done_mapping_button);
- RenderGamepadButton(cancel_button);
- RenderGamepadButton(clear_button);
- if (controller->has_bindings) {
- RenderGamepadButton(copy_button);
- }
- RenderGamepadButton(paste_button);
- }
- DrawGamepadInfo(screen);
- UpdateGamepadEffects();
- } else {
- DrawGamepadWaiting(screen);
- }
- SDL_Delay(16);
- SDL_RenderPresent(screen);
- return SDL_APP_CONTINUE;
- }
- SDL_AppResult SDLCALL SDL_AppInit(void **appstate, int argc, char *argv[])
- {
- bool show_mappings = false;
- int i;
- float content_scale;
- int screen_width, screen_height;
- SDL_FRect area;
- int gamepad_index = -1;
- /* Initialize test framework */
- state = SDLTest_CommonCreateState(argv, 0);
- if (!state) {
- return SDL_APP_FAILURE;
- }
- SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI, "1");
- SDL_SetHint(SDL_HINT_JOYSTICK_ENHANCED_REPORTS, "auto");
- SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_STEAM, "1");
- SDL_SetHint(SDL_HINT_JOYSTICK_ROG_CHAKRAM, "1");
- SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1");
- SDL_SetHint(SDL_HINT_JOYSTICK_LINUX_DEADZONES, "1");
- /* Enable input debug logging */
- SDL_SetLogPriority(SDL_LOG_CATEGORY_INPUT, SDL_LOG_PRIORITY_DEBUG);
- /* Parse commandline */
- for (i = 1; i < argc;) {
- int consumed;
- consumed = SDLTest_CommonArg(state, i);
- if (!consumed) {
- if (SDL_strcmp(argv[i], "--mappings") == 0) {
- show_mappings = true;
- consumed = 1;
- } else if (SDL_strcmp(argv[i], "--virtual") == 0) {
- OpenVirtualGamepad();
- consumed = 1;
- } else if (gamepad_index < 0) {
- char *endptr = NULL;
- gamepad_index = (int)SDL_strtol(argv[i], &endptr, 0);
- if (endptr != argv[i] && *endptr == '\0' && gamepad_index >= 0) {
- consumed = 1;
- }
- }
- }
- if (consumed <= 0) {
- static const char *options[] = { "[--mappings]", "[--virtual]", "[index]", NULL };
- SDLTest_CommonLogUsage(state, argv[0], options);
- return SDL_APP_FAILURE;
- }
- i += consumed;
- }
- if (gamepad_index < 0) {
- gamepad_index = 0;
- }
- /* Initialize SDL (Note: video is required to start event loop) */
- if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK | SDL_INIT_GAMEPAD)) {
- SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't initialize SDL: %s", SDL_GetError());
- return SDL_APP_FAILURE;
- }
- SDL_AddGamepadMappingsFromFile("gamecontrollerdb.txt");
- if (show_mappings) {
- int count = 0;
- char **mappings = SDL_GetGamepadMappings(&count);
- int map_i;
- SDL_Log("Supported mappings:");
- for (map_i = 0; map_i < count; ++map_i) {
- SDL_Log("\t%s", mappings[map_i]);
- }
- SDL_Log("%s", "");
- SDL_free(mappings);
- }
- /* Create a window to display gamepad state */
- content_scale = SDL_GetDisplayContentScale(SDL_GetPrimaryDisplay());
- if (content_scale == 0.0f) {
- content_scale = 1.0f;
- }
- screen_width = (int)SDL_ceilf(SCREEN_WIDTH * content_scale);
- screen_height = (int)SDL_ceilf(SCREEN_HEIGHT * content_scale);
- window = SDL_CreateWindow("SDL Controller Test", screen_width, screen_height, SDL_WINDOW_HIGH_PIXEL_DENSITY);
- if (!window) {
- SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't create window: %s", SDL_GetError());
- return SDL_APP_FAILURE;
- }
- screen = SDL_CreateRenderer(window, NULL);
- if (!screen) {
- SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't create renderer: %s", SDL_GetError());
- SDL_DestroyWindow(window);
- return SDL_APP_FAILURE;
- }
- SDL_SetRenderDrawColor(screen, 0x00, 0x00, 0x00, SDL_ALPHA_OPAQUE);
- SDL_RenderClear(screen);
- SDL_RenderPresent(screen);
- /* scale for platforms that don't give you the window size you asked for. */
- SDL_SetRenderLogicalPresentation(screen, (int)SCREEN_WIDTH, (int)SCREEN_HEIGHT,
- SDL_LOGICAL_PRESENTATION_LETTERBOX);
- title_area.w = GAMEPAD_WIDTH;
- title_area.h = FONT_CHARACTER_SIZE + 2 * BUTTON_MARGIN;
- title_area.x = PANEL_WIDTH + PANEL_SPACING;
- title_area.y = TITLE_HEIGHT / 2 - title_area.h / 2;
- type_area.w = PANEL_WIDTH - 2 * BUTTON_MARGIN;
- type_area.h = FONT_CHARACTER_SIZE + 2 * BUTTON_MARGIN;
- type_area.x = BUTTON_MARGIN;
- type_area.y = TITLE_HEIGHT / 2 - type_area.h / 2;
- image = CreateGamepadImage(screen);
- if (!image) {
- SDL_DestroyRenderer(screen);
- SDL_DestroyWindow(window);
- return SDL_APP_FAILURE;
- }
- SetGamepadImagePosition(image, PANEL_WIDTH + PANEL_SPACING, TITLE_HEIGHT);
- gamepad_elements = CreateGamepadDisplay(screen);
- area.x = 0;
- area.y = TITLE_HEIGHT;
- area.w = PANEL_WIDTH;
- area.h = GAMEPAD_HEIGHT;
- SetGamepadDisplayArea(gamepad_elements, &area);
- gyro_elements = CreateGyroDisplay(screen);
- const float vidReservedHeight = 24.0f;
- /* Bottom right of the screen */
- area.w = SCREEN_WIDTH * 0.375f;
- area.h = SCREEN_HEIGHT * 0.475f;
- area.x = SCREEN_WIDTH - area.w;
- area.y = SCREEN_HEIGHT - area.h - vidReservedHeight;
- SetGyroDisplayArea(gyro_elements, &area);
- InitCirclePoints3D();
- gamepad_type = CreateGamepadTypeDisplay(screen);
- area.x = 0;
- area.y = TITLE_HEIGHT;
- area.w = PANEL_WIDTH;
- area.h = GAMEPAD_HEIGHT;
- SetGamepadTypeDisplayArea(gamepad_type, &area);
- joystick_elements = CreateJoystickDisplay(screen);
- area.x = PANEL_WIDTH + PANEL_SPACING + GAMEPAD_WIDTH + PANEL_SPACING;
- area.y = TITLE_HEIGHT;
- area.w = PANEL_WIDTH;
- area.h = GAMEPAD_HEIGHT;
- SetJoystickDisplayArea(joystick_elements, &area);
- setup_mapping_button = CreateGamepadButton(screen, "Setup Mapping");
- area.w = SDL_max(MINIMUM_BUTTON_WIDTH, GetGamepadButtonLabelWidth(setup_mapping_button) + 2 * BUTTON_PADDING);
- area.h = GetGamepadButtonLabelHeight(setup_mapping_button) + 2 * BUTTON_PADDING;
- area.x = BUTTON_MARGIN;
- area.y = SCREEN_HEIGHT - BUTTON_MARGIN - area.h;
- SetGamepadButtonArea(setup_mapping_button, &area);
- cancel_button = CreateGamepadButton(screen, "Cancel");
- area.w = SDL_max(MINIMUM_BUTTON_WIDTH, GetGamepadButtonLabelWidth(cancel_button) + 2 * BUTTON_PADDING);
- area.h = GetGamepadButtonLabelHeight(cancel_button) + 2 * BUTTON_PADDING;
- area.x = BUTTON_MARGIN;
- area.y = SCREEN_HEIGHT - BUTTON_MARGIN - area.h;
- SetGamepadButtonArea(cancel_button, &area);
- clear_button = CreateGamepadButton(screen, "Clear");
- area.x += area.w + BUTTON_PADDING;
- area.w = SDL_max(MINIMUM_BUTTON_WIDTH, GetGamepadButtonLabelWidth(clear_button) + 2 * BUTTON_PADDING);
- area.h = GetGamepadButtonLabelHeight(clear_button) + 2 * BUTTON_PADDING;
- area.y = SCREEN_HEIGHT - BUTTON_MARGIN - area.h;
- SetGamepadButtonArea(clear_button, &area);
- copy_button = CreateGamepadButton(screen, "Copy");
- area.x += area.w + BUTTON_PADDING;
- area.w = SDL_max(MINIMUM_BUTTON_WIDTH, GetGamepadButtonLabelWidth(copy_button) + 2 * BUTTON_PADDING);
- area.h = GetGamepadButtonLabelHeight(copy_button) + 2 * BUTTON_PADDING;
- area.y = SCREEN_HEIGHT - BUTTON_MARGIN - area.h;
- SetGamepadButtonArea(copy_button, &area);
- paste_button = CreateGamepadButton(screen, "Paste");
- area.x += area.w + BUTTON_PADDING;
- area.w = SDL_max(MINIMUM_BUTTON_WIDTH, GetGamepadButtonLabelWidth(paste_button) + 2 * BUTTON_PADDING);
- area.h = GetGamepadButtonLabelHeight(paste_button) + 2 * BUTTON_PADDING;
- area.y = SCREEN_HEIGHT - BUTTON_MARGIN - area.h;
- SetGamepadButtonArea(paste_button, &area);
- done_mapping_button = CreateGamepadButton(screen, "Done");
- area.w = SDL_max(MINIMUM_BUTTON_WIDTH, GetGamepadButtonLabelWidth(done_mapping_button) + 2 * BUTTON_PADDING);
- area.h = GetGamepadButtonLabelHeight(done_mapping_button) + 2 * BUTTON_PADDING;
- area.x = SCREEN_WIDTH / 2 - area.w / 2;
- area.y = SCREEN_HEIGHT - BUTTON_MARGIN - area.h;
- SetGamepadButtonArea(done_mapping_button, &area);
- /* Process the initial gamepad list */
- SDL_AppIterate(NULL);
- if (gamepad_index < num_controllers) {
- SetController(controllers[gamepad_index].id);
- } else if (num_controllers > 0) {
- SetController(controllers[0].id);
- }
- return SDL_APP_CONTINUE;
- }
- void SDLCALL SDL_AppQuit(void *appstate, SDL_AppResult result)
- {
- CloseVirtualGamepad();
- while (num_controllers > 0) {
- HandleGamepadRemoved(controllers[0].id);
- DelController(controllers[0].id);
- }
- SDL_free(controllers);
- SDL_free(controller_name);
- DestroyGamepadImage(image);
- DestroyGamepadDisplay(gamepad_elements);
- DestroyGyroDisplay(gyro_elements);
- DestroyGamepadTypeDisplay(gamepad_type);
- DestroyJoystickDisplay(joystick_elements);
- DestroyGamepadButton(setup_mapping_button);
- DestroyGamepadButton(done_mapping_button);
- DestroyGamepadButton(cancel_button);
- DestroyGamepadButton(clear_button);
- DestroyGamepadButton(copy_button);
- DestroyGamepadButton(paste_button);
- SDLTest_CleanupTextDrawing();
- SDL_DestroyRenderer(screen);
- SDL_DestroyWindow(window);
- SDL_Quit();
- SDLTest_CommonDestroyState(state);
- }
|