Atlas - SDL_utils.c
Home / ext / SDL / src Lines: 1 | Size: 18567 bytes [Download] [Show on GitHub] [Search similar files] [Raw] [Raw (proxy)][FILE BEGIN]1/* 2 Simple DirectMedia Layer 3 Copyright (C) 1997-2026 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#ifdef SDL_PLATFORM_EMSCRIPTEN 30#include <emscripten.h> 31 32EMSCRIPTEN_KEEPALIVE void Emscripten_force_free(void *ptr) 33{ 34 free(ptr); // This should NOT be SDL_free() 35} 36#endif 37 38// Common utility functions that aren't in the public API 39 40int SDL_powerof2(int x) 41{ 42 int value; 43 44 if (x <= 0) { 45 // Return some sane value - we shouldn't hit this in our use cases 46 return 1; 47 } 48 49 // This trick works for 32-bit values 50 { 51 SDL_COMPILE_TIME_ASSERT(SDL_powerof2, sizeof(x) == sizeof(Uint32)); 52 } 53 value = x; 54 value -= 1; 55 value |= value >> 1; 56 value |= value >> 2; 57 value |= value >> 4; 58 value |= value >> 8; 59 value |= value >> 16; 60 value += 1; 61 62 return value; 63} 64 65Uint32 SDL_CalculateGCD(Uint32 a, Uint32 b) 66{ 67 if (b == 0) { 68 return a; 69 } 70 return SDL_CalculateGCD(b, (a % b)); 71} 72 73// Algorithm adapted with thanks from John Cook's blog post: 74// http://www.johndcook.com/blog/2010/10/20/best-rational-approximation 75void SDL_CalculateFraction(float x, int *numerator, int *denominator) 76{ 77 const int N = 1000; 78 int a = 0, b = 1; 79 int c = 1, d = 0; 80 81 while (b <= N && d <= N) { 82 float mediant = (float)(a + c) / (b + d); 83 if (x == mediant) { 84 if (b + d <= N) { 85 *numerator = a + c; 86 *denominator = b + d; 87 } else if (d > b) { 88 *numerator = c; 89 *denominator = d; 90 } else { 91 *numerator = a; 92 *denominator = b; 93 } 94 return; 95 } else if (x > mediant) { 96 a = a + c; 97 b = b + d; 98 } else { 99 c = a + c; 100 d = b + d; 101 } 102 } 103 if (b > N) { 104 *numerator = c; 105 *denominator = d; 106 } else { 107 *numerator = a; 108 *denominator = b; 109 } 110} 111 112bool SDL_startswith(const char *string, const char *prefix) 113{ 114 if (string && prefix && 115 SDL_strncmp(string, prefix, SDL_strlen(prefix)) == 0) { 116 return true; 117 } 118 return false; 119} 120 121bool SDL_endswith(const char *string, const char *suffix) 122{ 123 size_t string_length = string ? SDL_strlen(string) : 0; 124 size_t suffix_length = suffix ? SDL_strlen(suffix) : 0; 125 126 if (suffix_length > 0 && suffix_length <= string_length) { 127 if (SDL_memcmp(string + string_length - suffix_length, suffix, suffix_length) == 0) { 128 return true; 129 } 130 } 131 return false; 132} 133 134SDL_COMPILE_TIME_ASSERT(sizeof_object_id, sizeof(int) == sizeof(Uint32)); 135 136Uint32 SDL_GetNextObjectID(void) 137{ 138 static SDL_AtomicInt last_id; 139 140 Uint32 id = (Uint32)SDL_AtomicIncRef(&last_id) + 1; 141 if (id == 0) { 142 id = (Uint32)SDL_AtomicIncRef(&last_id) + 1; 143 } 144 return id; 145} 146 147static SDL_InitState SDL_objects_init; 148static SDL_HashTable *SDL_objects; 149bool SDL_object_validation = true; 150 151static void SDLCALL SDL_InvalidParamChecksChanged(void *userdata, const char *name, const char *oldValue, const char *hint) 152{ 153 bool validation_enabled = true; 154 155#ifndef OBJECT_VALIDATION_REQUIRED 156 if (hint) { 157 switch (*hint) { 158 case '0': 159 case '1': 160 validation_enabled = false; 161 break; 162 case '2': 163 validation_enabled = true; 164 break; 165 default: 166 break; 167 } 168 } 169#endif // !OBJECT_VALIDATION_REQUIRED 170 171 SDL_object_validation = validation_enabled; 172} 173 174static Uint32 SDLCALL SDL_HashObject(void *unused, const void *key) 175{ 176 return (Uint32)(uintptr_t)key; 177} 178 179static bool SDL_KeyMatchObject(void *unused, const void *a, const void *b) 180{ 181 return (a == b); 182} 183 184void SDL_SetObjectValid(void *object, SDL_ObjectType type, bool valid) 185{ 186 SDL_assert(object != NULL); 187 188 if (SDL_ShouldInit(&SDL_objects_init)) { 189 SDL_objects = SDL_CreateHashTable(0, true, SDL_HashObject, SDL_KeyMatchObject, NULL, NULL); 190 const bool initialized = (SDL_objects != NULL); 191 SDL_SetInitialized(&SDL_objects_init, initialized); 192 if (!initialized) { 193 return; 194 } 195 SDL_AddHintCallback(SDL_HINT_INVALID_PARAM_CHECKS, SDL_InvalidParamChecksChanged, NULL); 196 } 197 198 if (valid) { 199 SDL_InsertIntoHashTable(SDL_objects, object, (void *)(uintptr_t)type, true); 200 } else { 201 SDL_RemoveFromHashTable(SDL_objects, object); 202 } 203} 204 205bool SDL_FindObject(void *object, SDL_ObjectType type) 206{ 207 const void *object_type; 208 if (!SDL_FindInHashTable(SDL_objects, object, &object_type)) { 209 return false; 210 } 211 212 return (((SDL_ObjectType)(uintptr_t)object_type) == type); 213} 214 215typedef struct GetOneObjectData 216{ 217 const SDL_ObjectType type; 218 void **objects; 219 const int count; 220 int num_objects; 221} GetOneObjectData; 222 223static bool SDLCALL GetOneObject(void *userdata, const SDL_HashTable *table, const void *object, const void *object_type) 224{ 225 GetOneObjectData *data = (GetOneObjectData *) userdata; 226 if ((SDL_ObjectType)(uintptr_t)object_type == data->type) { 227 if (data->num_objects < data->count) { 228 data->objects[data->num_objects] = (void *)object; 229 } 230 ++data->num_objects; 231 } 232 return true; // keep iterating. 233} 234 235 236int SDL_GetObjects(SDL_ObjectType type, void **objects, int count) 237{ 238 GetOneObjectData data = { type, objects, count, 0 }; 239 SDL_IterateHashTable(SDL_objects, GetOneObject, &data); 240 return data.num_objects; 241} 242 243static bool SDLCALL LogOneLeakedObject(void *userdata, const SDL_HashTable *table, const void *object, const void *object_type) 244{ 245 const char *type = "unknown object"; 246 switch ((SDL_ObjectType)(uintptr_t)object_type) { 247 #define SDLOBJTYPECASE(typ, name) case SDL_OBJECT_TYPE_##typ: type = name; break 248 SDLOBJTYPECASE(WINDOW, "SDL_Window"); 249 SDLOBJTYPECASE(RENDERER, "SDL_Renderer"); 250 SDLOBJTYPECASE(TEXTURE, "SDL_Texture"); 251 SDLOBJTYPECASE(JOYSTICK, "SDL_Joystick"); 252 SDLOBJTYPECASE(GAMEPAD, "SDL_Gamepad"); 253 SDLOBJTYPECASE(HAPTIC, "SDL_Haptic"); 254 SDLOBJTYPECASE(SENSOR, "SDL_Sensor"); 255 SDLOBJTYPECASE(HIDAPI_DEVICE, "hidapi device"); 256 SDLOBJTYPECASE(HIDAPI_JOYSTICK, "hidapi joystick"); 257 SDLOBJTYPECASE(THREAD, "thread"); 258 SDLOBJTYPECASE(TRAY, "SDL_Tray"); 259 #undef SDLOBJTYPECASE 260 default: break; 261 } 262 SDL_LogDebug(SDL_LOG_CATEGORY_SYSTEM, "Leaked %s (%p)", type, object); 263 return true; // keep iterating. 264} 265 266void SDL_SetObjectsInvalid(void) 267{ 268 if (SDL_ShouldQuit(&SDL_objects_init)) { 269 // Log any leaked objects 270 SDL_IterateHashTable(SDL_objects, LogOneLeakedObject, NULL); 271 SDL_DestroyHashTable(SDL_objects); 272 SDL_objects = NULL; 273 SDL_SetInitialized(&SDL_objects_init, false); 274 SDL_RemoveHintCallback(SDL_HINT_INVALID_PARAM_CHECKS, SDL_InvalidParamChecksChanged, NULL); 275 } 276} 277 278static int SDL_URIDecode(const char *src, char *dst, int len) 279{ 280 int ri, wi, di; 281 char decode = '\0'; 282 if (!src || !dst || len < 0) { 283 return -1; 284 } 285 if (len == 0) { 286 len = (int)SDL_strlen(src); 287 } 288 for (ri = 0, wi = 0, di = 0; ri < len && wi < len; ri += 1) { 289 if (di == 0) { 290 // start decoding 291 if (src[ri] == '%') { 292 decode = '\0'; 293 di += 1; 294 continue; 295 } 296 // normal write 297 dst[wi] = src[ri]; 298 wi += 1; 299 } else if (di == 1 || di == 2) { 300 char off = '\0'; 301 char isa = src[ri] >= 'a' && src[ri] <= 'f'; 302 char isA = src[ri] >= 'A' && src[ri] <= 'F'; 303 char isn = src[ri] >= '0' && src[ri] <= '9'; 304 if (!(isa || isA || isn)) { 305 // not a hexadecimal 306 int sri; 307 for (sri = ri - di; sri <= ri; sri += 1) { 308 dst[wi] = src[sri]; 309 wi += 1; 310 } 311 di = 0; 312 continue; 313 } 314 // itsy bitsy magicsy 315 if (isn) { 316 off = 0 - '0'; 317 } else if (isa) { 318 off = 10 - 'a'; 319 } else if (isA) { 320 off = 10 - 'A'; 321 } 322 decode |= (src[ri] + off) << (2 - di) * 4; 323 if (di == 2) { 324 dst[wi] = decode; 325 wi += 1; 326 di = 0; 327 } else { 328 di += 1; 329 } 330 } 331 } 332 dst[wi] = '\0'; 333 return wi; 334} 335 336int SDL_URIToLocal(const char *src, char *dst) 337{ 338 if (SDL_memcmp(src, "file:/", 6) == 0) { 339 src += 6; // local file? 340 } else if (SDL_strstr(src, ":/") != NULL) { 341 return -1; // wrong scheme 342 } 343 344 bool local = src[0] != '/' || (src[0] != '\0' && src[1] == '/'); 345 346 // Check the hostname, if present. RFC 3986 states that the hostname component of a URI is not case-sensitive. 347 if (!local && src[0] == '/' && src[2] != '/') { 348 char *hostname_end = SDL_strchr(src + 1, '/'); 349 if (hostname_end) { 350 const size_t src_len = hostname_end - (src + 1); 351 size_t hostname_len; 352 353#if defined(HAVE_GETHOSTNAME) && !defined(SDL_PLATFORM_WINDOWS) 354 char hostname[257]; 355 if (gethostname(hostname, 255) == 0) { 356 hostname[256] = '\0'; 357 hostname_len = SDL_strlen(hostname); 358 if (hostname_len == src_len && SDL_strncasecmp(src + 1, hostname, src_len) == 0) { 359 src = hostname_end + 1; 360 local = true; 361 } 362 } 363#endif 364 365 if (!local) { 366 static const char *localhost = "localhost"; 367 hostname_len = SDL_strlen(localhost); 368 if (hostname_len == src_len && SDL_strncasecmp(src + 1, localhost, src_len) == 0) { 369 src = hostname_end + 1; 370 local = true; 371 } 372 } 373 } 374 } 375 376 if (local) { 377 // Convert URI escape sequences to real characters 378 if (src[0] == '/') { 379 src++; 380 } else { 381 src--; 382 } 383 return SDL_URIDecode(src, dst, 0); 384 } 385 return -1; 386} 387 388bool SDL_IsURI(const char *uri) 389{ 390 /* A valid URI begins with a letter and is followed by any sequence of 391 * letters, digits, '+', '.', or '-'. 392 */ 393 if (!uri) { 394 return false; 395 } 396 397 // The first character of the scheme must be a letter. 398 if (!((*uri >= 'a' && *uri <= 'z') || (*uri >= 'A' && *uri <= 'Z'))) { 399 return false; 400 } 401 402 /* If the colon is found before encountering the end of the string or 403 * any invalid characters, the scheme can be considered valid. 404 */ 405 while (*uri) { 406 if (!((*uri >= 'a' && *uri <= 'z') || 407 (*uri >= 'A' && *uri <= 'Z') || 408 (*uri >= '0' && *uri <= '9') || 409 *uri == '+' || *uri == '-' || *uri == '.')) { 410 return false; 411 } 412 413 if (*++uri == ':') { 414 return true; 415 } 416 } 417 418 return false; 419} 420 421// This is a set of per-thread persistent strings that we can return from the SDL API. 422// This is used for short strings that might persist past the lifetime of the object 423// they are related to. 424 425static SDL_TLSID SDL_string_storage; 426 427static void SDL_FreePersistentStrings( void *value ) 428{ 429 SDL_HashTable *strings = (SDL_HashTable *)value; 430 SDL_DestroyHashTable(strings); 431} 432 433const char *SDL_GetPersistentString(const char *string) 434{ 435 if (!string) { 436 return NULL; 437 } 438 if (!*string) { 439 return ""; 440 } 441 442 SDL_HashTable *strings = (SDL_HashTable *)SDL_GetTLS(&SDL_string_storage); 443 if (!strings) { 444 strings = SDL_CreateHashTable(0, false, SDL_HashString, SDL_KeyMatchString, SDL_DestroyHashValue, NULL); 445 if (!strings) { 446 return NULL; 447 } 448 449 SDL_SetTLS(&SDL_string_storage, strings, SDL_FreePersistentStrings); 450 } 451 452 const char *result; 453 if (!SDL_FindInHashTable(strings, string, (const void **)&result)) { 454 char *new_string = SDL_strdup(string); 455 if (!new_string) { 456 return NULL; 457 } 458 459 // If the hash table insert fails, at least we can return the string we allocated 460 SDL_InsertIntoHashTable(strings, new_string, new_string, false); 461 result = new_string; 462 } 463 return result; 464} 465 466static int PrefixMatch(const char *a, const char *b) 467{ 468 int matchlen = 0; 469 // Fixes the "HORI HORl Taiko No Tatsujin Drum Controller" 470 if (SDL_strncmp(a, "HORI ", 5) == 0 && SDL_strncmp(b, "HORl ", 5) == 0) { 471 return 5; 472 } 473 while (*a && *b) { 474 if (SDL_tolower((unsigned char)*a++) == SDL_tolower((unsigned char)*b++)) { 475 ++matchlen; 476 } else { 477 break; 478 } 479 } 480 return matchlen; 481} 482 483char *SDL_CreateDeviceName(Uint16 vendor, Uint16 product, const char *vendor_name, const char *product_name, const char *default_name) 484{ 485 static struct 486 { 487 const char *prefix; 488 const char *replacement; 489 } replacements[] = { 490 { "(Standard system devices) ", "" }, 491 { "8BitDo Tech Ltd", "8BitDo" }, 492 { "ASTRO Gaming", "ASTRO" }, 493 { "Bensussen Deutsch & Associates,Inc.(BDA)", "BDA" }, 494 { "Guangzhou Chicken Run Network Technology Co., Ltd.", "GameSir" }, 495 { "HORI CO.,LTD.", "HORI" }, 496 { "HORI CO.,LTD", "HORI" }, 497 { "Mad Catz Inc.", "Mad Catz" }, 498 { "Nintendo Co., Ltd.", "Nintendo" }, 499 { "NVIDIA Corporation ", "" }, 500 { "Performance Designed Products", "PDP" }, 501 { "QANBA USA, LLC", "Qanba" }, 502 { "QANBA USA,LLC", "Qanba" }, 503 { "Unknown ", "" }, 504 }; 505 char *name = NULL; 506 size_t i, len; 507 508 if (!vendor_name) { 509 vendor_name = ""; 510 } 511 if (!product_name) { 512 product_name = ""; 513 } 514 515 while (*vendor_name == ' ') { 516 ++vendor_name; 517 } 518 while (*product_name == ' ') { 519 ++product_name; 520 } 521 522 if (*vendor_name && *product_name) { 523 len = (SDL_strlen(vendor_name) + 1 + SDL_strlen(product_name) + 1); 524 name = (char *)SDL_malloc(len); 525 if (name) { 526 (void)SDL_snprintf(name, len, "%s %s", vendor_name, product_name); 527 } 528 } else if (*product_name) { 529 name = SDL_strdup(product_name); 530 } else if (vendor || product) { 531 // Couldn't find a controller name, try to give it one based on device type 532 switch (SDL_GetGamepadTypeFromVIDPID(vendor, product, NULL, true)) { 533 case SDL_GAMEPAD_TYPE_XBOX360: 534 name = SDL_strdup("Xbox 360 Controller"); 535 break; 536 case SDL_GAMEPAD_TYPE_XBOXONE: 537 name = SDL_strdup("Xbox One Controller"); 538 break; 539 case SDL_GAMEPAD_TYPE_PS3: 540 name = SDL_strdup("PS3 Controller"); 541 break; 542 case SDL_GAMEPAD_TYPE_PS4: 543 name = SDL_strdup("PS4 Controller"); 544 break; 545 case SDL_GAMEPAD_TYPE_PS5: 546 name = SDL_strdup("DualSense Wireless Controller"); 547 break; 548 case SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_PRO: 549 name = SDL_strdup("Nintendo Switch Pro Controller"); 550 break; 551 default: 552 len = (6 + 1 + 6 + 1); 553 name = (char *)SDL_malloc(len); 554 if (name) { 555 (void)SDL_snprintf(name, len, "0x%.4x/0x%.4x", vendor, product); 556 } 557 break; 558 } 559 } else if (default_name) { 560 name = SDL_strdup(default_name); 561 } 562 563 if (!name) { 564 return NULL; 565 } 566 567 // Trim trailing whitespace 568 for (len = SDL_strlen(name); (len > 0 && name[len - 1] == ' '); --len) { 569 // continue 570 } 571 name[len] = '\0'; 572 573 // Compress duplicate spaces 574 for (i = 0; i < (len - 1);) { 575 if (name[i] == ' ' && name[i + 1] == ' ') { 576 SDL_memmove(&name[i], &name[i + 1], (len - i)); 577 --len; 578 } else { 579 ++i; 580 } 581 } 582 583 // Perform any manufacturer replacements 584 for (i = 0; i < SDL_arraysize(replacements); ++i) { 585 size_t prefixlen = SDL_strlen(replacements[i].prefix); 586 if (SDL_strncasecmp(name, replacements[i].prefix, prefixlen) == 0) { 587 size_t replacementlen = SDL_strlen(replacements[i].replacement); 588 if (replacementlen <= prefixlen) { 589 SDL_memcpy(name, replacements[i].replacement, replacementlen); 590 SDL_memmove(name + replacementlen, name + prefixlen, (len - prefixlen) + 1); 591 len -= (prefixlen - replacementlen); 592 } else { 593 // FIXME: Need to handle the expand case by reallocating the string 594 } 595 break; 596 } 597 } 598 599 /* Remove duplicate manufacturer or product in the name 600 * e.g. Razer Razer Raiju Tournament Edition Wired 601 */ 602 for (i = 1; i < (len - 1); ++i) { 603 int matchlen = PrefixMatch(name, &name[i]); 604 while (matchlen > 0) { 605 if (name[matchlen] == ' ' || name[matchlen] == '-') { 606 SDL_memmove(name, name + matchlen + 1, len - matchlen); 607 break; 608 } 609 --matchlen; 610 } 611 if (matchlen > 0) { 612 // We matched the manufacturer's name and removed it 613 break; 614 } 615 } 616 617 return name; 618} 619 620 621void SDL_DebugLogBackend(const char *subsystem, const char *backend) 622{ 623 SDL_LogDebug(SDL_LOG_CATEGORY_SYSTEM, "SDL chose %s backend '%s'", subsystem, backend); 624} 625 626[FILE END](C) 2025 0x4248 (C) 2025 4248 Media and 4248 Systems, All part of 0x4248 See LICENCE files for more information. Not all files are by 0x4248 always check Licencing.