Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/thirdparty/sdl/SDL_utils.c
9903 views
1
/*
2
Simple DirectMedia Layer
3
Copyright (C) 1997-2025 Sam Lantinga <[email protected]>
4
5
This software is provided 'as-is', without any express or implied
6
warranty. In no event will the authors be held liable for any damages
7
arising from the use of this software.
8
9
Permission is granted to anyone to use this software for any purpose,
10
including commercial applications, and to alter it and redistribute it
11
freely, subject to the following restrictions:
12
13
1. The origin of this software must not be misrepresented; you must not
14
claim that you wrote the original software. If you use this software
15
in a product, an acknowledgment in the product documentation would be
16
appreciated but is not required.
17
2. Altered source versions must be plainly marked as such, and must not be
18
misrepresented as being the original software.
19
3. This notice may not be removed or altered from any source distribution.
20
*/
21
#include "SDL_internal.h"
22
23
#if defined(HAVE_GETHOSTNAME) && !defined(SDL_PLATFORM_WINDOWS)
24
#include <unistd.h>
25
#endif
26
27
#include "joystick/SDL_joystick_c.h" // For SDL_GetGamepadTypeFromVIDPID()
28
29
30
// Common utility functions that aren't in the public API
31
32
int SDL_powerof2(int x)
33
{
34
int value;
35
36
if (x <= 0) {
37
// Return some sane value - we shouldn't hit this in our use cases
38
return 1;
39
}
40
41
// This trick works for 32-bit values
42
{
43
SDL_COMPILE_TIME_ASSERT(SDL_powerof2, sizeof(x) == sizeof(Uint32));
44
}
45
value = x;
46
value -= 1;
47
value |= value >> 1;
48
value |= value >> 2;
49
value |= value >> 4;
50
value |= value >> 8;
51
value |= value >> 16;
52
value += 1;
53
54
return value;
55
}
56
57
Uint32 SDL_CalculateGCD(Uint32 a, Uint32 b)
58
{
59
if (b == 0) {
60
return a;
61
}
62
return SDL_CalculateGCD(b, (a % b));
63
}
64
65
// Algorithm adapted with thanks from John Cook's blog post:
66
// http://www.johndcook.com/blog/2010/10/20/best-rational-approximation
67
void SDL_CalculateFraction(float x, int *numerator, int *denominator)
68
{
69
const int N = 1000;
70
int a = 0, b = 1;
71
int c = 1, d = 0;
72
73
while (b <= N && d <= N) {
74
float mediant = (float)(a + c) / (b + d);
75
if (x == mediant) {
76
if (b + d <= N) {
77
*numerator = a + c;
78
*denominator = b + d;
79
} else if (d > b) {
80
*numerator = c;
81
*denominator = d;
82
} else {
83
*numerator = a;
84
*denominator = b;
85
}
86
return;
87
} else if (x > mediant) {
88
a = a + c;
89
b = b + d;
90
} else {
91
c = a + c;
92
d = b + d;
93
}
94
}
95
if (b > N) {
96
*numerator = c;
97
*denominator = d;
98
} else {
99
*numerator = a;
100
*denominator = b;
101
}
102
}
103
104
bool SDL_startswith(const char *string, const char *prefix)
105
{
106
if (SDL_strncmp(string, prefix, SDL_strlen(prefix)) == 0) {
107
return true;
108
}
109
return false;
110
}
111
112
bool SDL_endswith(const char *string, const char *suffix)
113
{
114
size_t string_length = string ? SDL_strlen(string) : 0;
115
size_t suffix_length = suffix ? SDL_strlen(suffix) : 0;
116
117
if (suffix_length > 0 && suffix_length <= string_length) {
118
if (SDL_memcmp(string + string_length - suffix_length, suffix, suffix_length) == 0) {
119
return true;
120
}
121
}
122
return false;
123
}
124
125
SDL_COMPILE_TIME_ASSERT(sizeof_object_id, sizeof(int) == sizeof(Uint32));
126
127
Uint32 SDL_GetNextObjectID(void)
128
{
129
static SDL_AtomicInt last_id;
130
131
Uint32 id = (Uint32)SDL_AtomicIncRef(&last_id) + 1;
132
if (id == 0) {
133
id = (Uint32)SDL_AtomicIncRef(&last_id) + 1;
134
}
135
return id;
136
}
137
138
static SDL_InitState SDL_objects_init;
139
static SDL_HashTable *SDL_objects;
140
141
static Uint32 SDLCALL SDL_HashObject(void *unused, const void *key)
142
{
143
return (Uint32)(uintptr_t)key;
144
}
145
146
static bool SDL_KeyMatchObject(void *unused, const void *a, const void *b)
147
{
148
return (a == b);
149
}
150
151
void SDL_SetObjectValid(void *object, SDL_ObjectType type, bool valid)
152
{
153
SDL_assert(object != NULL);
154
155
if (SDL_ShouldInit(&SDL_objects_init)) {
156
SDL_objects = SDL_CreateHashTable(0, true, SDL_HashObject, SDL_KeyMatchObject, NULL, NULL);
157
const bool initialized = (SDL_objects != NULL);
158
SDL_SetInitialized(&SDL_objects_init, initialized);
159
if (!initialized) {
160
return;
161
}
162
}
163
164
if (valid) {
165
SDL_InsertIntoHashTable(SDL_objects, object, (void *)(uintptr_t)type, true);
166
} else {
167
SDL_RemoveFromHashTable(SDL_objects, object);
168
}
169
}
170
171
bool SDL_ObjectValid(void *object, SDL_ObjectType type)
172
{
173
if (!object) {
174
return false;
175
}
176
177
const void *object_type;
178
if (!SDL_FindInHashTable(SDL_objects, object, &object_type)) {
179
return false;
180
}
181
182
return (((SDL_ObjectType)(uintptr_t)object_type) == type);
183
}
184
185
typedef struct GetOneObjectData
186
{
187
const SDL_ObjectType type;
188
void **objects;
189
const int count;
190
int num_objects;
191
} GetOneObjectData;
192
193
static bool SDLCALL GetOneObject(void *userdata, const SDL_HashTable *table, const void *object, const void *object_type)
194
{
195
GetOneObjectData *data = (GetOneObjectData *) userdata;
196
if ((SDL_ObjectType)(uintptr_t)object_type == data->type) {
197
if (data->num_objects < data->count) {
198
data->objects[data->num_objects] = (void *)object;
199
}
200
++data->num_objects;
201
}
202
return true; // keep iterating.
203
}
204
205
206
int SDL_GetObjects(SDL_ObjectType type, void **objects, int count)
207
{
208
GetOneObjectData data = { type, objects, count, 0 };
209
SDL_IterateHashTable(SDL_objects, GetOneObject, &data);
210
return data.num_objects;
211
}
212
213
static bool SDLCALL LogOneLeakedObject(void *userdata, const SDL_HashTable *table, const void *object, const void *object_type)
214
{
215
const char *type = "unknown object";
216
switch ((SDL_ObjectType)(uintptr_t)object_type) {
217
#define SDLOBJTYPECASE(typ, name) case SDL_OBJECT_TYPE_##typ: type = name; break
218
SDLOBJTYPECASE(WINDOW, "SDL_Window");
219
SDLOBJTYPECASE(RENDERER, "SDL_Renderer");
220
SDLOBJTYPECASE(TEXTURE, "SDL_Texture");
221
SDLOBJTYPECASE(JOYSTICK, "SDL_Joystick");
222
SDLOBJTYPECASE(GAMEPAD, "SDL_Gamepad");
223
SDLOBJTYPECASE(HAPTIC, "SDL_Haptic");
224
SDLOBJTYPECASE(SENSOR, "SDL_Sensor");
225
SDLOBJTYPECASE(HIDAPI_DEVICE, "hidapi device");
226
SDLOBJTYPECASE(HIDAPI_JOYSTICK, "hidapi joystick");
227
SDLOBJTYPECASE(THREAD, "thread");
228
SDLOBJTYPECASE(TRAY, "SDL_Tray");
229
#undef SDLOBJTYPECASE
230
default: break;
231
}
232
SDL_Log("Leaked %s (%p)", type, object);
233
return true; // keep iterating.
234
}
235
236
void SDL_SetObjectsInvalid(void)
237
{
238
if (SDL_ShouldQuit(&SDL_objects_init)) {
239
// Log any leaked objects
240
SDL_IterateHashTable(SDL_objects, LogOneLeakedObject, NULL);
241
SDL_assert(SDL_HashTableEmpty(SDL_objects));
242
SDL_DestroyHashTable(SDL_objects);
243
SDL_objects = NULL;
244
SDL_SetInitialized(&SDL_objects_init, false);
245
}
246
}
247
248
static int SDL_URIDecode(const char *src, char *dst, int len)
249
{
250
int ri, wi, di;
251
char decode = '\0';
252
if (!src || !dst || len < 0) {
253
return -1;
254
}
255
if (len == 0) {
256
len = (int)SDL_strlen(src);
257
}
258
for (ri = 0, wi = 0, di = 0; ri < len && wi < len; ri += 1) {
259
if (di == 0) {
260
// start decoding
261
if (src[ri] == '%') {
262
decode = '\0';
263
di += 1;
264
continue;
265
}
266
// normal write
267
dst[wi] = src[ri];
268
wi += 1;
269
} else if (di == 1 || di == 2) {
270
char off = '\0';
271
char isa = src[ri] >= 'a' && src[ri] <= 'f';
272
char isA = src[ri] >= 'A' && src[ri] <= 'F';
273
char isn = src[ri] >= '0' && src[ri] <= '9';
274
if (!(isa || isA || isn)) {
275
// not a hexadecimal
276
int sri;
277
for (sri = ri - di; sri <= ri; sri += 1) {
278
dst[wi] = src[sri];
279
wi += 1;
280
}
281
di = 0;
282
continue;
283
}
284
// itsy bitsy magicsy
285
if (isn) {
286
off = 0 - '0';
287
} else if (isa) {
288
off = 10 - 'a';
289
} else if (isA) {
290
off = 10 - 'A';
291
}
292
decode |= (src[ri] + off) << (2 - di) * 4;
293
if (di == 2) {
294
dst[wi] = decode;
295
wi += 1;
296
di = 0;
297
} else {
298
di += 1;
299
}
300
}
301
}
302
dst[wi] = '\0';
303
return wi;
304
}
305
306
int SDL_URIToLocal(const char *src, char *dst)
307
{
308
if (SDL_memcmp(src, "file:/", 6) == 0) {
309
src += 6; // local file?
310
} else if (SDL_strstr(src, ":/") != NULL) {
311
return -1; // wrong scheme
312
}
313
314
bool local = src[0] != '/' || (src[0] != '\0' && src[1] == '/');
315
316
// Check the hostname, if present. RFC 3986 states that the hostname component of a URI is not case-sensitive.
317
if (!local && src[0] == '/' && src[2] != '/') {
318
char *hostname_end = SDL_strchr(src + 1, '/');
319
if (hostname_end) {
320
const size_t src_len = hostname_end - (src + 1);
321
size_t hostname_len;
322
323
#if defined(HAVE_GETHOSTNAME) && !defined(SDL_PLATFORM_WINDOWS)
324
char hostname[257];
325
if (gethostname(hostname, 255) == 0) {
326
hostname[256] = '\0';
327
hostname_len = SDL_strlen(hostname);
328
if (hostname_len == src_len && SDL_strncasecmp(src + 1, hostname, src_len) == 0) {
329
src = hostname_end + 1;
330
local = true;
331
}
332
}
333
#endif
334
335
if (!local) {
336
static const char *localhost = "localhost";
337
hostname_len = SDL_strlen(localhost);
338
if (hostname_len == src_len && SDL_strncasecmp(src + 1, localhost, src_len) == 0) {
339
src = hostname_end + 1;
340
local = true;
341
}
342
}
343
}
344
}
345
346
if (local) {
347
// Convert URI escape sequences to real characters
348
if (src[0] == '/') {
349
src++;
350
} else {
351
src--;
352
}
353
return SDL_URIDecode(src, dst, 0);
354
}
355
return -1;
356
}
357
358
// This is a set of per-thread persistent strings that we can return from the SDL API.
359
// This is used for short strings that might persist past the lifetime of the object
360
// they are related to.
361
362
static SDL_TLSID SDL_string_storage;
363
364
static void SDL_FreePersistentStrings( void *value )
365
{
366
SDL_HashTable *strings = (SDL_HashTable *)value;
367
SDL_DestroyHashTable(strings);
368
}
369
370
const char *SDL_GetPersistentString(const char *string)
371
{
372
if (!string) {
373
return NULL;
374
}
375
if (!*string) {
376
return "";
377
}
378
379
SDL_HashTable *strings = (SDL_HashTable *)SDL_GetTLS(&SDL_string_storage);
380
if (!strings) {
381
strings = SDL_CreateHashTable(0, false, SDL_HashString, SDL_KeyMatchString, SDL_DestroyHashValue, NULL);
382
if (!strings) {
383
return NULL;
384
}
385
386
SDL_SetTLS(&SDL_string_storage, strings, SDL_FreePersistentStrings);
387
}
388
389
const char *result;
390
if (!SDL_FindInHashTable(strings, string, (const void **)&result)) {
391
char *new_string = SDL_strdup(string);
392
if (!new_string) {
393
return NULL;
394
}
395
396
// If the hash table insert fails, at least we can return the string we allocated
397
SDL_InsertIntoHashTable(strings, new_string, new_string, false);
398
result = new_string;
399
}
400
return result;
401
}
402
403
static int PrefixMatch(const char *a, const char *b)
404
{
405
int matchlen = 0;
406
// Fixes the "HORI HORl Taiko No Tatsujin Drum Controller"
407
if (SDL_strncmp(a, "HORI ", 5) == 0 && SDL_strncmp(b, "HORl ", 5) == 0) {
408
return 5;
409
}
410
while (*a && *b) {
411
if (SDL_tolower((unsigned char)*a++) == SDL_tolower((unsigned char)*b++)) {
412
++matchlen;
413
} else {
414
break;
415
}
416
}
417
return matchlen;
418
}
419
420
char *SDL_CreateDeviceName(Uint16 vendor, Uint16 product, const char *vendor_name, const char *product_name, const char *default_name)
421
{
422
static struct
423
{
424
const char *prefix;
425
const char *replacement;
426
} replacements[] = {
427
{ "8BitDo Tech Ltd", "8BitDo" },
428
{ "ASTRO Gaming", "ASTRO" },
429
{ "Bensussen Deutsch & Associates,Inc.(BDA)", "BDA" },
430
{ "Guangzhou Chicken Run Network Technology Co., Ltd.", "GameSir" },
431
{ "HORI CO.,LTD.", "HORI" },
432
{ "HORI CO.,LTD", "HORI" },
433
{ "Mad Catz Inc.", "Mad Catz" },
434
{ "Nintendo Co., Ltd.", "Nintendo" },
435
{ "NVIDIA Corporation ", "" },
436
{ "Performance Designed Products", "PDP" },
437
{ "QANBA USA, LLC", "Qanba" },
438
{ "QANBA USA,LLC", "Qanba" },
439
{ "Unknown ", "" },
440
};
441
char *name = NULL;
442
size_t i, len;
443
444
if (!vendor_name) {
445
vendor_name = "";
446
}
447
if (!product_name) {
448
product_name = "";
449
}
450
451
while (*vendor_name == ' ') {
452
++vendor_name;
453
}
454
while (*product_name == ' ') {
455
++product_name;
456
}
457
458
if (*vendor_name && *product_name) {
459
len = (SDL_strlen(vendor_name) + 1 + SDL_strlen(product_name) + 1);
460
name = (char *)SDL_malloc(len);
461
if (name) {
462
(void)SDL_snprintf(name, len, "%s %s", vendor_name, product_name);
463
}
464
} else if (*product_name) {
465
name = SDL_strdup(product_name);
466
} else if (vendor || product) {
467
// Couldn't find a controller name, try to give it one based on device type
468
switch (SDL_GetGamepadTypeFromVIDPID(vendor, product, NULL, true)) {
469
case SDL_GAMEPAD_TYPE_XBOX360:
470
name = SDL_strdup("Xbox 360 Controller");
471
break;
472
case SDL_GAMEPAD_TYPE_XBOXONE:
473
name = SDL_strdup("Xbox One Controller");
474
break;
475
case SDL_GAMEPAD_TYPE_PS3:
476
name = SDL_strdup("PS3 Controller");
477
break;
478
case SDL_GAMEPAD_TYPE_PS4:
479
name = SDL_strdup("PS4 Controller");
480
break;
481
case SDL_GAMEPAD_TYPE_PS5:
482
name = SDL_strdup("DualSense Wireless Controller");
483
break;
484
case SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_PRO:
485
name = SDL_strdup("Nintendo Switch Pro Controller");
486
break;
487
default:
488
len = (6 + 1 + 6 + 1);
489
name = (char *)SDL_malloc(len);
490
if (name) {
491
(void)SDL_snprintf(name, len, "0x%.4x/0x%.4x", vendor, product);
492
}
493
break;
494
}
495
} else if (default_name) {
496
name = SDL_strdup(default_name);
497
}
498
499
if (!name) {
500
return NULL;
501
}
502
503
// Trim trailing whitespace
504
for (len = SDL_strlen(name); (len > 0 && name[len - 1] == ' '); --len) {
505
// continue
506
}
507
name[len] = '\0';
508
509
// Compress duplicate spaces
510
for (i = 0; i < (len - 1);) {
511
if (name[i] == ' ' && name[i + 1] == ' ') {
512
SDL_memmove(&name[i], &name[i + 1], (len - i));
513
--len;
514
} else {
515
++i;
516
}
517
}
518
519
// Perform any manufacturer replacements
520
for (i = 0; i < SDL_arraysize(replacements); ++i) {
521
size_t prefixlen = SDL_strlen(replacements[i].prefix);
522
if (SDL_strncasecmp(name, replacements[i].prefix, prefixlen) == 0) {
523
size_t replacementlen = SDL_strlen(replacements[i].replacement);
524
if (replacementlen <= prefixlen) {
525
SDL_memcpy(name, replacements[i].replacement, replacementlen);
526
SDL_memmove(name + replacementlen, name + prefixlen, (len - prefixlen) + 1);
527
len -= (prefixlen - replacementlen);
528
} else {
529
// FIXME: Need to handle the expand case by reallocating the string
530
}
531
break;
532
}
533
}
534
535
/* Remove duplicate manufacturer or product in the name
536
* e.g. Razer Razer Raiju Tournament Edition Wired
537
*/
538
for (i = 1; i < (len - 1); ++i) {
539
int matchlen = PrefixMatch(name, &name[i]);
540
while (matchlen > 0) {
541
if (name[matchlen] == ' ' || name[matchlen] == '-') {
542
SDL_memmove(name, name + matchlen + 1, len - matchlen);
543
break;
544
}
545
--matchlen;
546
}
547
if (matchlen > 0) {
548
// We matched the manufacturer's name and removed it
549
break;
550
}
551
}
552
553
return name;
554
}
555
556