Atlas - SDL_utils.c
Home / ext / SDL / src Lines: 1 | Size: 17456 bytes [Download] [Show on GitHub] [Search similar files] [Raw] [Raw (proxy)][FILE BEGIN]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 32int 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 57Uint32 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 67void 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 104bool 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 112bool 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 125SDL_COMPILE_TIME_ASSERT(sizeof_object_id, sizeof(int) == sizeof(Uint32)); 126 127Uint32 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 138static SDL_InitState SDL_objects_init; 139static SDL_HashTable *SDL_objects; 140bool SDL_object_validation = true; 141 142static void SDLCALL SDL_InvalidParamChecksChanged(void *userdata, const char *name, const char *oldValue, const char *hint) 143{ 144 bool validation_enabled = true; 145 146#ifndef OBJECT_VALIDATION_REQUIRED 147 if (hint) { 148 switch (*hint) { 149 case '0': 150 case '1': 151 validation_enabled = false; 152 break; 153 case '2': 154 validation_enabled = true; 155 break; 156 default: 157 break; 158 } 159 } 160#endif // !OBJECT_VALIDATION_REQUIRED 161 162 SDL_object_validation = validation_enabled; 163} 164 165static Uint32 SDLCALL SDL_HashObject(void *unused, const void *key) 166{ 167 return (Uint32)(uintptr_t)key; 168} 169 170static bool SDL_KeyMatchObject(void *unused, const void *a, const void *b) 171{ 172 return (a == b); 173} 174 175void SDL_SetObjectValid(void *object, SDL_ObjectType type, bool valid) 176{ 177 SDL_assert(object != NULL); 178 179 if (SDL_ShouldInit(&SDL_objects_init)) { 180 SDL_objects = SDL_CreateHashTable(0, true, SDL_HashObject, SDL_KeyMatchObject, NULL, NULL); 181 const bool initialized = (SDL_objects != NULL); 182 SDL_SetInitialized(&SDL_objects_init, initialized); 183 if (!initialized) { 184 return; 185 } 186 SDL_AddHintCallback(SDL_HINT_INVALID_PARAM_CHECKS, SDL_InvalidParamChecksChanged, NULL); 187 } 188 189 if (valid) { 190 SDL_InsertIntoHashTable(SDL_objects, object, (void *)(uintptr_t)type, true); 191 } else { 192 SDL_RemoveFromHashTable(SDL_objects, object); 193 } 194} 195 196bool SDL_FindObject(void *object, SDL_ObjectType type) 197{ 198 const void *object_type; 199 if (!SDL_FindInHashTable(SDL_objects, object, &object_type)) { 200 return false; 201 } 202 203 return (((SDL_ObjectType)(uintptr_t)object_type) == type); 204} 205 206typedef struct GetOneObjectData 207{ 208 const SDL_ObjectType type; 209 void **objects; 210 const int count; 211 int num_objects; 212} GetOneObjectData; 213 214static bool SDLCALL GetOneObject(void *userdata, const SDL_HashTable *table, const void *object, const void *object_type) 215{ 216 GetOneObjectData *data = (GetOneObjectData *) userdata; 217 if ((SDL_ObjectType)(uintptr_t)object_type == data->type) { 218 if (data->num_objects < data->count) { 219 data->objects[data->num_objects] = (void *)object; 220 } 221 ++data->num_objects; 222 } 223 return true; // keep iterating. 224} 225 226 227int SDL_GetObjects(SDL_ObjectType type, void **objects, int count) 228{ 229 GetOneObjectData data = { type, objects, count, 0 }; 230 SDL_IterateHashTable(SDL_objects, GetOneObject, &data); 231 return data.num_objects; 232} 233 234static bool SDLCALL LogOneLeakedObject(void *userdata, const SDL_HashTable *table, const void *object, const void *object_type) 235{ 236 const char *type = "unknown object"; 237 switch ((SDL_ObjectType)(uintptr_t)object_type) { 238 #define SDLOBJTYPECASE(typ, name) case SDL_OBJECT_TYPE_##typ: type = name; break 239 SDLOBJTYPECASE(WINDOW, "SDL_Window"); 240 SDLOBJTYPECASE(RENDERER, "SDL_Renderer"); 241 SDLOBJTYPECASE(TEXTURE, "SDL_Texture"); 242 SDLOBJTYPECASE(JOYSTICK, "SDL_Joystick"); 243 SDLOBJTYPECASE(GAMEPAD, "SDL_Gamepad"); 244 SDLOBJTYPECASE(HAPTIC, "SDL_Haptic"); 245 SDLOBJTYPECASE(SENSOR, "SDL_Sensor"); 246 SDLOBJTYPECASE(HIDAPI_DEVICE, "hidapi device"); 247 SDLOBJTYPECASE(HIDAPI_JOYSTICK, "hidapi joystick"); 248 SDLOBJTYPECASE(THREAD, "thread"); 249 SDLOBJTYPECASE(TRAY, "SDL_Tray"); 250 #undef SDLOBJTYPECASE 251 default: break; 252 } 253 SDL_LogDebug(SDL_LOG_CATEGORY_SYSTEM, "Leaked %s (%p)", type, object); 254 return true; // keep iterating. 255} 256 257void SDL_SetObjectsInvalid(void) 258{ 259 if (SDL_ShouldQuit(&SDL_objects_init)) { 260 // Log any leaked objects 261 SDL_IterateHashTable(SDL_objects, LogOneLeakedObject, NULL); 262 SDL_DestroyHashTable(SDL_objects); 263 SDL_objects = NULL; 264 SDL_SetInitialized(&SDL_objects_init, false); 265 SDL_RemoveHintCallback(SDL_HINT_INVALID_PARAM_CHECKS, SDL_InvalidParamChecksChanged, NULL); 266 } 267} 268 269static int SDL_URIDecode(const char *src, char *dst, int len) 270{ 271 int ri, wi, di; 272 char decode = '\0'; 273 if (!src || !dst || len < 0) { 274 return -1; 275 } 276 if (len == 0) { 277 len = (int)SDL_strlen(src); 278 } 279 for (ri = 0, wi = 0, di = 0; ri < len && wi < len; ri += 1) { 280 if (di == 0) { 281 // start decoding 282 if (src[ri] == '%') { 283 decode = '\0'; 284 di += 1; 285 continue; 286 } 287 // normal write 288 dst[wi] = src[ri]; 289 wi += 1; 290 } else if (di == 1 || di == 2) { 291 char off = '\0'; 292 char isa = src[ri] >= 'a' && src[ri] <= 'f'; 293 char isA = src[ri] >= 'A' && src[ri] <= 'F'; 294 char isn = src[ri] >= '0' && src[ri] <= '9'; 295 if (!(isa || isA || isn)) { 296 // not a hexadecimal 297 int sri; 298 for (sri = ri - di; sri <= ri; sri += 1) { 299 dst[wi] = src[sri]; 300 wi += 1; 301 } 302 di = 0; 303 continue; 304 } 305 // itsy bitsy magicsy 306 if (isn) { 307 off = 0 - '0'; 308 } else if (isa) { 309 off = 10 - 'a'; 310 } else if (isA) { 311 off = 10 - 'A'; 312 } 313 decode |= (src[ri] + off) << (2 - di) * 4; 314 if (di == 2) { 315 dst[wi] = decode; 316 wi += 1; 317 di = 0; 318 } else { 319 di += 1; 320 } 321 } 322 } 323 dst[wi] = '\0'; 324 return wi; 325} 326 327int SDL_URIToLocal(const char *src, char *dst) 328{ 329 if (SDL_memcmp(src, "file:/", 6) == 0) { 330 src += 6; // local file? 331 } else if (SDL_strstr(src, ":/") != NULL) { 332 return -1; // wrong scheme 333 } 334 335 bool local = src[0] != '/' || (src[0] != '\0' && src[1] == '/'); 336 337 // Check the hostname, if present. RFC 3986 states that the hostname component of a URI is not case-sensitive. 338 if (!local && src[0] == '/' && src[2] != '/') { 339 char *hostname_end = SDL_strchr(src + 1, '/'); 340 if (hostname_end) { 341 const size_t src_len = hostname_end - (src + 1); 342 size_t hostname_len; 343 344#if defined(HAVE_GETHOSTNAME) && !defined(SDL_PLATFORM_WINDOWS) 345 char hostname[257]; 346 if (gethostname(hostname, 255) == 0) { 347 hostname[256] = '\0'; 348 hostname_len = SDL_strlen(hostname); 349 if (hostname_len == src_len && SDL_strncasecmp(src + 1, hostname, src_len) == 0) { 350 src = hostname_end + 1; 351 local = true; 352 } 353 } 354#endif 355 356 if (!local) { 357 static const char *localhost = "localhost"; 358 hostname_len = SDL_strlen(localhost); 359 if (hostname_len == src_len && SDL_strncasecmp(src + 1, localhost, src_len) == 0) { 360 src = hostname_end + 1; 361 local = true; 362 } 363 } 364 } 365 } 366 367 if (local) { 368 // Convert URI escape sequences to real characters 369 if (src[0] == '/') { 370 src++; 371 } else { 372 src--; 373 } 374 return SDL_URIDecode(src, dst, 0); 375 } 376 return -1; 377} 378 379// This is a set of per-thread persistent strings that we can return from the SDL API. 380// This is used for short strings that might persist past the lifetime of the object 381// they are related to. 382 383static SDL_TLSID SDL_string_storage; 384 385static void SDL_FreePersistentStrings( void *value ) 386{ 387 SDL_HashTable *strings = (SDL_HashTable *)value; 388 SDL_DestroyHashTable(strings); 389} 390 391const char *SDL_GetPersistentString(const char *string) 392{ 393 if (!string) { 394 return NULL; 395 } 396 if (!*string) { 397 return ""; 398 } 399 400 SDL_HashTable *strings = (SDL_HashTable *)SDL_GetTLS(&SDL_string_storage); 401 if (!strings) { 402 strings = SDL_CreateHashTable(0, false, SDL_HashString, SDL_KeyMatchString, SDL_DestroyHashValue, NULL); 403 if (!strings) { 404 return NULL; 405 } 406 407 SDL_SetTLS(&SDL_string_storage, strings, SDL_FreePersistentStrings); 408 } 409 410 const char *result; 411 if (!SDL_FindInHashTable(strings, string, (const void **)&result)) { 412 char *new_string = SDL_strdup(string); 413 if (!new_string) { 414 return NULL; 415 } 416 417 // If the hash table insert fails, at least we can return the string we allocated 418 SDL_InsertIntoHashTable(strings, new_string, new_string, false); 419 result = new_string; 420 } 421 return result; 422} 423 424static int PrefixMatch(const char *a, const char *b) 425{ 426 int matchlen = 0; 427 // Fixes the "HORI HORl Taiko No Tatsujin Drum Controller" 428 if (SDL_strncmp(a, "HORI ", 5) == 0 && SDL_strncmp(b, "HORl ", 5) == 0) { 429 return 5; 430 } 431 while (*a && *b) { 432 if (SDL_tolower((unsigned char)*a++) == SDL_tolower((unsigned char)*b++)) { 433 ++matchlen; 434 } else { 435 break; 436 } 437 } 438 return matchlen; 439} 440 441char *SDL_CreateDeviceName(Uint16 vendor, Uint16 product, const char *vendor_name, const char *product_name, const char *default_name) 442{ 443 static struct 444 { 445 const char *prefix; 446 const char *replacement; 447 } replacements[] = { 448 { "8BitDo Tech Ltd", "8BitDo" }, 449 { "ASTRO Gaming", "ASTRO" }, 450 { "Bensussen Deutsch & Associates,Inc.(BDA)", "BDA" }, 451 { "Guangzhou Chicken Run Network Technology Co., Ltd.", "GameSir" }, 452 { "HORI CO.,LTD.", "HORI" }, 453 { "HORI CO.,LTD", "HORI" }, 454 { "Mad Catz Inc.", "Mad Catz" }, 455 { "Nintendo Co., Ltd.", "Nintendo" }, 456 { "NVIDIA Corporation ", "" }, 457 { "Performance Designed Products", "PDP" }, 458 { "QANBA USA, LLC", "Qanba" }, 459 { "QANBA USA,LLC", "Qanba" }, 460 { "Unknown ", "" }, 461 }; 462 char *name = NULL; 463 size_t i, len; 464 465 if (!vendor_name) { 466 vendor_name = ""; 467 } 468 if (!product_name) { 469 product_name = ""; 470 } 471 472 while (*vendor_name == ' ') { 473 ++vendor_name; 474 } 475 while (*product_name == ' ') { 476 ++product_name; 477 } 478 479 if (*vendor_name && *product_name) { 480 len = (SDL_strlen(vendor_name) + 1 + SDL_strlen(product_name) + 1); 481 name = (char *)SDL_malloc(len); 482 if (name) { 483 (void)SDL_snprintf(name, len, "%s %s", vendor_name, product_name); 484 } 485 } else if (*product_name) { 486 name = SDL_strdup(product_name); 487 } else if (vendor || product) { 488 // Couldn't find a controller name, try to give it one based on device type 489 switch (SDL_GetGamepadTypeFromVIDPID(vendor, product, NULL, true)) { 490 case SDL_GAMEPAD_TYPE_XBOX360: 491 name = SDL_strdup("Xbox 360 Controller"); 492 break; 493 case SDL_GAMEPAD_TYPE_XBOXONE: 494 name = SDL_strdup("Xbox One Controller"); 495 break; 496 case SDL_GAMEPAD_TYPE_PS3: 497 name = SDL_strdup("PS3 Controller"); 498 break; 499 case SDL_GAMEPAD_TYPE_PS4: 500 name = SDL_strdup("PS4 Controller"); 501 break; 502 case SDL_GAMEPAD_TYPE_PS5: 503 name = SDL_strdup("DualSense Wireless Controller"); 504 break; 505 case SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_PRO: 506 name = SDL_strdup("Nintendo Switch Pro Controller"); 507 break; 508 default: 509 len = (6 + 1 + 6 + 1); 510 name = (char *)SDL_malloc(len); 511 if (name) { 512 (void)SDL_snprintf(name, len, "0x%.4x/0x%.4x", vendor, product); 513 } 514 break; 515 } 516 } else if (default_name) { 517 name = SDL_strdup(default_name); 518 } 519 520 if (!name) { 521 return NULL; 522 } 523 524 // Trim trailing whitespace 525 for (len = SDL_strlen(name); (len > 0 && name[len - 1] == ' '); --len) { 526 // continue 527 } 528 name[len] = '\0'; 529 530 // Compress duplicate spaces 531 for (i = 0; i < (len - 1);) { 532 if (name[i] == ' ' && name[i + 1] == ' ') { 533 SDL_memmove(&name[i], &name[i + 1], (len - i)); 534 --len; 535 } else { 536 ++i; 537 } 538 } 539 540 // Perform any manufacturer replacements 541 for (i = 0; i < SDL_arraysize(replacements); ++i) { 542 size_t prefixlen = SDL_strlen(replacements[i].prefix); 543 if (SDL_strncasecmp(name, replacements[i].prefix, prefixlen) == 0) { 544 size_t replacementlen = SDL_strlen(replacements[i].replacement); 545 if (replacementlen <= prefixlen) { 546 SDL_memcpy(name, replacements[i].replacement, replacementlen); 547 SDL_memmove(name + replacementlen, name + prefixlen, (len - prefixlen) + 1); 548 len -= (prefixlen - replacementlen); 549 } else { 550 // FIXME: Need to handle the expand case by reallocating the string 551 } 552 break; 553 } 554 } 555 556 /* Remove duplicate manufacturer or product in the name 557 * e.g. Razer Razer Raiju Tournament Edition Wired 558 */ 559 for (i = 1; i < (len - 1); ++i) { 560 int matchlen = PrefixMatch(name, &name[i]); 561 while (matchlen > 0) { 562 if (name[matchlen] == ' ' || name[matchlen] == '-') { 563 SDL_memmove(name, name + matchlen + 1, len - matchlen); 564 break; 565 } 566 --matchlen; 567 } 568 if (matchlen > 0) { 569 // We matched the manufacturer's name and removed it 570 break; 571 } 572 } 573 574 return name; 575} 576 577 578void SDL_DebugLogBackend(const char *subsystem, const char *backend) 579{ 580 SDL_LogDebug(SDL_LOG_CATEGORY_SYSTEM, "SDL chose %s backend '%s'", subsystem, backend); 581} 582 583[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.