SDL

Форк
0
/
testcontroller.c 
2218 строк · 80.3 Кб
1
/*
2
  Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
3

4
  This software is provided 'as-is', without any express or implied
5
  warranty.  In no event will the authors be held liable for any damages
6
  arising from the use of this software.
7

8
  Permission is granted to anyone to use this software for any purpose,
9
  including commercial applications, and to alter it and redistribute it
10
  freely.
11
*/
12

13
/* Simple program to test the SDL controller routines */
14

15
#include <SDL3/SDL.h>
16
#include <SDL3/SDL_main.h>
17
#include <SDL3/SDL_test.h>
18
#include <SDL3/SDL_test_font.h>
19

20
#ifdef SDL_PLATFORM_EMSCRIPTEN
21
#include <emscripten/emscripten.h>
22
#endif
23

24
#include "gamepadutils.h"
25
#include "testutils.h"
26

27
#if 0
28
#define DEBUG_AXIS_MAPPING
29
#endif
30

31
#define TITLE_HEIGHT 48.0f
32
#define PANEL_SPACING 25.0f
33
#define PANEL_WIDTH 250.0f
34
#define MINIMUM_BUTTON_WIDTH 96.0f
35
#define BUTTON_MARGIN 16.0f
36
#define BUTTON_PADDING 12.0f
37
#define GAMEPAD_WIDTH 512.0f
38
#define GAMEPAD_HEIGHT 560.0f
39

40
#define SCREEN_WIDTH  (PANEL_WIDTH + PANEL_SPACING + GAMEPAD_WIDTH + PANEL_SPACING + PANEL_WIDTH)
41
#define SCREEN_HEIGHT (TITLE_HEIGHT + GAMEPAD_HEIGHT)
42

43
typedef struct
44
{
45
    SDL_bool m_bMoving;
46
    int m_nLastValue;
47
    int m_nStartingValue;
48
    int m_nFarthestValue;
49
} AxisState;
50

51
typedef struct
52
{
53
    SDL_JoystickID id;
54

55
    SDL_Joystick *joystick;
56
    int num_axes;
57
    AxisState *axis_state;
58

59
    SDL_Gamepad *gamepad;
60
    char *mapping;
61
    SDL_bool has_bindings;
62

63
    int audio_route;
64
    int trigger_effect;
65
} Controller;
66

67
static SDL_Window *window = NULL;
68
static SDL_Renderer *screen = NULL;
69
static ControllerDisplayMode display_mode = CONTROLLER_MODE_TESTING;
70
static GamepadImage *image = NULL;
71
static GamepadDisplay *gamepad_elements = NULL;
72
static GamepadTypeDisplay *gamepad_type = NULL;
73
static JoystickDisplay *joystick_elements = NULL;
74
static GamepadButton *setup_mapping_button = NULL;
75
static GamepadButton *done_mapping_button = NULL;
76
static GamepadButton *cancel_button = NULL;
77
static GamepadButton *clear_button = NULL;
78
static GamepadButton *copy_button = NULL;
79
static GamepadButton *paste_button = NULL;
80
static char *backup_mapping = NULL;
81
static SDL_bool done = SDL_FALSE;
82
static SDL_bool set_LED = SDL_FALSE;
83
static int num_controllers = 0;
84
static Controller *controllers;
85
static Controller *controller;
86
static SDL_JoystickID mapping_controller = 0;
87
static int binding_element = SDL_GAMEPAD_ELEMENT_INVALID;
88
static int last_binding_element = SDL_GAMEPAD_ELEMENT_INVALID;
89
static SDL_bool binding_flow = SDL_FALSE;
90
static int binding_flow_direction = 0;
91
static Uint64 binding_advance_time = 0;
92
static SDL_FRect title_area;
93
static SDL_bool title_highlighted;
94
static SDL_bool title_pressed;
95
static SDL_FRect type_area;
96
static SDL_bool type_highlighted;
97
static SDL_bool type_pressed;
98
static char *controller_name;
99
static SDL_Joystick *virtual_joystick = NULL;
100
static SDL_GamepadAxis virtual_axis_active = SDL_GAMEPAD_AXIS_INVALID;
101
static float virtual_axis_start_x;
102
static float virtual_axis_start_y;
103
static SDL_GamepadButton virtual_button_active = SDL_GAMEPAD_BUTTON_INVALID;
104
static SDL_bool virtual_touchpad_active = SDL_FALSE;
105
static float virtual_touchpad_x;
106
static float virtual_touchpad_y;
107

108
static int s_arrBindingOrder[] = {
109
    /* Standard sequence */
110
    SDL_GAMEPAD_BUTTON_SOUTH,
111
    SDL_GAMEPAD_BUTTON_EAST,
112
    SDL_GAMEPAD_BUTTON_WEST,
113
    SDL_GAMEPAD_BUTTON_NORTH,
114
    SDL_GAMEPAD_BUTTON_DPAD_LEFT,
115
    SDL_GAMEPAD_BUTTON_DPAD_RIGHT,
116
    SDL_GAMEPAD_BUTTON_DPAD_UP,
117
    SDL_GAMEPAD_BUTTON_DPAD_DOWN,
118
    SDL_GAMEPAD_ELEMENT_AXIS_LEFTX_NEGATIVE,
119
    SDL_GAMEPAD_ELEMENT_AXIS_LEFTX_POSITIVE,
120
    SDL_GAMEPAD_ELEMENT_AXIS_LEFTY_NEGATIVE,
121
    SDL_GAMEPAD_ELEMENT_AXIS_LEFTY_POSITIVE,
122
    SDL_GAMEPAD_BUTTON_LEFT_STICK,
123
    SDL_GAMEPAD_ELEMENT_AXIS_RIGHTX_NEGATIVE,
124
    SDL_GAMEPAD_ELEMENT_AXIS_RIGHTX_POSITIVE,
125
    SDL_GAMEPAD_ELEMENT_AXIS_RIGHTY_NEGATIVE,
126
    SDL_GAMEPAD_ELEMENT_AXIS_RIGHTY_POSITIVE,
127
    SDL_GAMEPAD_BUTTON_RIGHT_STICK,
128
    SDL_GAMEPAD_BUTTON_LEFT_SHOULDER,
129
    SDL_GAMEPAD_ELEMENT_AXIS_LEFT_TRIGGER,
130
    SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER,
131
    SDL_GAMEPAD_ELEMENT_AXIS_RIGHT_TRIGGER,
132
    SDL_GAMEPAD_BUTTON_BACK,
133
    SDL_GAMEPAD_BUTTON_START,
134
    SDL_GAMEPAD_BUTTON_GUIDE,
135
    SDL_GAMEPAD_BUTTON_MISC1,
136
    SDL_GAMEPAD_ELEMENT_INVALID,
137

138
    /* Paddle sequence */
139
    SDL_GAMEPAD_BUTTON_RIGHT_PADDLE1,
140
    SDL_GAMEPAD_BUTTON_LEFT_PADDLE1,
141
    SDL_GAMEPAD_BUTTON_RIGHT_PADDLE2,
142
    SDL_GAMEPAD_BUTTON_LEFT_PADDLE2,
143
    SDL_GAMEPAD_ELEMENT_INVALID,
144
};
145

146

147
static const char *GetSensorName(SDL_SensorType sensor)
148
{
149
    switch (sensor) {
150
    case SDL_SENSOR_ACCEL:
151
        return "accelerometer";
152
    case SDL_SENSOR_GYRO:
153
        return "gyro";
154
    case SDL_SENSOR_ACCEL_L:
155
        return "accelerometer (L)";
156
    case SDL_SENSOR_GYRO_L:
157
        return "gyro (L)";
158
    case SDL_SENSOR_ACCEL_R:
159
        return "accelerometer (R)";
160
    case SDL_SENSOR_GYRO_R:
161
        return "gyro (R)";
162
    default:
163
        return "UNKNOWN";
164
    }
165
}
166

167
/* PS5 trigger effect documentation:
168
   https://controllers.fandom.com/wiki/Sony_DualSense#FFB_Trigger_Modes
169
*/
170
typedef struct
171
{
172
    Uint8 ucEnableBits1;              /* 0 */
173
    Uint8 ucEnableBits2;              /* 1 */
174
    Uint8 ucRumbleRight;              /* 2 */
175
    Uint8 ucRumbleLeft;               /* 3 */
176
    Uint8 ucHeadphoneVolume;          /* 4 */
177
    Uint8 ucSpeakerVolume;            /* 5 */
178
    Uint8 ucMicrophoneVolume;         /* 6 */
179
    Uint8 ucAudioEnableBits;          /* 7 */
180
    Uint8 ucMicLightMode;             /* 8 */
181
    Uint8 ucAudioMuteBits;            /* 9 */
182
    Uint8 rgucRightTriggerEffect[11]; /* 10 */
183
    Uint8 rgucLeftTriggerEffect[11];  /* 21 */
184
    Uint8 rgucUnknown1[6];            /* 32 */
185
    Uint8 ucLedFlags;                 /* 38 */
186
    Uint8 rgucUnknown2[2];            /* 39 */
187
    Uint8 ucLedAnim;                  /* 41 */
188
    Uint8 ucLedBrightness;            /* 42 */
189
    Uint8 ucPadLights;                /* 43 */
190
    Uint8 ucLedRed;                   /* 44 */
191
    Uint8 ucLedGreen;                 /* 45 */
192
    Uint8 ucLedBlue;                  /* 46 */
193
} DS5EffectsState_t;
194

195
static void CyclePS5AudioRoute(Controller *device)
196
{
197
    DS5EffectsState_t state;
198

199
    device->audio_route = (device->audio_route + 1) % 4;
200

201
    SDL_zero(state);
202
    switch (device->audio_route) {
203
    case 0:
204
        /* Audio disabled */
205
        state.ucEnableBits1 |= (0x80 | 0x20 | 0x10); /* Modify audio route and speaker / headphone volume */
206
        state.ucSpeakerVolume = 0;                   /* Minimum volume */
207
        state.ucHeadphoneVolume = 0;                 /* Minimum volume */
208
        state.ucAudioEnableBits = 0x00;              /* Output to headphones */
209
        break;
210
    case 1:
211
        /* Headphones */
212
        state.ucEnableBits1 |= (0x80 | 0x10); /* Modify audio route and headphone volume */
213
        state.ucHeadphoneVolume = 50;         /* 50% volume - don't blast into the ears */
214
        state.ucAudioEnableBits = 0x00;       /* Output to headphones */
215
        break;
216
    case 2:
217
        /* Speaker */
218
        state.ucEnableBits1 |= (0x80 | 0x20); /* Modify audio route and speaker volume */
219
        state.ucSpeakerVolume = 100;          /* Maximum volume */
220
        state.ucAudioEnableBits = 0x30;       /* Output to speaker */
221
        break;
222
    case 3:
223
        /* Both */
224
        state.ucEnableBits1 |= (0x80 | 0x20 | 0x10); /* Modify audio route and speaker / headphone volume */
225
        state.ucSpeakerVolume = 100;                 /* Maximum volume */
226
        state.ucHeadphoneVolume = 50;                /* 50% volume - don't blast into the ears */
227
        state.ucAudioEnableBits = 0x20;              /* Output to both speaker and headphones */
228
        break;
229
    }
230
    SDL_SendGamepadEffect(device->gamepad, &state, sizeof(state));
231
}
232

233
static void CyclePS5TriggerEffect(Controller *device)
234
{
235
    DS5EffectsState_t state;
236

237
    Uint8 effects[3][11] = {
238
        /* Clear trigger effect */
239
        { 0x05, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
240
        /* Constant resistance across entire trigger pull */
241
        { 0x01, 0, 110, 0, 0, 0, 0, 0, 0, 0, 0 },
242
        /* Resistance and vibration when trigger is pulled */
243
        { 0x06, 15, 63, 128, 0, 0, 0, 0, 0, 0, 0 },
244
    };
245

246
    device->trigger_effect = (device->trigger_effect + 1) % SDL_arraysize(effects);
247

248
    SDL_zero(state);
249
    state.ucEnableBits1 |= (0x04 | 0x08); /* Modify right and left trigger effect respectively */
250
    SDL_memcpy(state.rgucRightTriggerEffect, effects[device->trigger_effect], sizeof(effects[0]));
251
    SDL_memcpy(state.rgucLeftTriggerEffect, effects[device->trigger_effect], sizeof(effects[0]));
252
    SDL_SendGamepadEffect(device->gamepad, &state, sizeof(state));
253
}
254

255
static void ClearButtonHighlights(void)
256
{
257
    title_highlighted = SDL_FALSE;
258
    title_pressed = SDL_FALSE;
259

260
    type_highlighted = SDL_FALSE;
261
    type_pressed = SDL_FALSE;
262

263
    ClearGamepadImage(image);
264
    SetGamepadDisplayHighlight(gamepad_elements, SDL_GAMEPAD_ELEMENT_INVALID, SDL_FALSE);
265
    SetGamepadTypeDisplayHighlight(gamepad_type, SDL_GAMEPAD_TYPE_UNSELECTED, SDL_FALSE);
266
    SetGamepadButtonHighlight(setup_mapping_button, SDL_FALSE, SDL_FALSE);
267
    SetGamepadButtonHighlight(done_mapping_button, SDL_FALSE, SDL_FALSE);
268
    SetGamepadButtonHighlight(cancel_button, SDL_FALSE, SDL_FALSE);
269
    SetGamepadButtonHighlight(clear_button, SDL_FALSE, SDL_FALSE);
270
    SetGamepadButtonHighlight(copy_button, SDL_FALSE, SDL_FALSE);
271
    SetGamepadButtonHighlight(paste_button, SDL_FALSE, SDL_FALSE);
272
}
273

274
static void UpdateButtonHighlights(float x, float y, SDL_bool button_down)
275
{
276
    ClearButtonHighlights();
277

278
    if (display_mode == CONTROLLER_MODE_TESTING) {
279
        SetGamepadButtonHighlight(setup_mapping_button, GamepadButtonContains(setup_mapping_button, x, y), button_down);
280
    } else if (display_mode == CONTROLLER_MODE_BINDING) {
281
        SDL_FPoint point;
282
        int gamepad_highlight_element = SDL_GAMEPAD_ELEMENT_INVALID;
283
        char *joystick_highlight_element;
284

285
        point.x = x;
286
        point.y = y;
287
        if (SDL_PointInRectFloat(&point, &title_area)) {
288
            title_highlighted = SDL_TRUE;
289
            title_pressed = button_down;
290
        } else {
291
            title_highlighted = SDL_FALSE;
292
            title_pressed = SDL_FALSE;
293
        }
294

295
        if (SDL_PointInRectFloat(&point, &type_area)) {
296
            type_highlighted = SDL_TRUE;
297
            type_pressed = button_down;
298
        } else {
299
            type_highlighted = SDL_FALSE;
300
            type_pressed = SDL_FALSE;
301
        }
302

303
        if (controller->joystick != virtual_joystick) {
304
            gamepad_highlight_element = GetGamepadImageElementAt(image, x, y);
305
        }
306
        if (gamepad_highlight_element == SDL_GAMEPAD_ELEMENT_INVALID) {
307
            gamepad_highlight_element = GetGamepadDisplayElementAt(gamepad_elements, controller->gamepad, x, y);
308
        }
309
        SetGamepadDisplayHighlight(gamepad_elements, gamepad_highlight_element, button_down);
310

311
        if (binding_element == SDL_GAMEPAD_ELEMENT_TYPE) {
312
            int gamepad_highlight_type = GetGamepadTypeDisplayAt(gamepad_type, x, y);
313
            SetGamepadTypeDisplayHighlight(gamepad_type, gamepad_highlight_type, button_down);
314
        }
315

316
        joystick_highlight_element = GetJoystickDisplayElementAt(joystick_elements, controller->joystick, x, y);
317
        SetJoystickDisplayHighlight(joystick_elements, joystick_highlight_element, button_down);
318
        SDL_free(joystick_highlight_element);
319

320
        SetGamepadButtonHighlight(done_mapping_button, GamepadButtonContains(done_mapping_button, x, y), button_down);
321
        SetGamepadButtonHighlight(cancel_button, GamepadButtonContains(cancel_button, x, y), button_down);
322
        SetGamepadButtonHighlight(clear_button, GamepadButtonContains(clear_button, x, y), button_down);
323
        SetGamepadButtonHighlight(copy_button, GamepadButtonContains(copy_button, x, y), button_down);
324
        SetGamepadButtonHighlight(paste_button, GamepadButtonContains(paste_button, x, y), button_down);
325
    }
326
}
327

328
static int StandardizeAxisValue(int nValue)
329
{
330
    if (nValue > SDL_JOYSTICK_AXIS_MAX / 2) {
331
        return SDL_JOYSTICK_AXIS_MAX;
332
    } else if (nValue < SDL_JOYSTICK_AXIS_MIN / 2) {
333
        return SDL_JOYSTICK_AXIS_MIN;
334
    } else {
335
        return 0;
336
    }
337
}
338

339
static void RefreshControllerName(void)
340
{
341
    const char *name = NULL;
342

343
    SDL_free(controller_name);
344
    controller_name = NULL;
345

346
    if (controller) {
347
        if (controller->gamepad) {
348
            name = SDL_GetGamepadName(controller->gamepad);
349
        } else {
350
            name = SDL_GetJoystickName(controller->joystick);
351
        }
352
    }
353

354
    if (name) {
355
        controller_name = SDL_strdup(name);
356
    } else {
357
        controller_name = SDL_strdup("");
358
    }
359
}
360

361
static void SetAndFreeGamepadMapping(char *mapping)
362
{
363
    SDL_SetGamepadMapping(controller->id, mapping);
364
    SDL_free(mapping);
365
}
366

367
static void SetCurrentBindingElement(int element, SDL_bool flow)
368
{
369
    int i;
370

371
    if (binding_element == SDL_GAMEPAD_ELEMENT_NAME) {
372
        RefreshControllerName();
373
    }
374

375
    if (element == SDL_GAMEPAD_ELEMENT_INVALID) {
376
        binding_flow_direction = 0;
377
        last_binding_element = SDL_GAMEPAD_ELEMENT_INVALID;
378
    } else {
379
        last_binding_element = binding_element;
380
    }
381
    binding_element = element;
382
    binding_flow = flow || (element == SDL_GAMEPAD_BUTTON_SOUTH);
383
    binding_advance_time = 0;
384

385
    for (i = 0; i < controller->num_axes; ++i) {
386
        controller->axis_state[i].m_nFarthestValue = controller->axis_state[i].m_nStartingValue;
387
    }
388

389
    SetGamepadDisplaySelected(gamepad_elements, element);
390
}
391

392
static void SetNextBindingElement(void)
393
{
394
    int i;
395

396
    if (binding_element == SDL_GAMEPAD_ELEMENT_INVALID) {
397
        return;
398
    }
399

400
    for (i = 0; i < SDL_arraysize(s_arrBindingOrder); ++i) {
401
        if (binding_element == s_arrBindingOrder[i]) {
402
            binding_flow_direction = 1;
403
            SetCurrentBindingElement(s_arrBindingOrder[i + 1], SDL_TRUE);
404
            return;
405
        }
406
    }
407
    SetCurrentBindingElement(SDL_GAMEPAD_ELEMENT_INVALID, SDL_FALSE);
408
}
409

410
static void SetPrevBindingElement(void)
411
{
412
    int i;
413

414
    if (binding_element == SDL_GAMEPAD_ELEMENT_INVALID) {
415
        return;
416
    }
417

418
    for (i = 1; i < SDL_arraysize(s_arrBindingOrder); ++i) {
419
        if (binding_element == s_arrBindingOrder[i]) {
420
            binding_flow_direction = -1;
421
            SetCurrentBindingElement(s_arrBindingOrder[i - 1], SDL_TRUE);
422
            return;
423
        }
424
    }
425
    SetCurrentBindingElement(SDL_GAMEPAD_ELEMENT_INVALID, SDL_FALSE);
426
}
427

428
static void StopBinding(void)
429
{
430
    SetCurrentBindingElement(SDL_GAMEPAD_ELEMENT_INVALID, SDL_FALSE);
431
}
432

433
typedef struct
434
{
435
    int axis;
436
    int direction;
437
} AxisInfo;
438

439
static SDL_bool ParseAxisInfo(const char *description, AxisInfo *info)
440
{
441
    if (!description) {
442
        return SDL_FALSE;
443
    }
444

445
    if (*description == '-') {
446
        info->direction = -1;
447
        ++description;
448
    } else if (*description == '+') {
449
        info->direction = 1;
450
        ++description;
451
    } else {
452
        info->direction = 0;
453
    }
454

455
    if (description[0] == 'a' && SDL_isdigit(description[1])) {
456
        ++description;
457
        info->axis = SDL_atoi(description);
458
        return SDL_TRUE;
459
    }
460
    return SDL_FALSE;
461
}
462

463
static void CommitBindingElement(const char *binding, SDL_bool force)
464
{
465
    char *mapping;
466
    int direction = 1;
467
    SDL_bool ignore_binding = SDL_FALSE;
468

469
    if (binding_element == SDL_GAMEPAD_ELEMENT_INVALID) {
470
        return;
471
    }
472

473
    if (controller->mapping) {
474
        mapping = SDL_strdup(controller->mapping);
475
    } else {
476
        mapping = NULL;
477
    }
478

479
    /* If the controller generates multiple events for a single element, pick the best one */
480
    if (!force && binding_advance_time) {
481
        char *current = GetElementBinding(mapping, binding_element);
482
        SDL_bool native_button = (binding_element < SDL_GAMEPAD_BUTTON_MAX);
483
        SDL_bool native_axis = (binding_element >= SDL_GAMEPAD_BUTTON_MAX &&
484
                                binding_element <= SDL_GAMEPAD_ELEMENT_AXIS_MAX);
485
        SDL_bool native_trigger = (binding_element == SDL_GAMEPAD_ELEMENT_AXIS_LEFT_TRIGGER ||
486
                                   binding_element == SDL_GAMEPAD_ELEMENT_AXIS_RIGHT_TRIGGER);
487
        SDL_bool native_dpad = (binding_element == SDL_GAMEPAD_BUTTON_DPAD_UP ||
488
                                binding_element == SDL_GAMEPAD_BUTTON_DPAD_DOWN ||
489
                                binding_element == SDL_GAMEPAD_BUTTON_DPAD_LEFT ||
490
                                binding_element == SDL_GAMEPAD_BUTTON_DPAD_RIGHT);
491

492
        if (native_button) {
493
            SDL_bool current_button = (current && *current == 'b');
494
            SDL_bool proposed_button = (binding && *binding == 'b');
495
            if (current_button && !proposed_button) {
496
                ignore_binding = SDL_TRUE;
497
            }
498
            /* Use the lower index button (we map from lower to higher button index) */
499
            if (current_button && proposed_button && current[1] < binding[1]) {
500
                ignore_binding = SDL_TRUE;
501
            }
502
        }
503
        if (native_axis) {
504
            AxisInfo current_axis_info  = { 0, 0 };
505
            AxisInfo proposed_axis_info = { 0, 0 };
506
            SDL_bool current_axis = ParseAxisInfo(current, &current_axis_info);
507
            SDL_bool proposed_axis = ParseAxisInfo(binding, &proposed_axis_info);
508

509
            if (current_axis) {
510
                /* Ignore this unless the proposed binding extends the existing axis */
511
                ignore_binding = SDL_TRUE;
512

513
                if (native_trigger &&
514
                    ((*current == '-' && *binding == '+' &&
515
                      SDL_strcmp(current + 1, binding + 1) == 0) ||
516
                     (*current == '+' && *binding == '-' &&
517
                      SDL_strcmp(current + 1, binding + 1) == 0))) {
518
                    /* Merge two half axes into a whole axis for a trigger */
519
                    ++binding;
520
                    ignore_binding = SDL_FALSE;
521
                }
522

523
                /* Use the lower index axis (we map from lower to higher axis index) */
524
                if (proposed_axis && proposed_axis_info.axis < current_axis_info.axis) {
525
                    ignore_binding = SDL_FALSE;
526
                }
527
            }
528
        }
529
        if (native_dpad) {
530
            SDL_bool current_hat = (current && *current == 'h');
531
            SDL_bool proposed_hat = (binding && *binding == 'h');
532
            if (current_hat && !proposed_hat) {
533
                ignore_binding = SDL_TRUE;
534
            }
535
            /* Use the lower index hat (we map from lower to higher hat index) */
536
            if (current_hat && proposed_hat && current[1] < binding[1]) {
537
                ignore_binding = SDL_TRUE;
538
            }
539
        }
540
        SDL_free(current);
541
    }
542

543
    if (!ignore_binding && binding_flow && !force) {
544
        int existing = GetElementForBinding(mapping, binding);
545
        if (existing != SDL_GAMEPAD_ELEMENT_INVALID) {
546
            SDL_GamepadButton action_forward = SDL_GAMEPAD_BUTTON_SOUTH;
547
            SDL_GamepadButton action_backward = SDL_GAMEPAD_BUTTON_EAST;
548
            SDL_GamepadButton action_delete = SDL_GAMEPAD_BUTTON_WEST;
549
            if (binding_element == action_forward) {
550
                /* Bind it! */
551
            } else if (binding_element == action_backward) {
552
                if (existing == action_forward) {
553
                    SDL_bool bound_backward = MappingHasElement(controller->mapping, action_backward);
554
                    if (bound_backward) {
555
                        /* Just move on to the next one */
556
                        ignore_binding = SDL_TRUE;
557
                        SetNextBindingElement();
558
                    } else {
559
                        /* You can't skip the backward action, go back and start over */
560
                        ignore_binding = SDL_TRUE;
561
                        SetPrevBindingElement();
562
                    }
563
                } else if (existing == action_backward && binding_flow_direction == -1) {
564
                    /* Keep going backwards */
565
                    ignore_binding = SDL_TRUE;
566
                    SetPrevBindingElement();
567
                } else {
568
                    /* Bind it! */
569
                }
570
            } else if (existing == action_forward) {
571
                /* Just move on to the next one */
572
                ignore_binding = SDL_TRUE;
573
                SetNextBindingElement();
574
            } else if (existing == action_backward) {
575
                ignore_binding = SDL_TRUE;
576
                SetPrevBindingElement();
577
            } else if (existing == binding_element) {
578
                /* We're rebinding the same thing, just move to the next one */
579
                ignore_binding = SDL_TRUE;
580
                SetNextBindingElement();
581
            } else if (existing == action_delete) {
582
                /* Clear the current binding and move to the next one */
583
                binding = NULL;
584
                direction = 1;
585
                force = SDL_TRUE;
586
            } else if (binding_element != action_forward &&
587
                       binding_element != action_backward) {
588
                /* Actually, we'll just clear the existing binding */
589
                /*ignore_binding = SDL_TRUE;*/
590
            }
591
        }
592
    }
593

594
    if (ignore_binding) {
595
        SDL_free(mapping);
596
        return;
597
    }
598

599
    mapping = ClearMappingBinding(mapping, binding);
600
    mapping = SetElementBinding(mapping, binding_element, binding);
601
    SetAndFreeGamepadMapping(mapping);
602

603
    if (force) {
604
        if (binding_flow) {
605
            if (direction > 0) {
606
                SetNextBindingElement();
607
            } else if (direction < 0) {
608
                SetPrevBindingElement();
609
            }
610
        } else {
611
            StopBinding();
612
        }
613
    } else {
614
        /* Wait to see if any more bindings come in */
615
        binding_advance_time = SDL_GetTicks() + 30;
616
    }
617
}
618

619
static void ClearBinding(void)
620
{
621
    CommitBindingElement(NULL, SDL_TRUE);
622
}
623

624
static void SetDisplayMode(ControllerDisplayMode mode)
625
{
626
    float x, y;
627
    SDL_MouseButtonFlags button_state;
628

629
    if (mode == CONTROLLER_MODE_BINDING) {
630
        /* Make a backup of the current mapping */
631
        if (controller->mapping) {
632
            backup_mapping = SDL_strdup(controller->mapping);
633
        }
634
        mapping_controller = controller->id;
635
        if (MappingHasBindings(backup_mapping)) {
636
            SetCurrentBindingElement(SDL_GAMEPAD_ELEMENT_INVALID, SDL_FALSE);
637
        } else {
638
            SetCurrentBindingElement(SDL_GAMEPAD_BUTTON_SOUTH, SDL_TRUE);
639
        }
640
    } else {
641
        if (backup_mapping) {
642
            SDL_free(backup_mapping);
643
            backup_mapping = NULL;
644
        }
645
        mapping_controller = 0;
646
        StopBinding();
647
    }
648

649
    display_mode = mode;
650
    SetGamepadImageDisplayMode(image, mode);
651
    SetGamepadDisplayDisplayMode(gamepad_elements, mode);
652

653
    button_state = SDL_GetMouseState(&x, &y);
654
    SDL_RenderCoordinatesFromWindow(screen, x, y, &x, &y);
655
    UpdateButtonHighlights(x, y, button_state ? SDL_TRUE : SDL_FALSE);
656
}
657

658
static void CancelMapping(void)
659
{
660
    SetAndFreeGamepadMapping(backup_mapping);
661
    backup_mapping = NULL;
662

663
    SetDisplayMode(CONTROLLER_MODE_TESTING);
664
}
665

666
static void ClearMapping(void)
667
{
668
    SetAndFreeGamepadMapping(NULL);
669
    SetCurrentBindingElement(SDL_GAMEPAD_ELEMENT_INVALID, SDL_FALSE);
670
}
671

672
static void CopyMapping(void)
673
{
674
    if (controller && controller->mapping) {
675
        SDL_SetClipboardText(controller->mapping);
676
    }
677
}
678

679
static void PasteMapping(void)
680
{
681
    if (controller) {
682
        char *mapping = SDL_GetClipboardText();
683
        if (MappingHasBindings(mapping)) {
684
            StopBinding();
685
            SDL_SetGamepadMapping(controller->id, mapping);
686
            RefreshControllerName();
687
        } else {
688
            /* Not a valid mapping, ignore it */
689
        }
690
        SDL_free(mapping);
691
    }
692
}
693

694
static void CommitControllerName(void)
695
{
696
    char *mapping = NULL;
697

698
    if (controller->mapping) {
699
        mapping = SDL_strdup(controller->mapping);
700
    } else {
701
        mapping = NULL;
702
    }
703
    mapping = SetMappingName(mapping, controller_name);
704
    SetAndFreeGamepadMapping(mapping);
705
}
706

707
static void AddControllerNameText(const char *text)
708
{
709
    size_t current_length = (controller_name ? SDL_strlen(controller_name) : 0);
710
    size_t text_length = SDL_strlen(text);
711
    size_t size = current_length + text_length + 1;
712
    char *name = (char *)SDL_realloc(controller_name, size);
713
    if (name) {
714
        SDL_memcpy(&name[current_length], text, text_length + 1);
715
        controller_name = name;
716
    }
717
    CommitControllerName();
718
}
719

720
static void BackspaceControllerName(void)
721
{
722
    size_t length = (controller_name ? SDL_strlen(controller_name) : 0);
723
    if (length > 0) {
724
        controller_name[length - 1] = '\0';
725
    }
726
    CommitControllerName();
727
}
728

729
static void ClearControllerName(void)
730
{
731
    if (controller_name) {
732
        *controller_name = '\0';
733
    }
734
    CommitControllerName();
735
}
736

737
static void CopyControllerName(void)
738
{
739
    SDL_SetClipboardText(controller_name);
740
}
741

742
static void PasteControllerName(void)
743
{
744
    SDL_free(controller_name);
745
    controller_name = SDL_GetClipboardText();
746
    CommitControllerName();
747
}
748

749
static void CommitGamepadType(SDL_GamepadType type)
750
{
751
    char *mapping = NULL;
752

753
    if (controller->mapping) {
754
        mapping = SDL_strdup(controller->mapping);
755
    } else {
756
        mapping = NULL;
757
    }
758
    mapping = SetMappingType(mapping, type);
759
    SetAndFreeGamepadMapping(mapping);
760
}
761

762
static const char *GetBindingInstruction(void)
763
{
764
    switch (binding_element) {
765
    case SDL_GAMEPAD_ELEMENT_INVALID:
766
        return "Select an element to bind from the list on the left";
767
    case SDL_GAMEPAD_BUTTON_SOUTH:
768
    case SDL_GAMEPAD_BUTTON_EAST:
769
    case SDL_GAMEPAD_BUTTON_WEST:
770
    case SDL_GAMEPAD_BUTTON_NORTH:
771
        switch (SDL_GetGamepadButtonLabelForType(GetGamepadImageType(image), (SDL_GamepadButton)binding_element)) {
772
        case SDL_GAMEPAD_BUTTON_LABEL_A:
773
            return "Press the A button";
774
        case SDL_GAMEPAD_BUTTON_LABEL_B:
775
            return "Press the B button";
776
        case SDL_GAMEPAD_BUTTON_LABEL_X:
777
            return "Press the X button";
778
        case SDL_GAMEPAD_BUTTON_LABEL_Y:
779
            return "Press the Y button";
780
        case SDL_GAMEPAD_BUTTON_LABEL_CROSS:
781
            return "Press the Cross (X) button";
782
        case SDL_GAMEPAD_BUTTON_LABEL_CIRCLE:
783
            return "Press the Circle button";
784
        case SDL_GAMEPAD_BUTTON_LABEL_SQUARE:
785
            return "Press the Square button";
786
        case SDL_GAMEPAD_BUTTON_LABEL_TRIANGLE:
787
            return "Press the Triangle button";
788
        default:
789
            return "";
790
        }
791
        break;
792
    case SDL_GAMEPAD_BUTTON_BACK:
793
        return "Press the left center button (Back/View/Share)";
794
    case SDL_GAMEPAD_BUTTON_GUIDE:
795
        return "Press the center button (Home/Guide)";
796
    case SDL_GAMEPAD_BUTTON_START:
797
        return "Press the right center button (Start/Menu/Options)";
798
    case SDL_GAMEPAD_BUTTON_LEFT_STICK:
799
        return "Press the left thumbstick button (LSB/L3)";
800
    case SDL_GAMEPAD_BUTTON_RIGHT_STICK:
801
        return "Press the right thumbstick button (RSB/R3)";
802
    case SDL_GAMEPAD_BUTTON_LEFT_SHOULDER:
803
        return "Press the left shoulder button (LB/L1)";
804
    case SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER:
805
        return "Press the right shoulder button (RB/R1)";
806
    case SDL_GAMEPAD_BUTTON_DPAD_UP:
807
        return "Press the D-Pad up";
808
    case SDL_GAMEPAD_BUTTON_DPAD_DOWN:
809
        return "Press the D-Pad down";
810
    case SDL_GAMEPAD_BUTTON_DPAD_LEFT:
811
        return "Press the D-Pad left";
812
    case SDL_GAMEPAD_BUTTON_DPAD_RIGHT:
813
        return "Press the D-Pad right";
814
    case SDL_GAMEPAD_BUTTON_MISC1:
815
        return "Press the bottom center button (Share/Capture)";
816
    case SDL_GAMEPAD_BUTTON_RIGHT_PADDLE1:
817
        return "Press the upper paddle under your right hand";
818
    case SDL_GAMEPAD_BUTTON_LEFT_PADDLE1:
819
        return "Press the upper paddle under your left hand";
820
    case SDL_GAMEPAD_BUTTON_RIGHT_PADDLE2:
821
        return "Press the lower paddle under your right hand";
822
    case SDL_GAMEPAD_BUTTON_LEFT_PADDLE2:
823
        return "Press the lower paddle under your left hand";
824
    case SDL_GAMEPAD_BUTTON_TOUCHPAD:
825
        return "Press down on the touchpad";
826
    case SDL_GAMEPAD_BUTTON_MISC2:
827
    case SDL_GAMEPAD_BUTTON_MISC3:
828
    case SDL_GAMEPAD_BUTTON_MISC4:
829
    case SDL_GAMEPAD_BUTTON_MISC5:
830
    case SDL_GAMEPAD_BUTTON_MISC6:
831
        return "Press any additional button not already bound";
832
    case SDL_GAMEPAD_ELEMENT_AXIS_LEFTX_NEGATIVE:
833
        return "Move the left thumbstick to the left";
834
    case SDL_GAMEPAD_ELEMENT_AXIS_LEFTX_POSITIVE:
835
        return "Move the left thumbstick to the right";
836
    case SDL_GAMEPAD_ELEMENT_AXIS_LEFTY_NEGATIVE:
837
        return "Move the left thumbstick up";
838
    case SDL_GAMEPAD_ELEMENT_AXIS_LEFTY_POSITIVE:
839
        return "Move the left thumbstick down";
840
    case SDL_GAMEPAD_ELEMENT_AXIS_RIGHTX_NEGATIVE:
841
        return "Move the right thumbstick to the left";
842
    case SDL_GAMEPAD_ELEMENT_AXIS_RIGHTX_POSITIVE:
843
        return "Move the right thumbstick to the right";
844
    case SDL_GAMEPAD_ELEMENT_AXIS_RIGHTY_NEGATIVE:
845
        return "Move the right thumbstick up";
846
    case SDL_GAMEPAD_ELEMENT_AXIS_RIGHTY_POSITIVE:
847
        return "Move the right thumbstick down";
848
    case SDL_GAMEPAD_ELEMENT_AXIS_LEFT_TRIGGER:
849
        return "Pull the left trigger (LT/L2)";
850
    case SDL_GAMEPAD_ELEMENT_AXIS_RIGHT_TRIGGER:
851
        return "Pull the right trigger (RT/R2)";
852
    case SDL_GAMEPAD_ELEMENT_NAME:
853
        return "Type the name of your controller";
854
    case SDL_GAMEPAD_ELEMENT_TYPE:
855
        return "Select the type of your controller";
856
    default:
857
        return "";
858
    }
859
}
860

861
static int FindController(SDL_JoystickID id)
862
{
863
    int i;
864

865
    for (i = 0; i < num_controllers; ++i) {
866
        if (id == controllers[i].id) {
867
            return i;
868
        }
869
    }
870
    return -1;
871
}
872

873
static void SetController(SDL_JoystickID id)
874
{
875
    int i = FindController(id);
876

877
    if (i < 0 && num_controllers > 0) {
878
        i = 0;
879
    }
880

881
    if (i >= 0) {
882
        controller = &controllers[i];
883
    } else {
884
        controller = NULL;
885
    }
886

887
    RefreshControllerName();
888
}
889

890
static void AddController(SDL_JoystickID id, SDL_bool verbose)
891
{
892
    Controller *new_controllers;
893
    Controller *new_controller;
894
    SDL_Joystick *joystick;
895

896
    if (FindController(id) >= 0) {
897
        /* We already have this controller */
898
        return;
899
    }
900

901
    new_controllers = (Controller *)SDL_realloc(controllers, (num_controllers + 1) * sizeof(*controllers));
902
    if (!new_controllers) {
903
        return;
904
    }
905

906
    controller = NULL;
907
    controllers = new_controllers;
908
    new_controller = &new_controllers[num_controllers++];
909
    SDL_zerop(new_controller);
910
    new_controller->id = id;
911

912
    new_controller->joystick = SDL_OpenJoystick(id);
913
    new_controller->num_axes = SDL_GetNumJoystickAxes(new_controller->joystick);
914
    new_controller->axis_state = (AxisState *)SDL_calloc(new_controller->num_axes, sizeof(*new_controller->axis_state));
915

916
    joystick = new_controller->joystick;
917
    if (joystick) {
918
        if (verbose && !SDL_IsGamepad(id)) {
919
            const char *name = SDL_GetJoystickName(joystick);
920
            const char *path = SDL_GetJoystickPath(joystick);
921
            char guid[33];
922
            SDL_Log("Opened joystick %s%s%s\n", name, path ? ", " : "", path ? path : "");
923
            SDL_GUIDToString(SDL_GetJoystickGUID(joystick), guid, sizeof(guid));
924
            SDL_Log("No gamepad mapping for %s\n", guid);
925
        }
926
    } else {
927
        SDL_Log("Couldn't open joystick: %s", SDL_GetError());
928
    }
929

930
    if (mapping_controller) {
931
        SetController(mapping_controller);
932
    } else {
933
        SetController(id);
934
    }
935
}
936

937
static void DelController(SDL_JoystickID id)
938
{
939
    int i = FindController(id);
940

941
    if (i < 0) {
942
        return;
943
    }
944

945
    if (display_mode == CONTROLLER_MODE_BINDING && id == controller->id) {
946
        SetDisplayMode(CONTROLLER_MODE_TESTING);
947
    }
948

949
    /* Reset trigger state */
950
    if (controllers[i].trigger_effect != 0) {
951
        controllers[i].trigger_effect = -1;
952
        CyclePS5TriggerEffect(&controllers[i]);
953
    }
954
    SDL_assert(controllers[i].gamepad == NULL);
955
    if (controllers[i].axis_state) {
956
        SDL_free(controllers[i].axis_state);
957
    }
958
    if (controllers[i].joystick) {
959
        SDL_CloseJoystick(controllers[i].joystick);
960
    }
961

962
    --num_controllers;
963
    if (i < num_controllers) {
964
        SDL_memcpy(&controllers[i], &controllers[i + 1], (num_controllers - i) * sizeof(*controllers));
965
    }
966

967
    if (mapping_controller) {
968
        SetController(mapping_controller);
969
    } else {
970
        SetController(id);
971
    }
972
}
973

974
static void HandleGamepadRemapped(SDL_JoystickID id)
975
{
976
    char *mapping;
977
    int i = FindController(id);
978

979
    SDL_assert(i >= 0);
980
    if (i < 0) {
981
        return;
982
    }
983

984
    if (!controllers[i].gamepad) {
985
        /* Failed to open this controller */
986
        return;
987
    }
988

989
    /* Get the current mapping */
990
    mapping = SDL_GetGamepadMapping(controllers[i].gamepad);
991

992
    /* Make sure the mapping has a valid name */
993
    if (mapping && !MappingHasName(mapping)) {
994
        mapping = SetMappingName(mapping, SDL_GetJoystickName(controllers[i].joystick));
995
    }
996

997
    SDL_free(controllers[i].mapping);
998
    controllers[i].mapping = mapping;
999
    controllers[i].has_bindings = MappingHasBindings(mapping);
1000
}
1001

1002
static void HandleGamepadAdded(SDL_JoystickID id, SDL_bool verbose)
1003
{
1004
    SDL_Gamepad *gamepad;
1005
    Uint16 firmware_version;
1006
    SDL_SensorType sensors[] = {
1007
        SDL_SENSOR_ACCEL,
1008
        SDL_SENSOR_GYRO,
1009
        SDL_SENSOR_ACCEL_L,
1010
        SDL_SENSOR_GYRO_L,
1011
        SDL_SENSOR_ACCEL_R,
1012
        SDL_SENSOR_GYRO_R
1013
    };
1014
    int i;
1015

1016
    i = FindController(id);
1017
    if (i < 0) {
1018
        return;
1019
    }
1020

1021
    SDL_assert(!controllers[i].gamepad);
1022
    controllers[i].gamepad = SDL_OpenGamepad(id);
1023

1024
    gamepad = controllers[i].gamepad;
1025
    if (gamepad) {
1026
        if (verbose) {
1027
            SDL_PropertiesID props = SDL_GetGamepadProperties(gamepad);
1028
            const char *name = SDL_GetGamepadName(gamepad);
1029
            const char *path = SDL_GetGamepadPath(gamepad);
1030
            SDL_Log("Opened gamepad %s%s%s\n", name, path ? ", " : "", path ? path : "");
1031

1032
            firmware_version = SDL_GetGamepadFirmwareVersion(gamepad);
1033
            if (firmware_version) {
1034
                SDL_Log("Firmware version: 0x%x (%d)\n", firmware_version, firmware_version);
1035
            }
1036

1037
            if (SDL_GetBooleanProperty(props, SDL_PROP_GAMEPAD_CAP_PLAYER_LED_BOOLEAN, SDL_FALSE)) {
1038
                SDL_Log("Has player LED");
1039
            }
1040

1041
            if (SDL_GetBooleanProperty(props, SDL_PROP_GAMEPAD_CAP_RUMBLE_BOOLEAN, SDL_FALSE)) {
1042
                SDL_Log("Rumble supported");
1043
            }
1044

1045
            if (SDL_GetBooleanProperty(props, SDL_PROP_GAMEPAD_CAP_TRIGGER_RUMBLE_BOOLEAN, SDL_FALSE)) {
1046
                SDL_Log("Trigger rumble supported");
1047
            }
1048

1049
            if (SDL_GetGamepadPlayerIndex(gamepad) >= 0) {
1050
                SDL_Log("Player index: %d\n", SDL_GetGamepadPlayerIndex(gamepad));
1051
            }
1052
        }
1053

1054
        for (i = 0; i < SDL_arraysize(sensors); ++i) {
1055
            SDL_SensorType sensor = sensors[i];
1056

1057
            if (SDL_GamepadHasSensor(gamepad, sensor)) {
1058
                if (verbose) {
1059
                    SDL_Log("Enabling %s at %.2f Hz\n", GetSensorName(sensor), SDL_GetGamepadSensorDataRate(gamepad, sensor));
1060
                }
1061
                SDL_SetGamepadSensorEnabled(gamepad, sensor, SDL_TRUE);
1062
            }
1063
        }
1064

1065
        if (verbose) {
1066
            char *mapping = SDL_GetGamepadMapping(gamepad);
1067
            if (mapping) {
1068
                SDL_Log("Mapping: %s\n", mapping);
1069
                SDL_free(mapping);
1070
            }
1071
        }
1072
    } else {
1073
        SDL_Log("Couldn't open gamepad: %s", SDL_GetError());
1074
    }
1075

1076
    HandleGamepadRemapped(id);
1077
}
1078

1079
static void HandleGamepadRemoved(SDL_JoystickID id)
1080
{
1081
    int i = FindController(id);
1082

1083
    SDL_assert(i >= 0);
1084
    if (i < 0) {
1085
        return;
1086
    }
1087

1088
    if (controllers[i].mapping) {
1089
        SDL_free(controllers[i].mapping);
1090
        controllers[i].mapping = NULL;
1091
    }
1092
    if (controllers[i].gamepad) {
1093
        SDL_CloseGamepad(controllers[i].gamepad);
1094
        controllers[i].gamepad = NULL;
1095
    }
1096
}
1097

1098
static Uint16 ConvertAxisToRumble(Sint16 axisval)
1099
{
1100
    /* Only start rumbling if the axis is past the halfway point */
1101
    const Sint16 half_axis = (Sint16)SDL_ceil(SDL_JOYSTICK_AXIS_MAX / 2.0f);
1102
    if (axisval > half_axis) {
1103
        return (Uint16)(axisval - half_axis) * 4;
1104
    } else {
1105
        return 0;
1106
    }
1107
}
1108

1109
static SDL_bool ShowingFront(void)
1110
{
1111
    SDL_bool showing_front = SDL_TRUE;
1112
    int i;
1113

1114
    /* Show the back of the gamepad if the paddles are being held or bound */
1115
    for (i = SDL_GAMEPAD_BUTTON_RIGHT_PADDLE1; i <= SDL_GAMEPAD_BUTTON_LEFT_PADDLE2; ++i) {
1116
        if (SDL_GetGamepadButton(controller->gamepad, (SDL_GamepadButton)i) == SDL_PRESSED ||
1117
            binding_element == i) {
1118
            showing_front = SDL_FALSE;
1119
            break;
1120
        }
1121
    }
1122
    if ((SDL_GetModState() & SDL_KMOD_SHIFT) && binding_element != SDL_GAMEPAD_ELEMENT_NAME) {
1123
        showing_front = SDL_FALSE;
1124
    }
1125
    return showing_front;
1126
}
1127

1128
static void SDLCALL VirtualGamepadSetPlayerIndex(void *userdata, int player_index)
1129
{
1130
    SDL_Log("Virtual Gamepad: player index set to %d\n", player_index);
1131
}
1132

1133
static int SDLCALL VirtualGamepadRumble(void *userdata, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)
1134
{
1135
    SDL_Log("Virtual Gamepad: rumble set to %d/%d\n", low_frequency_rumble, high_frequency_rumble);
1136
    return 0;
1137
}
1138

1139
static int SDLCALL VirtualGamepadRumbleTriggers(void *userdata, Uint16 left_rumble, Uint16 right_rumble)
1140
{
1141
    SDL_Log("Virtual Gamepad: trigger rumble set to %d/%d\n", left_rumble, right_rumble);
1142
    return 0;
1143
}
1144

1145
static int SDLCALL VirtualGamepadSetLED(void *userdata, Uint8 red, Uint8 green, Uint8 blue)
1146
{
1147
    SDL_Log("Virtual Gamepad: LED set to RGB %d,%d,%d\n", red, green, blue);
1148
    return 0;
1149
}
1150

1151
static void OpenVirtualGamepad(void)
1152
{
1153
    SDL_VirtualJoystickTouchpadDesc virtual_touchpad = { 1, { 0, 0, 0 } };
1154
    SDL_VirtualJoystickSensorDesc virtual_sensor = { SDL_SENSOR_ACCEL, 0.0f };
1155
    SDL_VirtualJoystickDesc desc;
1156
    SDL_JoystickID virtual_id;
1157

1158
    if (virtual_joystick) {
1159
        return;
1160
    }
1161

1162
    SDL_zero(desc);
1163
    desc.type = SDL_JOYSTICK_TYPE_GAMEPAD;
1164
    desc.naxes = SDL_GAMEPAD_AXIS_MAX;
1165
    desc.nbuttons = SDL_GAMEPAD_BUTTON_MAX;
1166
    desc.ntouchpads = 1;
1167
    desc.touchpads = &virtual_touchpad;
1168
    desc.nsensors = 1;
1169
    desc.sensors = &virtual_sensor;
1170
    desc.SetPlayerIndex = VirtualGamepadSetPlayerIndex;
1171
    desc.Rumble = VirtualGamepadRumble;
1172
    desc.RumbleTriggers = VirtualGamepadRumbleTriggers;
1173
    desc.SetLED = VirtualGamepadSetLED;
1174

1175
    virtual_id = SDL_AttachVirtualJoystick(&desc);
1176
    if (virtual_id == 0) {
1177
        SDL_Log("Couldn't attach virtual device: %s\n", SDL_GetError());
1178
    } else {
1179
        virtual_joystick = SDL_OpenJoystick(virtual_id);
1180
        if (!virtual_joystick) {
1181
            SDL_Log("Couldn't open virtual device: %s\n", SDL_GetError());
1182
        }
1183
    }
1184
}
1185

1186
static void CloseVirtualGamepad(void)
1187
{
1188
    int i;
1189
    SDL_JoystickID *joysticks = SDL_GetJoysticks(NULL);
1190
    if (joysticks) {
1191
        for (i = 0; joysticks[i]; ++i) {
1192
            SDL_JoystickID instance_id = joysticks[i];
1193
            if (SDL_IsJoystickVirtual(instance_id)) {
1194
                SDL_DetachVirtualJoystick(instance_id);
1195
            }
1196
        }
1197
        SDL_free(joysticks);
1198
    }
1199

1200
    if (virtual_joystick) {
1201
        SDL_CloseJoystick(virtual_joystick);
1202
        virtual_joystick = NULL;
1203
    }
1204
}
1205

1206
static void VirtualGamepadMouseMotion(float x, float y)
1207
{
1208
    if (virtual_button_active != SDL_GAMEPAD_BUTTON_INVALID) {
1209
        if (virtual_axis_active != SDL_GAMEPAD_AXIS_INVALID) {
1210
            const float MOVING_DISTANCE = 2.0f;
1211
            if (SDL_fabs(x - virtual_axis_start_x) >= MOVING_DISTANCE ||
1212
                SDL_fabs(y - virtual_axis_start_y) >= MOVING_DISTANCE) {
1213
                SDL_SetJoystickVirtualButton(virtual_joystick, virtual_button_active, SDL_RELEASED);
1214
                virtual_button_active = SDL_GAMEPAD_BUTTON_INVALID;
1215
            }
1216
        }
1217
    }
1218

1219
    if (virtual_axis_active != SDL_GAMEPAD_AXIS_INVALID) {
1220
        if (virtual_axis_active == SDL_GAMEPAD_AXIS_LEFT_TRIGGER ||
1221
            virtual_axis_active == SDL_GAMEPAD_AXIS_RIGHT_TRIGGER) {
1222
            int range = (SDL_JOYSTICK_AXIS_MAX - SDL_JOYSTICK_AXIS_MIN);
1223
            float distance = SDL_clamp((y - virtual_axis_start_y) / GetGamepadImageAxisHeight(image), 0.0f, 1.0f);
1224
            Sint16 value = (Sint16)(SDL_JOYSTICK_AXIS_MIN + (distance * range));
1225
            SDL_SetJoystickVirtualAxis(virtual_joystick, virtual_axis_active, value);
1226
        } else {
1227
            float distanceX = SDL_clamp((x - virtual_axis_start_x) / GetGamepadImageAxisWidth(image), -1.0f, 1.0f);
1228
            float distanceY = SDL_clamp((y - virtual_axis_start_y) / GetGamepadImageAxisHeight(image), -1.0f, 1.0f);
1229
            Sint16 valueX, valueY;
1230

1231
            if (distanceX >= 0) {
1232
                valueX = (Sint16)(distanceX * SDL_JOYSTICK_AXIS_MAX);
1233
            } else {
1234
                valueX = (Sint16)(distanceX * -SDL_JOYSTICK_AXIS_MIN);
1235
            }
1236
            if (distanceY >= 0) {
1237
                valueY = (Sint16)(distanceY * SDL_JOYSTICK_AXIS_MAX);
1238
            } else {
1239
                valueY = (Sint16)(distanceY * -SDL_JOYSTICK_AXIS_MIN);
1240
            }
1241
            SDL_SetJoystickVirtualAxis(virtual_joystick, virtual_axis_active, valueX);
1242
            SDL_SetJoystickVirtualAxis(virtual_joystick, virtual_axis_active + 1, valueY);
1243
        }
1244
    }
1245

1246
    if (virtual_touchpad_active) {
1247
        SDL_FRect touchpad;
1248
        GetGamepadTouchpadArea(image, &touchpad);
1249
        virtual_touchpad_x = (x - touchpad.x) / touchpad.w;
1250
        virtual_touchpad_y = (y - touchpad.y) / touchpad.h;
1251
        SDL_SetJoystickVirtualTouchpad(virtual_joystick, 0, 0, SDL_PRESSED, virtual_touchpad_x, virtual_touchpad_y, 1.0f);
1252
    }
1253
}
1254

1255
static void VirtualGamepadMouseDown(float x, float y)
1256
{
1257
    int element = GetGamepadImageElementAt(image, x, y);
1258

1259
    if (element == SDL_GAMEPAD_ELEMENT_INVALID) {
1260
        SDL_FPoint point = { x, y };
1261
        SDL_FRect touchpad;
1262
        GetGamepadTouchpadArea(image, &touchpad);
1263
        if (SDL_PointInRectFloat(&point, &touchpad)) {
1264
            virtual_touchpad_active = SDL_TRUE;
1265
            virtual_touchpad_x = (x - touchpad.x) / touchpad.w;
1266
            virtual_touchpad_y = (y - touchpad.y) / touchpad.h;
1267
            SDL_SetJoystickVirtualTouchpad(virtual_joystick, 0, 0, SDL_PRESSED, virtual_touchpad_x, virtual_touchpad_y, 1.0f);
1268
        }
1269
        return;
1270
    }
1271

1272
    if (element < SDL_GAMEPAD_BUTTON_MAX) {
1273
        virtual_button_active = (SDL_GamepadButton)element;
1274
        SDL_SetJoystickVirtualButton(virtual_joystick, virtual_button_active, SDL_PRESSED);
1275
    } else {
1276
        switch (element) {
1277
        case SDL_GAMEPAD_ELEMENT_AXIS_LEFTX_NEGATIVE:
1278
        case SDL_GAMEPAD_ELEMENT_AXIS_LEFTX_POSITIVE:
1279
        case SDL_GAMEPAD_ELEMENT_AXIS_LEFTY_NEGATIVE:
1280
        case SDL_GAMEPAD_ELEMENT_AXIS_LEFTY_POSITIVE:
1281
            virtual_axis_active = SDL_GAMEPAD_AXIS_LEFTX;
1282
            break;
1283
        case SDL_GAMEPAD_ELEMENT_AXIS_RIGHTX_NEGATIVE:
1284
        case SDL_GAMEPAD_ELEMENT_AXIS_RIGHTX_POSITIVE:
1285
        case SDL_GAMEPAD_ELEMENT_AXIS_RIGHTY_NEGATIVE:
1286
        case SDL_GAMEPAD_ELEMENT_AXIS_RIGHTY_POSITIVE:
1287
            virtual_axis_active = SDL_GAMEPAD_AXIS_RIGHTX;
1288
            break;
1289
        case SDL_GAMEPAD_ELEMENT_AXIS_LEFT_TRIGGER:
1290
            virtual_axis_active = SDL_GAMEPAD_AXIS_LEFT_TRIGGER;
1291
            break;
1292
        case SDL_GAMEPAD_ELEMENT_AXIS_RIGHT_TRIGGER:
1293
            virtual_axis_active = SDL_GAMEPAD_AXIS_RIGHT_TRIGGER;
1294
            break;
1295
        }
1296
        virtual_axis_start_x = x;
1297
        virtual_axis_start_y = y;
1298
    }
1299
}
1300

1301
static void VirtualGamepadMouseUp(float x, float y)
1302
{
1303
    if (virtual_button_active != SDL_GAMEPAD_BUTTON_INVALID) {
1304
        SDL_SetJoystickVirtualButton(virtual_joystick, virtual_button_active, SDL_RELEASED);
1305
        virtual_button_active = SDL_GAMEPAD_BUTTON_INVALID;
1306
    }
1307

1308
    if (virtual_axis_active != SDL_GAMEPAD_AXIS_INVALID) {
1309
        if (virtual_axis_active == SDL_GAMEPAD_AXIS_LEFT_TRIGGER ||
1310
            virtual_axis_active == SDL_GAMEPAD_AXIS_RIGHT_TRIGGER) {
1311
            SDL_SetJoystickVirtualAxis(virtual_joystick, virtual_axis_active, SDL_JOYSTICK_AXIS_MIN);
1312
        } else {
1313
            SDL_SetJoystickVirtualAxis(virtual_joystick, virtual_axis_active, 0);
1314
            SDL_SetJoystickVirtualAxis(virtual_joystick, virtual_axis_active + 1, 0);
1315
        }
1316
        virtual_axis_active = SDL_GAMEPAD_AXIS_INVALID;
1317
    }
1318

1319
    if (virtual_touchpad_active) {
1320
        SDL_SetJoystickVirtualTouchpad(virtual_joystick, 0, 0, SDL_RELEASED, virtual_touchpad_x, virtual_touchpad_y, 0.0f);
1321
        virtual_touchpad_active = SDL_FALSE;
1322
    }
1323
}
1324

1325
static void DrawGamepadWaiting(SDL_Renderer *renderer)
1326
{
1327
    const char *text = "Waiting for gamepad, press A to add a virtual controller";
1328
    float x, y;
1329

1330
    x = SCREEN_WIDTH / 2 - (FONT_CHARACTER_SIZE * SDL_strlen(text)) / 2;
1331
    y = TITLE_HEIGHT / 2 - FONT_CHARACTER_SIZE / 2;
1332
    SDLTest_DrawString(renderer, x, y, text);
1333
}
1334

1335
static void DrawGamepadInfo(SDL_Renderer *renderer)
1336
{
1337
    const char *type;
1338
    const char *serial;
1339
    char text[128];
1340
    float x, y;
1341

1342
    if (title_highlighted) {
1343
        Uint8 r, g, b, a;
1344

1345
        SDL_GetRenderDrawColor(renderer, &r, &g, &b, &a);
1346

1347
        if (title_pressed) {
1348
            SDL_SetRenderDrawColor(renderer, PRESSED_COLOR);
1349
        } else {
1350
            SDL_SetRenderDrawColor(renderer, HIGHLIGHT_COLOR);
1351
        }
1352
        SDL_RenderFillRect(renderer, &title_area);
1353

1354
        SDL_SetRenderDrawColor(renderer, r, g, b, a);
1355
    }
1356

1357
    if (type_highlighted) {
1358
        Uint8 r, g, b, a;
1359

1360
        SDL_GetRenderDrawColor(renderer, &r, &g, &b, &a);
1361

1362
        if (type_pressed) {
1363
            SDL_SetRenderDrawColor(renderer, PRESSED_COLOR);
1364
        } else {
1365
            SDL_SetRenderDrawColor(renderer, HIGHLIGHT_COLOR);
1366
        }
1367
        SDL_RenderFillRect(renderer, &type_area);
1368

1369
        SDL_SetRenderDrawColor(renderer, r, g, b, a);
1370
    }
1371

1372
    if (controller->joystick) {
1373
        SDL_snprintf(text, sizeof(text), "(%" SDL_PRIu32 ")", SDL_GetJoystickID(controller->joystick));
1374
        x = SCREEN_WIDTH - (FONT_CHARACTER_SIZE * SDL_strlen(text)) - 8.0f;
1375
        y = 8.0f;
1376
        SDLTest_DrawString(renderer, x, y, text);
1377
    }
1378

1379
    if (controller_name && *controller_name) {
1380
        x = title_area.x + title_area.w / 2 - (FONT_CHARACTER_SIZE * SDL_strlen(controller_name)) / 2;
1381
        y = title_area.y + title_area.h / 2 - FONT_CHARACTER_SIZE / 2;
1382
        SDLTest_DrawString(renderer, x, y, controller_name);
1383
    }
1384

1385
    if (SDL_IsJoystickVirtual(controller->id)) {
1386
        SDL_strlcpy(text, "Click on the gamepad image below to generate input", sizeof(text));
1387
        x = SCREEN_WIDTH / 2 - (FONT_CHARACTER_SIZE * SDL_strlen(text)) / 2;
1388
        y = TITLE_HEIGHT / 2 - FONT_CHARACTER_SIZE / 2 + FONT_LINE_HEIGHT + 2.0f;
1389
        SDLTest_DrawString(renderer, x, y, text);
1390
    }
1391

1392
    type = GetGamepadTypeString(SDL_GetGamepadType(controller->gamepad));
1393
    x = type_area.x + type_area.w / 2 - (FONT_CHARACTER_SIZE * SDL_strlen(type)) / 2;
1394
    y = type_area.y + type_area.h / 2 - FONT_CHARACTER_SIZE / 2;
1395
    SDLTest_DrawString(renderer, x, y, type);
1396

1397
    if (display_mode == CONTROLLER_MODE_TESTING) {
1398
        Uint64 steam_handle = SDL_GetGamepadSteamHandle(controller->gamepad);
1399
        if (steam_handle) {
1400
            SDL_snprintf(text, SDL_arraysize(text), "Steam: 0x%.16" SDL_PRIx64, steam_handle);
1401
            y = SCREEN_HEIGHT - 2 * (8.0f + FONT_LINE_HEIGHT);
1402
            x = SCREEN_WIDTH - 8.0f - (FONT_CHARACTER_SIZE * SDL_strlen(text));
1403
            SDLTest_DrawString(renderer, x, y, text);
1404
        }
1405

1406
        SDL_snprintf(text, SDL_arraysize(text), "VID: 0x%.4x PID: 0x%.4x",
1407
                     SDL_GetJoystickVendor(controller->joystick),
1408
                     SDL_GetJoystickProduct(controller->joystick));
1409
        y = SCREEN_HEIGHT - 8.0f - FONT_LINE_HEIGHT;
1410
        x = SCREEN_WIDTH - 8.0f - (FONT_CHARACTER_SIZE * SDL_strlen(text));
1411
        SDLTest_DrawString(renderer, x, y, text);
1412

1413
        serial = SDL_GetJoystickSerial(controller->joystick);
1414
        if (serial && *serial) {
1415
            SDL_snprintf(text, SDL_arraysize(text), "Serial: %s", serial);
1416
            x = SCREEN_WIDTH / 2 - (FONT_CHARACTER_SIZE * SDL_strlen(text)) / 2;
1417
            y = SCREEN_HEIGHT - 8.0f - FONT_LINE_HEIGHT;
1418
            SDLTest_DrawString(renderer, x, y, text);
1419
        }
1420
    }
1421
}
1422

1423
static const char *GetButtonLabel(SDL_GamepadType type, SDL_GamepadButton button)
1424
{
1425
    switch (SDL_GetGamepadButtonLabelForType(type, button)) {
1426
    case SDL_GAMEPAD_BUTTON_LABEL_A:
1427
        return "A";
1428
    case SDL_GAMEPAD_BUTTON_LABEL_B:
1429
        return "B";
1430
    case SDL_GAMEPAD_BUTTON_LABEL_X:
1431
        return "X";
1432
    case SDL_GAMEPAD_BUTTON_LABEL_Y:
1433
        return "Y";
1434
    case SDL_GAMEPAD_BUTTON_LABEL_CROSS:
1435
        return "Cross (X)";
1436
    case SDL_GAMEPAD_BUTTON_LABEL_CIRCLE:
1437
        return "Circle";
1438
    case SDL_GAMEPAD_BUTTON_LABEL_SQUARE:
1439
        return "Square";
1440
    case SDL_GAMEPAD_BUTTON_LABEL_TRIANGLE:
1441
        return "Triangle";
1442
    default:
1443
        return "UNKNOWN";
1444
    }
1445
}
1446

1447
static void DrawBindingTips(SDL_Renderer *renderer)
1448
{
1449
    const char *text;
1450
    SDL_FRect image_area, button_area;
1451
    float x, y;
1452

1453
    GetGamepadImageArea(image, &image_area);
1454
    GetGamepadButtonArea(done_mapping_button, &button_area);
1455
    x = image_area.x + image_area.w / 2;
1456
    y = image_area.y + image_area.h;
1457
    y += (button_area.y - y - FONT_CHARACTER_SIZE) / 2;
1458

1459
    text = GetBindingInstruction();
1460

1461
    if (binding_element == SDL_GAMEPAD_ELEMENT_INVALID) {
1462
        SDLTest_DrawString(renderer, x - (FONT_CHARACTER_SIZE * SDL_strlen(text)) / 2, y, text);
1463
    } else {
1464
        Uint8 r, g, b, a;
1465
        SDL_FRect rect;
1466
        SDL_GamepadButton action_forward = SDL_GAMEPAD_BUTTON_SOUTH;
1467
        SDL_bool bound_forward = MappingHasElement(controller->mapping, action_forward);
1468
        SDL_GamepadButton action_backward = SDL_GAMEPAD_BUTTON_EAST;
1469
        SDL_bool bound_backward = MappingHasElement(controller->mapping, action_backward);
1470
        SDL_GamepadButton action_delete = SDL_GAMEPAD_BUTTON_WEST;
1471
        SDL_bool bound_delete = MappingHasElement(controller->mapping, action_delete);
1472

1473
        y -= (FONT_CHARACTER_SIZE + BUTTON_MARGIN) / 2;
1474

1475
        rect.w = 2.0f + (FONT_CHARACTER_SIZE * SDL_strlen(text)) + 2.0f;
1476
        rect.h = 2.0f + FONT_CHARACTER_SIZE + 2.0f;
1477
        rect.x = x - rect.w / 2;
1478
        rect.y = y - 2.0f;
1479

1480
        SDL_GetRenderDrawColor(renderer, &r, &g, &b, &a);
1481
        SDL_SetRenderDrawColor(renderer, SELECTED_COLOR);
1482
        SDL_RenderFillRect(renderer, &rect);
1483
        SDL_SetRenderDrawColor(renderer, r, g, b, a);
1484
        SDLTest_DrawString(renderer, x - (FONT_CHARACTER_SIZE * SDL_strlen(text)) / 2, y, text);
1485

1486
        y += (FONT_CHARACTER_SIZE + BUTTON_MARGIN);
1487

1488
        if (binding_element == SDL_GAMEPAD_ELEMENT_NAME) {
1489
            text = "(press RETURN to complete)";
1490
        } else if (binding_element == SDL_GAMEPAD_ELEMENT_TYPE ||
1491
                   binding_element == action_forward ||
1492
                   binding_element == action_backward) {
1493
            text = "(press ESC to cancel)";
1494
        } else {
1495
            static char dynamic_text[128];
1496
            SDL_GamepadType type = GetGamepadImageType(image);
1497
            if (binding_flow && bound_forward && bound_backward) {
1498
                if (binding_element != action_delete && bound_delete) {
1499
                    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));
1500
                } else {
1501
                    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));
1502
                }
1503
                text = dynamic_text;
1504
            } else {
1505
                text = "(press SPACE to delete and ESC to cancel)";
1506
            }
1507
        }
1508
        SDLTest_DrawString(renderer, x - (FONT_CHARACTER_SIZE * SDL_strlen(text)) / 2, y, text);
1509
    }
1510
}
1511

1512
static void UpdateGamepadEffects(void)
1513
{
1514
    if (display_mode != CONTROLLER_MODE_TESTING || !controller->gamepad) {
1515
        return;
1516
    }
1517

1518
    /* Update LED based on left thumbstick position */
1519
    {
1520
        Sint16 x = SDL_GetGamepadAxis(controller->gamepad, SDL_GAMEPAD_AXIS_LEFTX);
1521
        Sint16 y = SDL_GetGamepadAxis(controller->gamepad, SDL_GAMEPAD_AXIS_LEFTY);
1522

1523
        if (!set_LED) {
1524
            set_LED = (x < -8000 || x > 8000 || y > 8000);
1525
        }
1526
        if (set_LED) {
1527
            Uint8 r, g, b;
1528

1529
            if (x < 0) {
1530
                r = (Uint8)(((~x) * 255) / 32767);
1531
                b = 0;
1532
            } else {
1533
                r = 0;
1534
                b = (Uint8)(((int)(x)*255) / 32767);
1535
            }
1536
            if (y > 0) {
1537
                g = (Uint8)(((int)(y)*255) / 32767);
1538
            } else {
1539
                g = 0;
1540
            }
1541

1542
            SDL_SetGamepadLED(controller->gamepad, r, g, b);
1543
        }
1544
    }
1545

1546
    if (controller->trigger_effect == 0) {
1547
        /* Update rumble based on trigger state */
1548
        {
1549
            Sint16 left = SDL_GetGamepadAxis(controller->gamepad, SDL_GAMEPAD_AXIS_LEFT_TRIGGER);
1550
            Sint16 right = SDL_GetGamepadAxis(controller->gamepad, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER);
1551
            Uint16 low_frequency_rumble = ConvertAxisToRumble(left);
1552
            Uint16 high_frequency_rumble = ConvertAxisToRumble(right);
1553
            SDL_RumbleGamepad(controller->gamepad, low_frequency_rumble, high_frequency_rumble, 250);
1554
        }
1555

1556
        /* Update trigger rumble based on thumbstick state */
1557
        {
1558
            Sint16 left = SDL_GetGamepadAxis(controller->gamepad, SDL_GAMEPAD_AXIS_LEFTY);
1559
            Sint16 right = SDL_GetGamepadAxis(controller->gamepad, SDL_GAMEPAD_AXIS_RIGHTY);
1560
            Uint16 left_rumble = ConvertAxisToRumble(~left);
1561
            Uint16 right_rumble = ConvertAxisToRumble(~right);
1562

1563
            SDL_RumbleGamepadTriggers(controller->gamepad, left_rumble, right_rumble, 250);
1564
        }
1565
    }
1566
}
1567

1568
static void loop(void *arg)
1569
{
1570
    SDL_Event event;
1571

1572
    /* If we have a virtual controller, send a virtual accelerometer sensor reading */
1573
    if (virtual_joystick) {
1574
        float data[3] = { 0.0f, SDL_STANDARD_GRAVITY, 0.0f };
1575
        SDL_SendJoystickVirtualSensorData(virtual_joystick, SDL_SENSOR_ACCEL, SDL_GetTicksNS(), data, SDL_arraysize(data));
1576
    }
1577

1578
    /* Update to get the current event state */
1579
    SDL_PumpEvents();
1580

1581
    /* Process all currently pending events */
1582
    while (SDL_PeepEvents(&event, 1, SDL_GETEVENT, SDL_EVENT_FIRST, SDL_EVENT_LAST) == 1) {
1583
        SDL_ConvertEventToRenderCoordinates(screen, &event);
1584

1585
        switch (event.type) {
1586
        case SDL_EVENT_JOYSTICK_ADDED:
1587
            AddController(event.jdevice.which, SDL_TRUE);
1588
            break;
1589

1590
        case SDL_EVENT_JOYSTICK_REMOVED:
1591
            DelController(event.jdevice.which);
1592
            break;
1593

1594
        case SDL_EVENT_JOYSTICK_AXIS_MOTION:
1595
            if (display_mode == CONTROLLER_MODE_TESTING) {
1596
                if (event.jaxis.value <= (-SDL_JOYSTICK_AXIS_MAX / 2) || event.jaxis.value >= (SDL_JOYSTICK_AXIS_MAX / 2)) {
1597
                    SetController(event.jaxis.which);
1598
                }
1599
            } else if (display_mode == CONTROLLER_MODE_BINDING &&
1600
                       event.jaxis.which == controller->id &&
1601
                       event.jaxis.axis < controller->num_axes &&
1602
                       binding_element != SDL_GAMEPAD_ELEMENT_INVALID) {
1603
                const int MAX_ALLOWED_JITTER = SDL_JOYSTICK_AXIS_MAX / 80; /* ShanWan PS3 gamepad needed 96 */
1604
                AxisState *pAxisState = &controller->axis_state[event.jaxis.axis];
1605
                int nValue = event.jaxis.value;
1606
                int nCurrentDistance, nFarthestDistance;
1607
                if (!pAxisState->m_bMoving) {
1608
                    Sint16 nInitialValue;
1609
                    pAxisState->m_bMoving = SDL_GetJoystickAxisInitialState(controller->joystick, event.jaxis.axis, &nInitialValue);
1610
                    pAxisState->m_nLastValue = nValue;
1611
                    pAxisState->m_nStartingValue = nInitialValue;
1612
                    pAxisState->m_nFarthestValue = nInitialValue;
1613
                } else if (SDL_abs(nValue - pAxisState->m_nLastValue) <= MAX_ALLOWED_JITTER) {
1614
                    break;
1615
                } else {
1616
                    pAxisState->m_nLastValue = nValue;
1617
                }
1618
                nCurrentDistance = SDL_abs(nValue - pAxisState->m_nStartingValue);
1619
                nFarthestDistance = SDL_abs(pAxisState->m_nFarthestValue - pAxisState->m_nStartingValue);
1620
                if (nCurrentDistance > nFarthestDistance) {
1621
                    pAxisState->m_nFarthestValue = nValue;
1622
                    nFarthestDistance = SDL_abs(pAxisState->m_nFarthestValue - pAxisState->m_nStartingValue);
1623
                }
1624

1625
#ifdef DEBUG_AXIS_MAPPING
1626
                SDL_Log("AXIS %d nValue %d nCurrentDistance %d nFarthestDistance %d\n", event.jaxis.axis, nValue, nCurrentDistance, nFarthestDistance);
1627
#endif
1628
                /* If we've gone out far enough and started to come back, let's bind this axis */
1629
                if (nFarthestDistance >= 16000 && nCurrentDistance <= 10000) {
1630
                    char binding[12];
1631
                    int axis_min = StandardizeAxisValue(pAxisState->m_nStartingValue);
1632
                    int axis_max = StandardizeAxisValue(pAxisState->m_nFarthestValue);
1633

1634
                    if (axis_min == 0 && axis_max == SDL_JOYSTICK_AXIS_MIN) {
1635
                        /* The negative half axis */
1636
                        (void)SDL_snprintf(binding, sizeof(binding), "-a%d", event.jaxis.axis);
1637
                    } else if (axis_min == 0 && axis_max == SDL_JOYSTICK_AXIS_MAX) {
1638
                        /* The positive half axis */
1639
                        (void)SDL_snprintf(binding, sizeof(binding), "+a%d", event.jaxis.axis);
1640
                    } else {
1641
                        (void)SDL_snprintf(binding, sizeof(binding), "a%d", event.jaxis.axis);
1642
                        if (axis_min > axis_max) {
1643
                            /* Invert the axis */
1644
                            SDL_strlcat(binding, "~", SDL_arraysize(binding));
1645
                        }
1646
                    }
1647
#ifdef DEBUG_AXIS_MAPPING
1648
                    SDL_Log("AXIS %d axis_min = %d, axis_max = %d, binding = %s\n", event.jaxis.axis, axis_min, axis_max, binding);
1649
#endif
1650
                    CommitBindingElement(binding, SDL_FALSE);
1651
                }
1652
            }
1653
            break;
1654

1655
        case SDL_EVENT_JOYSTICK_BUTTON_DOWN:
1656
            if (display_mode == CONTROLLER_MODE_TESTING) {
1657
                SetController(event.jbutton.which);
1658
            }
1659
            break;
1660

1661
        case SDL_EVENT_JOYSTICK_BUTTON_UP:
1662
            if (display_mode == CONTROLLER_MODE_BINDING &&
1663
                event.jbutton.which == controller->id &&
1664
                binding_element != SDL_GAMEPAD_ELEMENT_INVALID) {
1665
                char binding[12];
1666

1667
                SDL_snprintf(binding, sizeof(binding), "b%d", event.jbutton.button);
1668
                CommitBindingElement(binding, SDL_FALSE);
1669
            }
1670
            break;
1671

1672
        case SDL_EVENT_JOYSTICK_HAT_MOTION:
1673
            if (display_mode == CONTROLLER_MODE_BINDING &&
1674
                event.jhat.which == controller->id &&
1675
                event.jhat.value != SDL_HAT_CENTERED &&
1676
                binding_element != SDL_GAMEPAD_ELEMENT_INVALID) {
1677
                char binding[12];
1678

1679
                SDL_snprintf(binding, sizeof(binding), "h%d.%d", event.jhat.hat, event.jhat.value);
1680
                CommitBindingElement(binding, SDL_FALSE);
1681
            }
1682
            break;
1683

1684
        case SDL_EVENT_GAMEPAD_ADDED:
1685
            HandleGamepadAdded(event.gdevice.which, SDL_TRUE);
1686
            break;
1687

1688
        case SDL_EVENT_GAMEPAD_REMOVED:
1689
            HandleGamepadRemoved(event.gdevice.which);
1690
            break;
1691

1692
        case SDL_EVENT_GAMEPAD_REMAPPED:
1693
            HandleGamepadRemapped(event.gdevice.which);
1694
            break;
1695

1696
        case SDL_EVENT_GAMEPAD_STEAM_HANDLE_UPDATED:
1697
            RefreshControllerName();
1698
            break;
1699

1700
#ifdef VERBOSE_TOUCHPAD
1701
        case SDL_EVENT_GAMEPAD_TOUCHPAD_DOWN:
1702
        case SDL_EVENT_GAMEPAD_TOUCHPAD_MOTION:
1703
        case SDL_EVENT_GAMEPAD_TOUCHPAD_UP:
1704
            SDL_Log("Gamepad %" SDL_PRIu32 " touchpad %" SDL_PRIs32 " finger %" SDL_PRIs32 " %s %.2f, %.2f, %.2f\n",
1705
                    event.gtouchpad.which,
1706
                    event.gtouchpad.touchpad,
1707
                    event.gtouchpad.finger,
1708
                    (event.type == SDL_EVENT_GAMEPAD_TOUCHPAD_DOWN ? "pressed at" : (event.type == SDL_EVENT_GAMEPAD_TOUCHPAD_UP ? "released at" : "moved to")),
1709
                    event.gtouchpad.x,
1710
                    event.gtouchpad.y,
1711
                    event.gtouchpad.pressure);
1712
            break;
1713
#endif /* VERBOSE_TOUCHPAD */
1714

1715
#ifdef VERBOSE_SENSORS
1716
        case SDL_EVENT_GAMEPAD_SENSOR_UPDATE:
1717
            SDL_Log("Gamepad %" SDL_PRIu32 " sensor %s: %.2f, %.2f, %.2f (%" SDL_PRIu64 ")\n",
1718
                    event.gsensor.which,
1719
                    GetSensorName((SDL_SensorType) event.gsensor.sensor),
1720
                    event.gsensor.data[0],
1721
                    event.gsensor.data[1],
1722
                    event.gsensor.data[2],
1723
                    event.gsensor.sensor_timestamp);
1724
            break;
1725
#endif /* VERBOSE_SENSORS */
1726

1727
#ifdef VERBOSE_AXES
1728
        case SDL_EVENT_GAMEPAD_AXIS_MOTION:
1729
            if (display_mode == CONTROLLER_MODE_TESTING) {
1730
                if (event.gaxis.value <= (-SDL_JOYSTICK_AXIS_MAX / 2) || event.gaxis.value >= (SDL_JOYSTICK_AXIS_MAX / 2)) {
1731
                    SetController(event.gaxis.which);
1732
                }
1733
            }
1734
            SDL_Log("Gamepad %" SDL_PRIu32 " axis %s changed to %d\n",
1735
                    event.gaxis.which,
1736
                    SDL_GetGamepadStringForAxis((SDL_GamepadAxis) event.gaxis.axis),
1737
                    event.gaxis.value);
1738
            break;
1739
#endif /* VERBOSE_AXES */
1740

1741
        case SDL_EVENT_GAMEPAD_BUTTON_DOWN:
1742
        case SDL_EVENT_GAMEPAD_BUTTON_UP:
1743
            if (display_mode == CONTROLLER_MODE_TESTING) {
1744
                if (event.type == SDL_EVENT_GAMEPAD_BUTTON_DOWN) {
1745
                    SetController(event.gbutton.which);
1746
                }
1747
            }
1748
#ifdef VERBOSE_BUTTONS
1749
            SDL_Log("Gamepad %" SDL_PRIu32 " button %s %s\n",
1750
                    event.gbutton.which,
1751
                    SDL_GetGamepadStringForButton((SDL_GamepadButton) event.gbutton.button),
1752
                    event.gbutton.state ? "pressed" : "released");
1753
#endif /* VERBOSE_BUTTONS */
1754

1755
            if (display_mode == CONTROLLER_MODE_TESTING) {
1756
                if (event.type == SDL_EVENT_GAMEPAD_BUTTON_DOWN &&
1757
                    controller && SDL_GetGamepadType(controller->gamepad) == SDL_GAMEPAD_TYPE_PS5) {
1758
                    /* Cycle PS5 audio routing when the microphone button is pressed */
1759
                    if (event.gbutton.button == SDL_GAMEPAD_BUTTON_MISC1) {
1760
                        CyclePS5AudioRoute(controller);
1761
                    }
1762

1763
                    /* Cycle PS5 trigger effects when the triangle button is pressed */
1764
                    if (event.gbutton.button == SDL_GAMEPAD_BUTTON_NORTH) {
1765
                        CyclePS5TriggerEffect(controller);
1766
                    }
1767
                }
1768
            }
1769
            break;
1770

1771
        case SDL_EVENT_MOUSE_BUTTON_DOWN:
1772
            if (virtual_joystick && controller && controller->joystick == virtual_joystick) {
1773
                VirtualGamepadMouseDown(event.button.x, event.button.y);
1774
            }
1775
            UpdateButtonHighlights(event.button.x, event.button.y, event.button.state ? SDL_TRUE : SDL_FALSE);
1776
            break;
1777

1778
        case SDL_EVENT_MOUSE_BUTTON_UP:
1779
            if (virtual_joystick && controller && controller->joystick == virtual_joystick) {
1780
                VirtualGamepadMouseUp(event.button.x, event.button.y);
1781
            }
1782

1783
            if (display_mode == CONTROLLER_MODE_TESTING) {
1784
                if (GamepadButtonContains(setup_mapping_button, event.button.x, event.button.y)) {
1785
                    SetDisplayMode(CONTROLLER_MODE_BINDING);
1786
                }
1787
            } else if (display_mode == CONTROLLER_MODE_BINDING) {
1788
                if (GamepadButtonContains(done_mapping_button, event.button.x, event.button.y)) {
1789
                    if (controller->mapping) {
1790
                        SDL_Log("Mapping complete:\n");
1791
                        SDL_Log("%s\n", controller->mapping);
1792
                    }
1793
                    SetDisplayMode(CONTROLLER_MODE_TESTING);
1794
                } else if (GamepadButtonContains(cancel_button, event.button.x, event.button.y)) {
1795
                    CancelMapping();
1796
                } else if (GamepadButtonContains(clear_button, event.button.x, event.button.y)) {
1797
                    ClearMapping();
1798
                } else if (controller->has_bindings &&
1799
                           GamepadButtonContains(copy_button, event.button.x, event.button.y)) {
1800
                    CopyMapping();
1801
                } else if (GamepadButtonContains(paste_button, event.button.x, event.button.y)) {
1802
                    PasteMapping();
1803
                } else if (title_pressed) {
1804
                    SetCurrentBindingElement(SDL_GAMEPAD_ELEMENT_NAME, SDL_FALSE);
1805
                } else if (type_pressed) {
1806
                    SetCurrentBindingElement(SDL_GAMEPAD_ELEMENT_TYPE, SDL_FALSE);
1807
                } else if (binding_element == SDL_GAMEPAD_ELEMENT_TYPE) {
1808
                    int type = GetGamepadTypeDisplayAt(gamepad_type, event.button.x, event.button.y);
1809
                    if (type != SDL_GAMEPAD_TYPE_UNSELECTED) {
1810
                        CommitGamepadType((SDL_GamepadType)type);
1811
                        StopBinding();
1812
                    }
1813
                } else {
1814
                    int gamepad_element = SDL_GAMEPAD_ELEMENT_INVALID;
1815
                    char *joystick_element;
1816

1817
                    if (controller->joystick != virtual_joystick) {
1818
                        gamepad_element = GetGamepadImageElementAt(image, event.button.x, event.button.y);
1819
                    }
1820
                    if (gamepad_element == SDL_GAMEPAD_ELEMENT_INVALID) {
1821
                        gamepad_element = GetGamepadDisplayElementAt(gamepad_elements, controller->gamepad, event.button.x, event.button.y);
1822
                    }
1823
                    if (gamepad_element != SDL_GAMEPAD_ELEMENT_INVALID) {
1824
                        /* Set this to SDL_FALSE if you don't want to start the binding flow at this point */
1825
                        const SDL_bool should_start_flow = SDL_TRUE;
1826
                        SetCurrentBindingElement(gamepad_element, should_start_flow);
1827
                    }
1828

1829
                    joystick_element = GetJoystickDisplayElementAt(joystick_elements, controller->joystick, event.button.x, event.button.y);
1830
                    if (joystick_element) {
1831
                        CommitBindingElement(joystick_element, SDL_TRUE);
1832
                        SDL_free(joystick_element);
1833
                    }
1834
                }
1835
            }
1836
            UpdateButtonHighlights(event.button.x, event.button.y, event.button.state ? SDL_TRUE : SDL_FALSE);
1837
            break;
1838

1839
        case SDL_EVENT_MOUSE_MOTION:
1840
            if (virtual_joystick && controller && controller->joystick == virtual_joystick) {
1841
                VirtualGamepadMouseMotion(event.motion.x, event.motion.y);
1842
            }
1843
            UpdateButtonHighlights(event.motion.x, event.motion.y, event.motion.state ? SDL_TRUE : SDL_FALSE);
1844
            break;
1845

1846
        case SDL_EVENT_KEY_DOWN:
1847
            if (display_mode == CONTROLLER_MODE_TESTING) {
1848
                if (event.key.key >= SDLK_0 && event.key.key <= SDLK_9) {
1849
                    if (controller && controller->gamepad) {
1850
                        int player_index = (event.key.key - SDLK_0);
1851

1852
                        SDL_SetGamepadPlayerIndex(controller->gamepad, player_index);
1853
                    }
1854
                    break;
1855
                } else if (event.key.key == SDLK_A) {
1856
                    OpenVirtualGamepad();
1857
                } else if (event.key.key == SDLK_D) {
1858
                    CloseVirtualGamepad();
1859
                } else if (event.key.key == SDLK_R && (event.key.mod & SDL_KMOD_CTRL)) {
1860
                    SDL_ReloadGamepadMappings();
1861
                } else if (event.key.key == SDLK_ESCAPE) {
1862
                    done = SDL_TRUE;
1863
                }
1864
            } else if (display_mode == CONTROLLER_MODE_BINDING) {
1865
                if (event.key.key == SDLK_C && (event.key.mod & SDL_KMOD_CTRL)) {
1866
                    if (binding_element == SDL_GAMEPAD_ELEMENT_NAME) {
1867
                        CopyControllerName();
1868
                    } else {
1869
                        CopyMapping();
1870
                    }
1871
                } else if (event.key.key == SDLK_V && (event.key.mod & SDL_KMOD_CTRL)) {
1872
                    if (binding_element == SDL_GAMEPAD_ELEMENT_NAME) {
1873
                        ClearControllerName();
1874
                        PasteControllerName();
1875
                    } else {
1876
                        PasteMapping();
1877
                    }
1878
                } else if (event.key.key == SDLK_X && (event.key.mod & SDL_KMOD_CTRL)) {
1879
                    if (binding_element == SDL_GAMEPAD_ELEMENT_NAME) {
1880
                        CopyControllerName();
1881
                        ClearControllerName();
1882
                    } else {
1883
                        CopyMapping();
1884
                        ClearMapping();
1885
                    }
1886
                } else if (event.key.key == SDLK_SPACE) {
1887
                    if (binding_element != SDL_GAMEPAD_ELEMENT_NAME) {
1888
                        ClearBinding();
1889
                    }
1890
                } else if (event.key.key == SDLK_BACKSPACE) {
1891
                    if (binding_element == SDL_GAMEPAD_ELEMENT_NAME) {
1892
                        BackspaceControllerName();
1893
                    }
1894
                } else if (event.key.key == SDLK_RETURN) {
1895
                    if (binding_element == SDL_GAMEPAD_ELEMENT_NAME) {
1896
                        StopBinding();
1897
                    }
1898
                } else if (event.key.key == SDLK_ESCAPE) {
1899
                    if (binding_element != SDL_GAMEPAD_ELEMENT_INVALID) {
1900
                        StopBinding();
1901
                    } else {
1902
                        CancelMapping();
1903
                    }
1904
                }
1905
            }
1906
            break;
1907
        case SDL_EVENT_TEXT_INPUT:
1908
            if (display_mode == CONTROLLER_MODE_BINDING) {
1909
                if (binding_element == SDL_GAMEPAD_ELEMENT_NAME) {
1910
                    AddControllerNameText(event.text.text);
1911
                }
1912
            }
1913
            break;
1914
        case SDL_EVENT_QUIT:
1915
            done = SDL_TRUE;
1916
            break;
1917
        default:
1918
            break;
1919
        }
1920
    }
1921

1922
    /* Wait 30 ms for joystick events to stop coming in,
1923
       in case a gamepad sends multiple events for a single control (e.g. axis and button for trigger)
1924
    */
1925
    if (binding_advance_time && SDL_GetTicks() > (binding_advance_time + 30)) {
1926
        if (binding_flow) {
1927
            SetNextBindingElement();
1928
        } else {
1929
            StopBinding();
1930
        }
1931
    }
1932

1933
    /* blank screen, set up for drawing this frame. */
1934
    SDL_SetRenderDrawColor(screen, 0xFF, 0xFF, 0xFF, SDL_ALPHA_OPAQUE);
1935
    SDL_RenderClear(screen);
1936
    SDL_SetRenderDrawColor(screen, 0x10, 0x10, 0x10, SDL_ALPHA_OPAQUE);
1937

1938
    if (controller) {
1939
        SetGamepadImageShowingFront(image, ShowingFront());
1940
        UpdateGamepadImageFromGamepad(image, controller->gamepad);
1941
        if (display_mode == CONTROLLER_MODE_BINDING &&
1942
            binding_element != SDL_GAMEPAD_ELEMENT_INVALID) {
1943
            SetGamepadImageElement(image, binding_element, SDL_TRUE);
1944
        }
1945
        RenderGamepadImage(image);
1946

1947
        if (binding_element == SDL_GAMEPAD_ELEMENT_TYPE) {
1948
            SetGamepadTypeDisplayRealType(gamepad_type, SDL_GetRealGamepadType(controller->gamepad));
1949
            RenderGamepadTypeDisplay(gamepad_type);
1950
        } else {
1951
            RenderGamepadDisplay(gamepad_elements, controller->gamepad);
1952
        }
1953
        RenderJoystickDisplay(joystick_elements, controller->joystick);
1954

1955
        if (display_mode == CONTROLLER_MODE_TESTING) {
1956
            RenderGamepadButton(setup_mapping_button);
1957
        } else if (display_mode == CONTROLLER_MODE_BINDING) {
1958
            DrawBindingTips(screen);
1959
            RenderGamepadButton(done_mapping_button);
1960
            RenderGamepadButton(cancel_button);
1961
            RenderGamepadButton(clear_button);
1962
            if (controller->has_bindings) {
1963
                RenderGamepadButton(copy_button);
1964
            }
1965
            RenderGamepadButton(paste_button);
1966
        }
1967

1968
        DrawGamepadInfo(screen);
1969

1970
        UpdateGamepadEffects();
1971
    } else {
1972
        DrawGamepadWaiting(screen);
1973
    }
1974
    SDL_Delay(16);
1975
    SDL_RenderPresent(screen);
1976

1977
#ifdef SDL_PLATFORM_EMSCRIPTEN
1978
    if (done) {
1979
        emscripten_cancel_main_loop();
1980
    }
1981
#endif
1982
}
1983

1984
int main(int argc, char *argv[])
1985
{
1986
    SDL_bool show_mappings = SDL_FALSE;
1987
    int i;
1988
    float content_scale;
1989
    int screen_width, screen_height;
1990
    SDL_FRect area;
1991
    int gamepad_index = -1;
1992
    SDLTest_CommonState *state;
1993

1994
    /* Initialize test framework */
1995
    state = SDLTest_CommonCreateState(argv, 0);
1996
    if (!state) {
1997
        return 1;
1998
    }
1999

2000
    SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI, "1");
2001
    SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE, "1");
2002
    SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE, "1");
2003
    SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_STEAM, "1");
2004
    SDL_SetHint(SDL_HINT_JOYSTICK_ROG_CHAKRAM, "1");
2005
    SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1");
2006
    SDL_SetHint(SDL_HINT_JOYSTICK_LINUX_DEADZONES, "1");
2007

2008
    /* Enable standard application logging */
2009
    SDL_SetLogPriority(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_INFO);
2010

2011
    /* Enable input debug logging */
2012
    SDL_SetLogPriority(SDL_LOG_CATEGORY_INPUT, SDL_LOG_PRIORITY_DEBUG);
2013

2014
    /* Parse commandline */
2015
    for (i = 1; i < argc;) {
2016
        int consumed;
2017

2018
        consumed = SDLTest_CommonArg(state, i);
2019
        if (!consumed) {
2020
            if (SDL_strcmp(argv[i], "--mappings") == 0) {
2021
                show_mappings = SDL_TRUE;
2022
                consumed = 1;
2023
            } else if (SDL_strcmp(argv[i], "--virtual") == 0) {
2024
                OpenVirtualGamepad();
2025
                consumed = 1;
2026
            } else if (gamepad_index < 0) {
2027
                char *endptr = NULL;
2028
                gamepad_index = (int)SDL_strtol(argv[i], &endptr, 0);
2029
                if (endptr != argv[i] && *endptr == '\0' && gamepad_index >= 0) {
2030
                    consumed = 1;
2031
                }
2032
            }
2033
        }
2034
        if (consumed <= 0) {
2035
            static const char *options[] = { "[--mappings]", "[--virtual]", "[index]", NULL };
2036
            SDLTest_CommonLogUsage(state, argv[0], options);
2037
            return 1;
2038
        }
2039

2040
        i += consumed;
2041
    }
2042
    if (gamepad_index < 0) {
2043
        gamepad_index = 0;
2044
    }
2045

2046
    /* Initialize SDL (Note: video is required to start event loop) */
2047
    if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK | SDL_INIT_GAMEPAD) < 0) {
2048
        SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't initialize SDL: %s\n", SDL_GetError());
2049
        return 1;
2050
    }
2051

2052
    SDL_AddGamepadMappingsFromFile("gamecontrollerdb.txt");
2053

2054
    if (show_mappings) {
2055
        int count = 0;
2056
        char **mappings = SDL_GetGamepadMappings(&count);
2057
        int map_i;
2058
        SDL_Log("Supported mappings:\n");
2059
        for (map_i = 0; map_i < count; ++map_i) {
2060
            SDL_Log("\t%s\n", mappings[map_i]);
2061
        }
2062
        SDL_Log("\n");
2063
        SDL_free(mappings);
2064
    }
2065

2066
    /* Create a window to display gamepad state */
2067
    content_scale = SDL_GetDisplayContentScale(SDL_GetPrimaryDisplay());
2068
    if (content_scale == 0.0f) {
2069
        content_scale = 1.0f;
2070
    }
2071
    screen_width = (int)SDL_ceilf(SCREEN_WIDTH * content_scale);
2072
    screen_height = (int)SDL_ceilf(SCREEN_HEIGHT * content_scale);
2073
    window = SDL_CreateWindow("SDL Controller Test", screen_width, screen_height, 0);
2074
    if (!window) {
2075
        SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't create window: %s\n", SDL_GetError());
2076
        return 2;
2077
    }
2078

2079
    screen = SDL_CreateRenderer(window, NULL);
2080
    if (!screen) {
2081
        SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't create renderer: %s\n", SDL_GetError());
2082
        SDL_DestroyWindow(window);
2083
        return 2;
2084
    }
2085

2086
    SDL_SetRenderDrawColor(screen, 0x00, 0x00, 0x00, SDL_ALPHA_OPAQUE);
2087
    SDL_RenderClear(screen);
2088
    SDL_RenderPresent(screen);
2089

2090
    /* scale for platforms that don't give you the window size you asked for. */
2091
    SDL_SetRenderLogicalPresentation(screen, (int)SCREEN_WIDTH, (int)SCREEN_HEIGHT,
2092
                                     SDL_LOGICAL_PRESENTATION_LETTERBOX,
2093
                                     SDL_SCALEMODE_LINEAR);
2094

2095

2096
    title_area.w = GAMEPAD_WIDTH;
2097
    title_area.h = FONT_CHARACTER_SIZE + 2 * BUTTON_MARGIN;
2098
    title_area.x = PANEL_WIDTH + PANEL_SPACING;
2099
    title_area.y = TITLE_HEIGHT / 2 - title_area.h / 2;
2100

2101
    type_area.w = PANEL_WIDTH - 2 * BUTTON_MARGIN;
2102
    type_area.h = FONT_CHARACTER_SIZE + 2 * BUTTON_MARGIN;
2103
    type_area.x = BUTTON_MARGIN;
2104
    type_area.y = TITLE_HEIGHT / 2 - type_area.h / 2;
2105

2106
    image = CreateGamepadImage(screen);
2107
    if (!image) {
2108
        SDL_DestroyRenderer(screen);
2109
        SDL_DestroyWindow(window);
2110
        return 2;
2111
    }
2112
    SetGamepadImagePosition(image, PANEL_WIDTH + PANEL_SPACING, TITLE_HEIGHT);
2113

2114
    gamepad_elements = CreateGamepadDisplay(screen);
2115
    area.x = 0;
2116
    area.y = TITLE_HEIGHT;
2117
    area.w = PANEL_WIDTH;
2118
    area.h = GAMEPAD_HEIGHT;
2119
    SetGamepadDisplayArea(gamepad_elements, &area);
2120

2121
    gamepad_type = CreateGamepadTypeDisplay(screen);
2122
    area.x = 0;
2123
    area.y = TITLE_HEIGHT;
2124
    area.w = PANEL_WIDTH;
2125
    area.h = GAMEPAD_HEIGHT;
2126
    SetGamepadTypeDisplayArea(gamepad_type, &area);
2127

2128
    joystick_elements = CreateJoystickDisplay(screen);
2129
    area.x = PANEL_WIDTH + PANEL_SPACING + GAMEPAD_WIDTH + PANEL_SPACING;
2130
    area.y = TITLE_HEIGHT;
2131
    area.w = PANEL_WIDTH;
2132
    area.h = GAMEPAD_HEIGHT;
2133
    SetJoystickDisplayArea(joystick_elements, &area);
2134

2135
    setup_mapping_button = CreateGamepadButton(screen, "Setup Mapping");
2136
    area.w = SDL_max(MINIMUM_BUTTON_WIDTH, GetGamepadButtonLabelWidth(setup_mapping_button) + 2 * BUTTON_PADDING);
2137
    area.h = GetGamepadButtonLabelHeight(setup_mapping_button) + 2 * BUTTON_PADDING;
2138
    area.x = BUTTON_MARGIN;
2139
    area.y = SCREEN_HEIGHT - BUTTON_MARGIN - area.h;
2140
    SetGamepadButtonArea(setup_mapping_button, &area);
2141

2142
    cancel_button = CreateGamepadButton(screen, "Cancel");
2143
    area.w = SDL_max(MINIMUM_BUTTON_WIDTH, GetGamepadButtonLabelWidth(cancel_button) + 2 * BUTTON_PADDING);
2144
    area.h = GetGamepadButtonLabelHeight(cancel_button) + 2 * BUTTON_PADDING;
2145
    area.x = BUTTON_MARGIN;
2146
    area.y = SCREEN_HEIGHT - BUTTON_MARGIN - area.h;
2147
    SetGamepadButtonArea(cancel_button, &area);
2148

2149
    clear_button = CreateGamepadButton(screen, "Clear");
2150
    area.x += area.w + BUTTON_PADDING;
2151
    area.w = SDL_max(MINIMUM_BUTTON_WIDTH, GetGamepadButtonLabelWidth(clear_button) + 2 * BUTTON_PADDING);
2152
    area.h = GetGamepadButtonLabelHeight(clear_button) + 2 * BUTTON_PADDING;
2153
    area.y = SCREEN_HEIGHT - BUTTON_MARGIN - area.h;
2154
    SetGamepadButtonArea(clear_button, &area);
2155

2156
    copy_button = CreateGamepadButton(screen, "Copy");
2157
    area.x += area.w + BUTTON_PADDING;
2158
    area.w = SDL_max(MINIMUM_BUTTON_WIDTH, GetGamepadButtonLabelWidth(copy_button) + 2 * BUTTON_PADDING);
2159
    area.h = GetGamepadButtonLabelHeight(copy_button) + 2 * BUTTON_PADDING;
2160
    area.y = SCREEN_HEIGHT - BUTTON_MARGIN - area.h;
2161
    SetGamepadButtonArea(copy_button, &area);
2162

2163
    paste_button = CreateGamepadButton(screen, "Paste");
2164
    area.x += area.w + BUTTON_PADDING;
2165
    area.w = SDL_max(MINIMUM_BUTTON_WIDTH, GetGamepadButtonLabelWidth(paste_button) + 2 * BUTTON_PADDING);
2166
    area.h = GetGamepadButtonLabelHeight(paste_button) + 2 * BUTTON_PADDING;
2167
    area.y = SCREEN_HEIGHT - BUTTON_MARGIN - area.h;
2168
    SetGamepadButtonArea(paste_button, &area);
2169

2170
    done_mapping_button = CreateGamepadButton(screen, "Done");
2171
    area.w = SDL_max(MINIMUM_BUTTON_WIDTH, GetGamepadButtonLabelWidth(done_mapping_button) + 2 * BUTTON_PADDING);
2172
    area.h = GetGamepadButtonLabelHeight(done_mapping_button) + 2 * BUTTON_PADDING;
2173
    area.x = SCREEN_WIDTH / 2 - area.w / 2;
2174
    area.y = SCREEN_HEIGHT - BUTTON_MARGIN - area.h;
2175
    SetGamepadButtonArea(done_mapping_button, &area);
2176

2177
    /* Process the initial gamepad list */
2178
    loop(NULL);
2179

2180
    if (gamepad_index < num_controllers) {
2181
        SetController(controllers[gamepad_index].id);
2182
    } else if (num_controllers > 0) {
2183
        SetController(controllers[0].id);
2184
    }
2185

2186
    /* Loop, getting gamepad events! */
2187
#ifdef SDL_PLATFORM_EMSCRIPTEN
2188
    emscripten_set_main_loop_arg(loop, NULL, 0, 1);
2189
#else
2190
    while (!done) {
2191
        loop(NULL);
2192
    }
2193
#endif
2194

2195
    CloseVirtualGamepad();
2196
    while (num_controllers > 0) {
2197
        HandleGamepadRemoved(controllers[0].id);
2198
        DelController(controllers[0].id);
2199
    }
2200
    SDL_free(controllers);
2201
    SDL_free(controller_name);
2202
    DestroyGamepadImage(image);
2203
    DestroyGamepadDisplay(gamepad_elements);
2204
    DestroyGamepadTypeDisplay(gamepad_type);
2205
    DestroyJoystickDisplay(joystick_elements);
2206
    DestroyGamepadButton(setup_mapping_button);
2207
    DestroyGamepadButton(done_mapping_button);
2208
    DestroyGamepadButton(cancel_button);
2209
    DestroyGamepadButton(clear_button);
2210
    DestroyGamepadButton(copy_button);
2211
    DestroyGamepadButton(paste_button);
2212
    SDLTest_CleanupTextDrawing();
2213
    SDL_DestroyRenderer(screen);
2214
    SDL_DestroyWindow(window);
2215
    SDL_Quit();
2216
    SDLTest_CommonDestroyState(state);
2217
    return 0;
2218
}
2219

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.