Atlas - SDL_utils.c
Home / ext / SDL / src Lines: 1 | Size: 17502 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 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 { "(Standard system devices) ", "" }, 449 { "8BitDo Tech Ltd", "8BitDo" }, 450 { "ASTRO Gaming", "ASTRO" }, 451 { "Bensussen Deutsch & Associates,Inc.(BDA)", "BDA" }, 452 { "Guangzhou Chicken Run Network Technology Co., Ltd.", "GameSir" }, 453 { "HORI CO.,LTD.", "HORI" }, 454 { "HORI CO.,LTD", "HORI" }, 455 { "Mad Catz Inc.", "Mad Catz" }, 456 { "Nintendo Co., Ltd.", "Nintendo" }, 457 { "NVIDIA Corporation ", "" }, 458 { "Performance Designed Products", "PDP" }, 459 { "QANBA USA, LLC", "Qanba" }, 460 { "QANBA USA,LLC", "Qanba" }, 461 { "Unknown ", "" }, 462 }; 463 char *name = NULL; 464 size_t i, len; 465 466 if (!vendor_name) { 467 vendor_name = ""; 468 } 469 if (!product_name) { 470 product_name = ""; 471 } 472 473 while (*vendor_name == ' ') { 474 ++vendor_name; 475 } 476 while (*product_name == ' ') { 477 ++product_name; 478 } 479 480 if (*vendor_name && *product_name) { 481 len = (SDL_strlen(vendor_name) + 1 + SDL_strlen(product_name) + 1); 482 name = (char *)SDL_malloc(len); 483 if (name) { 484 (void)SDL_snprintf(name, len, "%s %s", vendor_name, product_name); 485 } 486 } else if (*product_name) { 487 name = SDL_strdup(product_name); 488 } else if (vendor || product) { 489 // Couldn't find a controller name, try to give it one based on device type 490 switch (SDL_GetGamepadTypeFromVIDPID(vendor, product, NULL, true)) { 491 case SDL_GAMEPAD_TYPE_XBOX360: 492 name = SDL_strdup("Xbox 360 Controller"); 493 break; 494 case SDL_GAMEPAD_TYPE_XBOXONE: 495 name = SDL_strdup("Xbox One Controller"); 496 break; 497 case SDL_GAMEPAD_TYPE_PS3: 498 name = SDL_strdup("PS3 Controller"); 499 break; 500 case SDL_GAMEPAD_TYPE_PS4: 501 name = SDL_strdup("PS4 Controller"); 502 break; 503 case SDL_GAMEPAD_TYPE_PS5: 504 name = SDL_strdup("DualSense Wireless Controller"); 505 break; 506 case SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_PRO: 507 name = SDL_strdup("Nintendo Switch Pro Controller"); 508 break; 509 default: 510 len = (6 + 1 + 6 + 1); 511 name = (char *)SDL_malloc(len); 512 if (name) { 513 (void)SDL_snprintf(name, len, "0x%.4x/0x%.4x", vendor, product); 514 } 515 break; 516 } 517 } else if (default_name) { 518 name = SDL_strdup(default_name); 519 } 520 521 if (!name) { 522 return NULL; 523 } 524 525 // Trim trailing whitespace 526 for (len = SDL_strlen(name); (len > 0 && name[len - 1] == ' '); --len) { 527 // continue 528 } 529 name[len] = '\0'; 530 531 // Compress duplicate spaces 532 for (i = 0; i < (len - 1);) { 533 if (name[i] == ' ' && name[i + 1] == ' ') { 534 SDL_memmove(&name[i], &name[i + 1], (len - i)); 535 --len; 536 } else { 537 ++i; 538 } 539 } 540 541 // Perform any manufacturer replacements 542 for (i = 0; i < SDL_arraysize(replacements); ++i) { 543 size_t prefixlen = SDL_strlen(replacements[i].prefix); 544 if (SDL_strncasecmp(name, replacements[i].prefix, prefixlen) == 0) { 545 size_t replacementlen = SDL_strlen(replacements[i].replacement); 546 if (replacementlen <= prefixlen) { 547 SDL_memcpy(name, replacements[i].replacement, replacementlen); 548 SDL_memmove(name + replacementlen, name + prefixlen, (len - prefixlen) + 1); 549 len -= (prefixlen - replacementlen); 550 } else { 551 // FIXME: Need to handle the expand case by reallocating the string 552 } 553 break; 554 } 555 } 556 557 /* Remove duplicate manufacturer or product in the name 558 * e.g. Razer Razer Raiju Tournament Edition Wired 559 */ 560 for (i = 1; i < (len - 1); ++i) { 561 int matchlen = PrefixMatch(name, &name[i]); 562 while (matchlen > 0) { 563 if (name[matchlen] == ' ' || name[matchlen] == '-') { 564 SDL_memmove(name, name + matchlen + 1, len - matchlen); 565 break; 566 } 567 --matchlen; 568 } 569 if (matchlen > 0) { 570 // We matched the manufacturer's name and removed it 571 break; 572 } 573 } 574 575 return name; 576} 577 578 579void SDL_DebugLogBackend(const char *subsystem, const char *backend) 580{ 581 SDL_LogDebug(SDL_LOG_CATEGORY_SYSTEM, "SDL chose %s backend '%s'", subsystem, backend); 582} 583 584[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.