Add support for SDL3 joystick input driver

Made possible by EIREXE, xsellier and the SDL team.

This commit includes statically linked SDL3 for Windows, Linux and macOS.
The vendored copy of SDL3 was setup to only build the required subsystems
for gamepad/joystick support, with some patches to be able to make it as
minimal as possible and reduce the impact on binary size and code size.

Co-authored-by: Álex Román Núñez <eirexe123@gmail.com>
Co-authored-by: Xavier Sellier <xsellier@gmail.com>
Co-authored-by: Rémi Verschelde <rverschelde@gmail.com>
This commit is contained in:
Nintorch 2025-05-28 14:22:20 +05:00 committed by Rémi Verschelde
parent 987832be46
commit 0b3496fb4f
No known key found for this signature in database
GPG key ID: C3336907360768E1
330 changed files with 154930 additions and 1561 deletions

33
thirdparty/README.md vendored
View file

@ -447,6 +447,20 @@ Files extracted from upstream source:
- Except `main.cc`, `harfbuzz*.cc`, `failing-alloc.c`, `test*.cc`, `hb-wasm*.*`, `wasm/*`
## hidapi
- Upstream: https://github.com/libsdl-org/SDL/tree/main/src/hidapi
- Version: 0.14.0 (8d604353a53853fa56d1bdce0363535605ca868f, 2025)
- License: BSD-3-Clause
Files extracted from upstream source:
- See `thirdparty/sdl/update-sdl.sh`
The source code of this library is being bundled with SDL's source code files.
The files of hidapi are stored in `thirdparty/sdl/hidapi/` folder.
## icu4c
- Upstream: https://github.com/unicode-org/icu
@ -942,6 +956,25 @@ Files extracted from upstream source:
- Textures generated using the Python scripts in the `Scripts` folder
## sdl
- Upstream: https://github.com/libsdl-org/SDL
- Version: 3.2.14 (8d604353a53853fa56d1bdce0363535605ca868f, 2025)
- License: Zlib
Files extracted from upstream source:
- See `thirdparty/sdl/update-sdl.sh`
Patches:
- `0001-remove-unnecessary-subsystems.patch` (GH-106218)
- `0002-msvc-constants-fpstrict.patch` (GH-106218)
The SDL source code folder includes `hidapi` library inside of folder `thirdparty/sdl/hidapi/`.
Its version and license is described in this file under `hidapi`.
## spirv-cross
- Upstream: https://github.com/KhronosGroup/SPIRV-Cross

38
thirdparty/sdl/CREDITS.md vendored Normal file
View file

@ -0,0 +1,38 @@
# Simple DirectMedia Layer CREDITS
Thanks to everyone who made this possible, including:
- Cliff Matthews, for giving me a reason to start this project. :) -- Executor rocks! *grin*
- Ryan Gordon for helping everybody out and keeping the dream alive. :)
- Frank Praznik for his Wayland support and general windowing development.
- Ozkan Sezer for sanity checks and make sure the i's are dotted and t's are crossed.
- Anonymous Maarten for CMake support and build system development.
- Evan Hemsley, Caleb Cornett, and Ethan Lee for SDL GPU development.
- Gabriel Jacobo for his work on the Android port and generally helping out all around.
- Philipp Wiesemann for his attention to detail reviewing the entire SDL code base and proposes patches.
- Andreas Schiffler for his dedication to unit tests, Visual Studio projects, and managing the Google Summer of Code.
- Mike Sartain for incorporating SDL into Team Fortress 2 and cheering me on at Valve.
- Alfred Reynolds for the game controller API and general (in)sanity
- Jørgen Tjernø¸ for numerous magical macOS fixes.
- Pierre-Loup Griffais for his deep knowledge of OpenGL drivers.
- Julian Winter for the SDL 2.0 website.
- Sheena Smith for many months of great work on the SDL wiki creating the API documentation and style guides.
- Paul Hunkin for his port of SDL to Android during the Google Summer of Code 2010.
- Eli Gottlieb for his work on shaped windows during the Google Summer of Code 2010.
- Jim Grandpre for his work on multi-touch and gesture recognition during
the Google Summer of Code 2010.
- Edgar "bobbens" Simo for his force feedback API development during the
Google Summer of Code 2008.
- Aaron Wishnick for his work on audio resampling and pitch shifting during
the Google Summer of Code 2008.
- Holmes Futrell for his port of SDL to the iPhone and iPod Touch during the
Google Summer of Code 2008.
- Jon Atkins for SDL_image, SDL_mixer and SDL_net documentation.
- Everybody at Loki Software, Inc. and Valve Corporation for their great contributions!
And a big hand to everyone else who has contributed over the years.
THANKS! :)
-- Sam Lantinga <slouken@libsdl.org>

18
thirdparty/sdl/LICENSE.txt vendored Normal file
View file

@ -0,0 +1,18 @@
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.

841
thirdparty/sdl/SDL.c vendored Normal file
View file

@ -0,0 +1,841 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#include "SDL3/SDL_revision.h"
#if defined(SDL_PLATFORM_WINDOWS)
#include "core/windows/SDL_windows.h"
#else
#include <unistd.h> // _exit(), etc.
#endif
// this checks for HAVE_DBUS_DBUS_H internally.
#include "core/linux/SDL_dbus.h"
#ifdef SDL_PLATFORM_EMSCRIPTEN
#include <emscripten.h>
#endif
// Initialization code for SDL
#include "SDL_assert_c.h"
#include "SDL_hints_c.h"
#include "SDL_log_c.h"
#include "SDL_properties_c.h"
//#include "audio/SDL_sysaudio.h"
#include "events/SDL_events_c.h"
#include "haptic/SDL_haptic_c.h"
#include "joystick/SDL_gamepad_c.h"
#include "joystick/SDL_joystick_c.h"
#include "sensor/SDL_sensor_c.h"
#include "stdlib/SDL_getenv_c.h"
#include "thread/SDL_thread_c.h"
#ifdef SDL_PLATFORM_ANDROID
#include "core/android/SDL_android.h"
#endif
#define SDL_INIT_EVERYTHING ~0U
// Initialization/Cleanup routines
#include "timer/SDL_timer_c.h"
#ifdef SDL_VIDEO_DRIVER_WINDOWS
extern bool SDL_HelperWindowCreate(void);
extern void SDL_HelperWindowDestroy(void);
#endif
#ifdef SDL_BUILD_MAJOR_VERSION
SDL_COMPILE_TIME_ASSERT(SDL_BUILD_MAJOR_VERSION,
SDL_MAJOR_VERSION == SDL_BUILD_MAJOR_VERSION);
SDL_COMPILE_TIME_ASSERT(SDL_BUILD_MINOR_VERSION,
SDL_MINOR_VERSION == SDL_BUILD_MINOR_VERSION);
SDL_COMPILE_TIME_ASSERT(SDL_BUILD_MICRO_VERSION,
SDL_MICRO_VERSION == SDL_BUILD_MICRO_VERSION);
#endif
// Limited by its encoding in SDL_VERSIONNUM
SDL_COMPILE_TIME_ASSERT(SDL_MAJOR_VERSION_min, SDL_MAJOR_VERSION >= 0);
SDL_COMPILE_TIME_ASSERT(SDL_MAJOR_VERSION_max, SDL_MAJOR_VERSION <= 10);
SDL_COMPILE_TIME_ASSERT(SDL_MINOR_VERSION_min, SDL_MINOR_VERSION >= 0);
SDL_COMPILE_TIME_ASSERT(SDL_MINOR_VERSION_max, SDL_MINOR_VERSION <= 999);
SDL_COMPILE_TIME_ASSERT(SDL_MICRO_VERSION_min, SDL_MICRO_VERSION >= 0);
SDL_COMPILE_TIME_ASSERT(SDL_MICRO_VERSION_max, SDL_MICRO_VERSION <= 999);
/* This is not declared in any header, although it is shared between some
parts of SDL, because we don't want anything calling it without an
extremely good reason. */
extern SDL_NORETURN void SDL_ExitProcess(int exitcode);
SDL_NORETURN void SDL_ExitProcess(int exitcode)
{
#if defined(SDL_PLATFORM_WINDOWS)
/* "if you do not know the state of all threads in your process, it is
better to call TerminateProcess than ExitProcess"
https://msdn.microsoft.com/en-us/library/windows/desktop/ms682658(v=vs.85).aspx */
TerminateProcess(GetCurrentProcess(), exitcode);
/* MingW doesn't have TerminateProcess marked as noreturn, so add an
ExitProcess here that will never be reached but make MingW happy. */
ExitProcess(exitcode);
#elif defined(SDL_PLATFORM_EMSCRIPTEN)
emscripten_cancel_main_loop(); // this should "kill" the app.
emscripten_force_exit(exitcode); // this should "kill" the app.
exit(exitcode);
#elif defined(SDL_PLATFORM_HAIKU) // Haiku has _Exit, but it's not marked noreturn.
_exit(exitcode);
#elif defined(HAVE__EXIT) // Upper case _Exit()
_Exit(exitcode);
#else
_exit(exitcode);
#endif
}
// App metadata
bool SDL_SetAppMetadata(const char *appname, const char *appversion, const char *appidentifier)
{
SDL_SetAppMetadataProperty(SDL_PROP_APP_METADATA_NAME_STRING, appname);
SDL_SetAppMetadataProperty(SDL_PROP_APP_METADATA_VERSION_STRING, appversion);
SDL_SetAppMetadataProperty(SDL_PROP_APP_METADATA_IDENTIFIER_STRING, appidentifier);
return true;
}
static bool SDL_ValidMetadataProperty(const char *name)
{
if (!name || !*name) {
return false;
}
if (SDL_strcmp(name, SDL_PROP_APP_METADATA_NAME_STRING) == 0 ||
SDL_strcmp(name, SDL_PROP_APP_METADATA_VERSION_STRING) == 0 ||
SDL_strcmp(name, SDL_PROP_APP_METADATA_IDENTIFIER_STRING) == 0 ||
SDL_strcmp(name, SDL_PROP_APP_METADATA_CREATOR_STRING) == 0 ||
SDL_strcmp(name, SDL_PROP_APP_METADATA_COPYRIGHT_STRING) == 0 ||
SDL_strcmp(name, SDL_PROP_APP_METADATA_URL_STRING) == 0 ||
SDL_strcmp(name, SDL_PROP_APP_METADATA_TYPE_STRING) == 0) {
return true;
}
return false;
}
bool SDL_SetAppMetadataProperty(const char *name, const char *value)
{
if (!SDL_ValidMetadataProperty(name)) {
return SDL_InvalidParamError("name");
}
return SDL_SetStringProperty(SDL_GetGlobalProperties(), name, value);
}
const char *SDL_GetAppMetadataProperty(const char *name)
{
if (!SDL_ValidMetadataProperty(name)) {
SDL_InvalidParamError("name");
return NULL;
}
const char *value = NULL;
if (SDL_strcmp(name, SDL_PROP_APP_METADATA_NAME_STRING) == 0) {
value = SDL_GetHint(SDL_HINT_APP_NAME);
} else if (SDL_strcmp(name, SDL_PROP_APP_METADATA_IDENTIFIER_STRING) == 0) {
value = SDL_GetHint(SDL_HINT_APP_ID);
}
if (!value || !*value) {
value = SDL_GetStringProperty(SDL_GetGlobalProperties(), name, NULL);
}
if (!value || !*value) {
if (SDL_strcmp(name, SDL_PROP_APP_METADATA_NAME_STRING) == 0) {
value = "SDL Application";
} else if (SDL_strcmp(name, SDL_PROP_APP_METADATA_TYPE_STRING) == 0) {
value = "application";
}
}
return value;
}
// The initialized subsystems
#ifdef SDL_MAIN_NEEDED
static bool SDL_MainIsReady = false;
#else
static bool SDL_MainIsReady = true;
#endif
static SDL_ThreadID SDL_MainThreadID = 0;
static bool SDL_bInMainQuit = false;
static Uint8 SDL_SubsystemRefCount[32];
// Private helper to increment a subsystem's ref counter.
static void SDL_IncrementSubsystemRefCount(Uint32 subsystem)
{
const int subsystem_index = SDL_MostSignificantBitIndex32(subsystem);
SDL_assert((subsystem_index < 0) || (SDL_SubsystemRefCount[subsystem_index] < 255));
if (subsystem_index >= 0) {
++SDL_SubsystemRefCount[subsystem_index];
}
}
// Private helper to decrement a subsystem's ref counter.
static void SDL_DecrementSubsystemRefCount(Uint32 subsystem)
{
const int subsystem_index = SDL_MostSignificantBitIndex32(subsystem);
if ((subsystem_index >= 0) && (SDL_SubsystemRefCount[subsystem_index] > 0)) {
if (SDL_bInMainQuit) {
SDL_SubsystemRefCount[subsystem_index] = 0;
} else {
--SDL_SubsystemRefCount[subsystem_index];
}
}
}
// Private helper to check if a system needs init.
static bool SDL_ShouldInitSubsystem(Uint32 subsystem)
{
const int subsystem_index = SDL_MostSignificantBitIndex32(subsystem);
SDL_assert((subsystem_index < 0) || (SDL_SubsystemRefCount[subsystem_index] < 255));
return ((subsystem_index >= 0) && (SDL_SubsystemRefCount[subsystem_index] == 0));
}
// Private helper to check if a system needs to be quit.
static bool SDL_ShouldQuitSubsystem(Uint32 subsystem)
{
const int subsystem_index = SDL_MostSignificantBitIndex32(subsystem);
if ((subsystem_index >= 0) && (SDL_SubsystemRefCount[subsystem_index] == 0)) {
return false;
}
/* If we're in SDL_Quit, we shut down every subsystem, even if refcount
* isn't zero.
*/
return (((subsystem_index >= 0) && (SDL_SubsystemRefCount[subsystem_index] == 1)) || SDL_bInMainQuit);
}
/* Private helper to either increment's existing ref counter,
* or fully init a new subsystem. */
static bool SDL_InitOrIncrementSubsystem(Uint32 subsystem)
{
int subsystem_index = SDL_MostSignificantBitIndex32(subsystem);
SDL_assert((subsystem_index < 0) || (SDL_SubsystemRefCount[subsystem_index] < 255));
if (subsystem_index < 0) {
return false;
}
if (SDL_SubsystemRefCount[subsystem_index] > 0) {
++SDL_SubsystemRefCount[subsystem_index];
return true;
}
return SDL_InitSubSystem(subsystem);
}
void SDL_SetMainReady(void)
{
SDL_MainIsReady = true;
if (SDL_MainThreadID == 0) {
SDL_MainThreadID = SDL_GetCurrentThreadID();
}
}
bool SDL_IsMainThread(void)
{
if (SDL_MainThreadID == 0) {
// Not initialized yet?
return true;
}
if (SDL_MainThreadID == SDL_GetCurrentThreadID()) {
return true;
}
return false;
}
// Initialize all the subsystems that require initialization before threads start
void SDL_InitMainThread(void)
{
static bool done_info = false;
SDL_InitTLSData();
SDL_InitEnvironment();
SDL_InitTicks();
//SDL_InitFilesystem();
if (!done_info) {
const char *value;
value = SDL_GetAppMetadataProperty(SDL_PROP_APP_METADATA_NAME_STRING);
SDL_LogInfo(SDL_LOG_CATEGORY_SYSTEM, "App name: %s", value ? value : "<unspecified>");
value = SDL_GetAppMetadataProperty(SDL_PROP_APP_METADATA_VERSION_STRING);
SDL_LogInfo(SDL_LOG_CATEGORY_SYSTEM, "App version: %s", value ? value : "<unspecified>");
value = SDL_GetAppMetadataProperty(SDL_PROP_APP_METADATA_IDENTIFIER_STRING);
SDL_LogInfo(SDL_LOG_CATEGORY_SYSTEM, "App ID: %s", value ? value : "<unspecified>");
SDL_LogInfo(SDL_LOG_CATEGORY_SYSTEM, "SDL revision: %s", SDL_REVISION);
done_info = true;
}
}
static void SDL_QuitMainThread(void)
{
//SDL_QuitFilesystem();
SDL_QuitTicks();
SDL_QuitEnvironment();
SDL_QuitTLSData();
}
bool SDL_InitSubSystem(SDL_InitFlags flags)
{
Uint32 flags_initialized = 0;
if (!SDL_MainIsReady) {
return SDL_SetError("Application didn't initialize properly, did you include SDL_main.h in the file containing your main() function?");
}
SDL_InitMainThread();
#ifdef SDL_USE_LIBDBUS
SDL_DBus_Init();
#endif
#ifdef SDL_VIDEO_DRIVER_WINDOWS
if (flags & (SDL_INIT_HAPTIC | SDL_INIT_JOYSTICK)) {
if (!SDL_HelperWindowCreate()) {
goto quit_and_error;
}
}
#endif
// Initialize the event subsystem
if (flags & SDL_INIT_EVENTS) {
if (SDL_ShouldInitSubsystem(SDL_INIT_EVENTS)) {
SDL_IncrementSubsystemRefCount(SDL_INIT_EVENTS);
if (!SDL_InitEvents()) {
SDL_DecrementSubsystemRefCount(SDL_INIT_EVENTS);
goto quit_and_error;
}
} else {
SDL_IncrementSubsystemRefCount(SDL_INIT_EVENTS);
}
flags_initialized |= SDL_INIT_EVENTS;
}
// Initialize the video subsystem
if (flags & SDL_INIT_VIDEO) {
#ifndef SDL_VIDEO_DISABLED
if (SDL_ShouldInitSubsystem(SDL_INIT_VIDEO)) {
// video implies events
if (!SDL_InitOrIncrementSubsystem(SDL_INIT_EVENTS)) {
goto quit_and_error;
}
// We initialize video on the main thread
// On Apple platforms this is a requirement.
// On other platforms, this is the definition.
SDL_MainThreadID = SDL_GetCurrentThreadID();
SDL_IncrementSubsystemRefCount(SDL_INIT_VIDEO);
if (!SDL_VideoInit(NULL)) {
SDL_DecrementSubsystemRefCount(SDL_INIT_VIDEO);
SDL_PushError();
SDL_QuitSubSystem(SDL_INIT_EVENTS);
SDL_PopError();
goto quit_and_error;
}
} else {
SDL_IncrementSubsystemRefCount(SDL_INIT_VIDEO);
}
flags_initialized |= SDL_INIT_VIDEO;
#else
SDL_SetError("SDL not built with video support");
goto quit_and_error;
#endif
}
// Initialize the audio subsystem
if (flags & SDL_INIT_AUDIO) {
#ifndef SDL_AUDIO_DISABLED
if (SDL_ShouldInitSubsystem(SDL_INIT_AUDIO)) {
// audio implies events
if (!SDL_InitOrIncrementSubsystem(SDL_INIT_EVENTS)) {
goto quit_and_error;
}
SDL_IncrementSubsystemRefCount(SDL_INIT_AUDIO);
if (!SDL_InitAudio(NULL)) {
SDL_DecrementSubsystemRefCount(SDL_INIT_AUDIO);
SDL_PushError();
SDL_QuitSubSystem(SDL_INIT_EVENTS);
SDL_PopError();
goto quit_and_error;
}
} else {
SDL_IncrementSubsystemRefCount(SDL_INIT_AUDIO);
}
flags_initialized |= SDL_INIT_AUDIO;
#else
SDL_SetError("SDL not built with audio support");
goto quit_and_error;
#endif
}
// Initialize the joystick subsystem
if (flags & SDL_INIT_JOYSTICK) {
#ifndef SDL_JOYSTICK_DISABLED
if (SDL_ShouldInitSubsystem(SDL_INIT_JOYSTICK)) {
// joystick implies events
if (!SDL_InitOrIncrementSubsystem(SDL_INIT_EVENTS)) {
goto quit_and_error;
}
SDL_IncrementSubsystemRefCount(SDL_INIT_JOYSTICK);
if (!SDL_InitJoysticks()) {
SDL_DecrementSubsystemRefCount(SDL_INIT_JOYSTICK);
SDL_PushError();
SDL_QuitSubSystem(SDL_INIT_EVENTS);
SDL_PopError();
goto quit_and_error;
}
} else {
SDL_IncrementSubsystemRefCount(SDL_INIT_JOYSTICK);
}
flags_initialized |= SDL_INIT_JOYSTICK;
#else
SDL_SetError("SDL not built with joystick support");
goto quit_and_error;
#endif
}
if (flags & SDL_INIT_GAMEPAD) {
#ifndef SDL_JOYSTICK_DISABLED
if (SDL_ShouldInitSubsystem(SDL_INIT_GAMEPAD)) {
// game controller implies joystick
if (!SDL_InitOrIncrementSubsystem(SDL_INIT_JOYSTICK)) {
goto quit_and_error;
}
SDL_IncrementSubsystemRefCount(SDL_INIT_GAMEPAD);
if (!SDL_InitGamepads()) {
SDL_DecrementSubsystemRefCount(SDL_INIT_GAMEPAD);
SDL_PushError();
SDL_QuitSubSystem(SDL_INIT_JOYSTICK);
SDL_PopError();
goto quit_and_error;
}
} else {
SDL_IncrementSubsystemRefCount(SDL_INIT_GAMEPAD);
}
flags_initialized |= SDL_INIT_GAMEPAD;
#else
SDL_SetError("SDL not built with joystick support");
goto quit_and_error;
#endif
}
// Initialize the haptic subsystem
if (flags & SDL_INIT_HAPTIC) {
#ifndef SDL_HAPTIC_DISABLED
if (SDL_ShouldInitSubsystem(SDL_INIT_HAPTIC)) {
SDL_IncrementSubsystemRefCount(SDL_INIT_HAPTIC);
if (!SDL_InitHaptics()) {
SDL_DecrementSubsystemRefCount(SDL_INIT_HAPTIC);
goto quit_and_error;
}
} else {
SDL_IncrementSubsystemRefCount(SDL_INIT_HAPTIC);
}
flags_initialized |= SDL_INIT_HAPTIC;
#else
SDL_SetError("SDL not built with haptic (force feedback) support");
goto quit_and_error;
#endif
}
// Initialize the sensor subsystem
if (flags & SDL_INIT_SENSOR) {
#ifndef SDL_SENSOR_DISABLED
if (SDL_ShouldInitSubsystem(SDL_INIT_SENSOR)) {
SDL_IncrementSubsystemRefCount(SDL_INIT_SENSOR);
if (!SDL_InitSensors()) {
SDL_DecrementSubsystemRefCount(SDL_INIT_SENSOR);
goto quit_and_error;
}
} else {
SDL_IncrementSubsystemRefCount(SDL_INIT_SENSOR);
}
flags_initialized |= SDL_INIT_SENSOR;
#else
SDL_SetError("SDL not built with sensor support");
goto quit_and_error;
#endif
}
// Initialize the camera subsystem
if (flags & SDL_INIT_CAMERA) {
#ifndef SDL_CAMERA_DISABLED
if (SDL_ShouldInitSubsystem(SDL_INIT_CAMERA)) {
// camera implies events
if (!SDL_InitOrIncrementSubsystem(SDL_INIT_EVENTS)) {
goto quit_and_error;
}
SDL_IncrementSubsystemRefCount(SDL_INIT_CAMERA);
if (!SDL_CameraInit(NULL)) {
SDL_DecrementSubsystemRefCount(SDL_INIT_CAMERA);
SDL_PushError();
SDL_QuitSubSystem(SDL_INIT_EVENTS);
SDL_PopError();
goto quit_and_error;
}
} else {
SDL_IncrementSubsystemRefCount(SDL_INIT_CAMERA);
}
flags_initialized |= SDL_INIT_CAMERA;
#else
SDL_SetError("SDL not built with camera support");
goto quit_and_error;
#endif
}
(void)flags_initialized; // make static analysis happy, since this only gets used in error cases.
return SDL_ClearError();
quit_and_error:
{
SDL_PushError();
SDL_QuitSubSystem(flags_initialized);
SDL_PopError();
}
return false;
}
bool SDL_Init(SDL_InitFlags flags)
{
return SDL_InitSubSystem(flags);
}
void SDL_QuitSubSystem(SDL_InitFlags flags)
{
// Shut down requested initialized subsystems
#ifndef SDL_CAMERA_DISABLED
if (flags & SDL_INIT_CAMERA) {
if (SDL_ShouldQuitSubsystem(SDL_INIT_CAMERA)) {
SDL_QuitCamera();
// camera implies events
SDL_QuitSubSystem(SDL_INIT_EVENTS);
}
SDL_DecrementSubsystemRefCount(SDL_INIT_CAMERA);
}
#endif
#ifndef SDL_SENSOR_DISABLED
if (flags & SDL_INIT_SENSOR) {
if (SDL_ShouldQuitSubsystem(SDL_INIT_SENSOR)) {
SDL_QuitSensors();
}
SDL_DecrementSubsystemRefCount(SDL_INIT_SENSOR);
}
#endif
#ifndef SDL_JOYSTICK_DISABLED
if (flags & SDL_INIT_GAMEPAD) {
if (SDL_ShouldQuitSubsystem(SDL_INIT_GAMEPAD)) {
SDL_QuitGamepads();
// game controller implies joystick
SDL_QuitSubSystem(SDL_INIT_JOYSTICK);
}
SDL_DecrementSubsystemRefCount(SDL_INIT_GAMEPAD);
}
if (flags & SDL_INIT_JOYSTICK) {
if (SDL_ShouldQuitSubsystem(SDL_INIT_JOYSTICK)) {
SDL_QuitJoysticks();
// joystick implies events
SDL_QuitSubSystem(SDL_INIT_EVENTS);
}
SDL_DecrementSubsystemRefCount(SDL_INIT_JOYSTICK);
}
#endif
#ifndef SDL_HAPTIC_DISABLED
if (flags & SDL_INIT_HAPTIC) {
if (SDL_ShouldQuitSubsystem(SDL_INIT_HAPTIC)) {
SDL_QuitHaptics();
}
SDL_DecrementSubsystemRefCount(SDL_INIT_HAPTIC);
}
#endif
#ifndef SDL_AUDIO_DISABLED
if (flags & SDL_INIT_AUDIO) {
if (SDL_ShouldQuitSubsystem(SDL_INIT_AUDIO)) {
SDL_QuitAudio();
// audio implies events
SDL_QuitSubSystem(SDL_INIT_EVENTS);
}
SDL_DecrementSubsystemRefCount(SDL_INIT_AUDIO);
}
#endif
#ifndef SDL_VIDEO_DISABLED
if (flags & SDL_INIT_VIDEO) {
if (SDL_ShouldQuitSubsystem(SDL_INIT_VIDEO)) {
SDL_QuitRender();
SDL_VideoQuit();
// video implies events
SDL_QuitSubSystem(SDL_INIT_EVENTS);
}
SDL_DecrementSubsystemRefCount(SDL_INIT_VIDEO);
}
#endif
if (flags & SDL_INIT_EVENTS) {
if (SDL_ShouldQuitSubsystem(SDL_INIT_EVENTS)) {
SDL_QuitEvents();
}
SDL_DecrementSubsystemRefCount(SDL_INIT_EVENTS);
}
}
Uint32 SDL_WasInit(SDL_InitFlags flags)
{
int i;
int num_subsystems = SDL_arraysize(SDL_SubsystemRefCount);
Uint32 initialized = 0;
// Fast path for checking one flag
if (SDL_HasExactlyOneBitSet32(flags)) {
int subsystem_index = SDL_MostSignificantBitIndex32(flags);
return SDL_SubsystemRefCount[subsystem_index] ? flags : 0;
}
if (!flags) {
flags = SDL_INIT_EVERYTHING;
}
num_subsystems = SDL_min(num_subsystems, SDL_MostSignificantBitIndex32(flags) + 1);
// Iterate over each bit in flags, and check the matching subsystem.
for (i = 0; i < num_subsystems; ++i) {
if ((flags & 1) && SDL_SubsystemRefCount[i] > 0) {
initialized |= (1 << i);
}
flags >>= 1;
}
return initialized;
}
void SDL_Quit(void)
{
SDL_bInMainQuit = true;
// Quit all subsystems
#ifdef SDL_VIDEO_DRIVER_WINDOWS
SDL_HelperWindowDestroy();
#endif
SDL_QuitSubSystem(SDL_INIT_EVERYTHING);
//SDL_CleanupTrays();
#ifdef SDL_USE_LIBDBUS
SDL_DBus_Quit();
#endif
SDL_QuitTimers();
//SDL_QuitAsyncIO();
SDL_SetObjectsInvalid();
SDL_AssertionsQuit();
//SDL_QuitPixelFormatDetails();
//SDL_QuitCPUInfo();
/* Now that every subsystem has been quit, we reset the subsystem refcount
* and the list of initialized subsystems.
*/
SDL_memset(SDL_SubsystemRefCount, 0x0, sizeof(SDL_SubsystemRefCount));
SDL_QuitLog();
SDL_QuitHints();
SDL_QuitProperties();
SDL_QuitMainThread();
SDL_bInMainQuit = false;
}
// Get the library version number
int SDL_GetVersion(void)
{
return SDL_VERSION;
}
// Get the library source revision
const char *SDL_GetRevision(void)
{
return SDL_REVISION;
}
// Get the name of the platform
const char *SDL_GetPlatform(void)
{
#if defined(SDL_PLATFORM_PRIVATE)
return SDL_PLATFORM_PRIVATE_NAME;
#elif defined(SDL_PLATFORM_AIX)
return "AIX";
#elif defined(SDL_PLATFORM_ANDROID)
return "Android";
#elif defined(SDL_PLATFORM_BSDI)
return "BSDI";
#elif defined(SDL_PLATFORM_EMSCRIPTEN)
return "Emscripten";
#elif defined(SDL_PLATFORM_FREEBSD)
return "FreeBSD";
#elif defined(SDL_PLATFORM_HAIKU)
return "Haiku";
#elif defined(SDL_PLATFORM_HPUX)
return "HP-UX";
#elif defined(SDL_PLATFORM_IRIX)
return "Irix";
#elif defined(SDL_PLATFORM_LINUX)
return "Linux";
#elif defined(__MINT__)
return "Atari MiNT";
#elif defined(SDL_PLATFORM_MACOS)
return "macOS";
#elif defined(SDL_PLATFORM_NETBSD)
return "NetBSD";
#elif defined(SDL_PLATFORM_OPENBSD)
return "OpenBSD";
#elif defined(SDL_PLATFORM_OS2)
return "OS/2";
#elif defined(SDL_PLATFORM_OSF)
return "OSF/1";
#elif defined(SDL_PLATFORM_QNXNTO)
return "QNX Neutrino";
#elif defined(SDL_PLATFORM_RISCOS)
return "RISC OS";
#elif defined(SDL_PLATFORM_SOLARIS)
return "Solaris";
#elif defined(SDL_PLATFORM_WIN32)
return "Windows";
#elif defined(SDL_PLATFORM_WINGDK)
return "WinGDK";
#elif defined(SDL_PLATFORM_XBOXONE)
return "Xbox One";
#elif defined(SDL_PLATFORM_XBOXSERIES)
return "Xbox Series X|S";
#elif defined(SDL_PLATFORM_IOS)
return "iOS";
#elif defined(SDL_PLATFORM_TVOS)
return "tvOS";
#elif defined(SDL_PLATFORM_PS2)
return "PlayStation 2";
#elif defined(SDL_PLATFORM_PSP)
return "PlayStation Portable";
#elif defined(SDL_PLATFORM_VITA)
return "PlayStation Vita";
#elif defined(SDL_PLATFORM_3DS)
return "Nintendo 3DS";
#elif defined(__managarm__)
return "Managarm";
#else
return "Unknown (see SDL_platform.h)";
#endif
}
bool SDL_IsTablet(void)
{
#ifdef SDL_PLATFORM_ANDROID
return SDL_IsAndroidTablet();
#elif defined(SDL_PLATFORM_IOS)
extern bool SDL_IsIPad(void);
return SDL_IsIPad();
#else
return false;
#endif
}
bool SDL_IsTV(void)
{
#ifdef SDL_PLATFORM_ANDROID
return SDL_IsAndroidTV();
#elif defined(SDL_PLATFORM_IOS)
extern bool SDL_IsAppleTV(void);
return SDL_IsAppleTV();
#else
return false;
#endif
}
static SDL_Sandbox SDL_DetectSandbox(void)
{
#if defined(SDL_PLATFORM_LINUX)
if (access("/.flatpak-info", F_OK) == 0) {
return SDL_SANDBOX_FLATPAK;
}
/* For Snap, we check multiple variables because they might be set for
* unrelated reasons. This is the same thing WebKitGTK does. */
if (SDL_getenv("SNAP") && SDL_getenv("SNAP_NAME") && SDL_getenv("SNAP_REVISION")) {
return SDL_SANDBOX_SNAP;
}
if (access("/run/host/container-manager", F_OK) == 0) {
return SDL_SANDBOX_UNKNOWN_CONTAINER;
}
#elif defined(SDL_PLATFORM_MACOS)
if (SDL_getenv("APP_SANDBOX_CONTAINER_ID")) {
return SDL_SANDBOX_MACOS;
}
#endif
return SDL_SANDBOX_NONE;
}
SDL_Sandbox SDL_GetSandbox(void)
{
static SDL_Sandbox sandbox;
static bool sandbox_initialized;
if (!sandbox_initialized) {
sandbox = SDL_DetectSandbox();
sandbox_initialized = true;
}
return sandbox;
}
#ifdef SDL_PLATFORM_WIN32
#if (!defined(HAVE_LIBC) || defined(__WATCOMC__)) && !defined(SDL_STATIC_LIB)
// FIXME: Still need to include DllMain() on Watcom C ?
BOOL APIENTRY MINGW32_FORCEALIGN _DllMainCRTStartup(HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{
switch (ul_reason_for_call) {
case DLL_PROCESS_ATTACH:
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
#endif // Building DLL
#endif // defined(SDL_PLATFORM_WIN32)

443
thirdparty/sdl/SDL_assert.c vendored Normal file
View file

@ -0,0 +1,443 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#if defined(SDL_PLATFORM_WINDOWS)
#include "core/windows/SDL_windows.h"
#endif
#include "SDL_assert_c.h"
//#include "video/SDL_sysvideo.h"
#if defined(SDL_PLATFORM_WINDOWS)
#ifndef WS_OVERLAPPEDWINDOW
#define WS_OVERLAPPEDWINDOW 0
#endif
#endif
#ifdef SDL_PLATFORM_EMSCRIPTEN
#include <emscripten.h>
#endif
// The size of the stack buffer to use for rendering assert messages.
#define SDL_MAX_ASSERT_MESSAGE_STACK 256
static SDL_AssertState SDLCALL SDL_PromptAssertion(const SDL_AssertData *data, void *userdata);
/*
* We keep all triggered assertions in a singly-linked list so we can
* generate a report later.
*/
static SDL_AssertData *triggered_assertions = NULL;
#ifndef SDL_THREADS_DISABLED
static SDL_Mutex *assertion_mutex = NULL;
#endif
static SDL_AssertionHandler assertion_handler = SDL_PromptAssertion;
static void *assertion_userdata = NULL;
#ifdef __GNUC__
static void debug_print(const char *fmt, ...) __attribute__((format(printf, 1, 2)));
#endif
static void debug_print(const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
SDL_LogMessageV(SDL_LOG_CATEGORY_ASSERT, SDL_LOG_PRIORITY_WARN, fmt, ap);
va_end(ap);
}
static void SDL_AddAssertionToReport(SDL_AssertData *data)
{
/* (data) is always a static struct defined with the assert macros, so
we don't have to worry about copying or allocating them. */
data->trigger_count++;
if (data->trigger_count == 1) { // not yet added?
data->next = triggered_assertions;
triggered_assertions = data;
}
}
#if defined(SDL_PLATFORM_WINDOWS)
#define ENDLINE "\r\n"
#else
#define ENDLINE "\n"
#endif
static int SDL_RenderAssertMessage(char *buf, size_t buf_len, const SDL_AssertData *data)
{
return SDL_snprintf(buf, buf_len,
"Assertion failure at %s (%s:%d), triggered %u %s:" ENDLINE " '%s'",
data->function, data->filename, data->linenum,
data->trigger_count, (data->trigger_count == 1) ? "time" : "times",
data->condition);
}
static void SDL_GenerateAssertionReport(void)
{
const SDL_AssertData *item = triggered_assertions;
// only do this if the app hasn't assigned an assertion handler.
if ((item) && (assertion_handler != SDL_PromptAssertion)) {
debug_print("\n\nSDL assertion report.\n");
debug_print("All SDL assertions between last init/quit:\n\n");
while (item) {
debug_print(
"'%s'\n"
" * %s (%s:%d)\n"
" * triggered %u time%s.\n"
" * always ignore: %s.\n",
item->condition, item->function, item->filename,
item->linenum, item->trigger_count,
(item->trigger_count == 1) ? "" : "s",
item->always_ignore ? "yes" : "no");
item = item->next;
}
debug_print("\n");
SDL_ResetAssertionReport();
}
}
/* This is not declared in any header, although it is shared between some
parts of SDL, because we don't want anything calling it without an
extremely good reason. */
#ifdef __WATCOMC__
extern void SDL_ExitProcess(int exitcode);
#pragma aux SDL_ExitProcess aborts;
#endif
extern SDL_NORETURN void SDL_ExitProcess(int exitcode);
#ifdef __WATCOMC__
static void SDL_AbortAssertion(void);
#pragma aux SDL_AbortAssertion aborts;
#endif
static SDL_NORETURN void SDL_AbortAssertion(void)
{
SDL_Quit();
SDL_ExitProcess(42);
}
static SDL_AssertState SDLCALL SDL_PromptAssertion(const SDL_AssertData *data, void *userdata)
{
SDL_AssertState state = SDL_ASSERTION_ABORT;
SDL_Window *window;
SDL_MessageBoxData messagebox;
SDL_MessageBoxButtonData buttons[] = {
{ 0, SDL_ASSERTION_RETRY, "Retry" },
{ 0, SDL_ASSERTION_BREAK, "Break" },
{ 0, SDL_ASSERTION_ABORT, "Abort" },
{ SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT,
SDL_ASSERTION_IGNORE, "Ignore" },
{ SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT,
SDL_ASSERTION_ALWAYS_IGNORE, "Always Ignore" }
};
int selected;
char stack_buf[SDL_MAX_ASSERT_MESSAGE_STACK];
char *message = stack_buf;
size_t buf_len = sizeof(stack_buf);
int len;
(void)userdata; // unused in default handler.
// Assume the output will fit...
len = SDL_RenderAssertMessage(message, buf_len, data);
// .. and if it didn't, try to allocate as much room as we actually need.
if (len >= (int)buf_len) {
if (SDL_size_add_check_overflow(len, 1, &buf_len)) {
message = (char *)SDL_malloc(buf_len);
if (message) {
len = SDL_RenderAssertMessage(message, buf_len, data);
} else {
message = stack_buf;
}
}
}
// Something went very wrong
if (len < 0) {
if (message != stack_buf) {
SDL_free(message);
}
return SDL_ASSERTION_ABORT;
}
debug_print("\n\n%s\n\n", message);
// let env. variable override, so unit tests won't block in a GUI.
const char *hint = SDL_GetHint(SDL_HINT_ASSERT);
if (hint) {
if (message != stack_buf) {
SDL_free(message);
}
if (SDL_strcmp(hint, "abort") == 0) {
return SDL_ASSERTION_ABORT;
} else if (SDL_strcmp(hint, "break") == 0) {
return SDL_ASSERTION_BREAK;
} else if (SDL_strcmp(hint, "retry") == 0) {
return SDL_ASSERTION_RETRY;
} else if (SDL_strcmp(hint, "ignore") == 0) {
return SDL_ASSERTION_IGNORE;
} else if (SDL_strcmp(hint, "always_ignore") == 0) {
return SDL_ASSERTION_ALWAYS_IGNORE;
} else {
return SDL_ASSERTION_ABORT; // oh well.
}
}
// Show a messagebox if we can, otherwise fall back to stdio
SDL_zero(messagebox);
messagebox.flags = SDL_MESSAGEBOX_WARNING;
messagebox.window = window;
messagebox.title = "Assertion Failed";
messagebox.message = message;
messagebox.numbuttons = SDL_arraysize(buttons);
messagebox.buttons = buttons;
//if (SDL_ShowMessageBox(&messagebox, &selected)) {
if (false) {
if (selected == -1) {
state = SDL_ASSERTION_IGNORE;
} else {
state = (SDL_AssertState)selected;
}
} else {
#ifdef SDL_PLATFORM_PRIVATE_ASSERT
SDL_PRIVATE_PROMPTASSERTION();
#elif defined(SDL_PLATFORM_EMSCRIPTEN)
// This is nasty, but we can't block on a custom UI.
for (;;) {
bool okay = true;
/* *INDENT-OFF* */ // clang-format off
int reply = MAIN_THREAD_EM_ASM_INT({
var str =
UTF8ToString($0) + '\n\n' +
'Abort/Retry/Ignore/AlwaysIgnore? [ariA] :';
var reply = window.prompt(str, "i");
if (reply === null) {
reply = "i";
}
return reply.length === 1 ? reply.charCodeAt(0) : -1;
}, message);
/* *INDENT-ON* */ // clang-format on
switch (reply) {
case 'a':
state = SDL_ASSERTION_ABORT;
break;
#if 0 // (currently) no break functionality on Emscripten
case 'b':
state = SDL_ASSERTION_BREAK;
break;
#endif
case 'r':
state = SDL_ASSERTION_RETRY;
break;
case 'i':
state = SDL_ASSERTION_IGNORE;
break;
case 'A':
state = SDL_ASSERTION_ALWAYS_IGNORE;
break;
default:
okay = false;
break;
}
if (okay) {
break;
}
}
#elif defined(HAVE_STDIO_H) && !defined(SDL_PLATFORM_3DS)
// this is a little hacky.
for (;;) {
char buf[32];
(void)fprintf(stderr, "Abort/Break/Retry/Ignore/AlwaysIgnore? [abriA] : ");
(void)fflush(stderr);
if (fgets(buf, sizeof(buf), stdin) == NULL) {
break;
}
if (SDL_strncmp(buf, "a", 1) == 0) {
state = SDL_ASSERTION_ABORT;
break;
} else if (SDL_strncmp(buf, "b", 1) == 0) {
state = SDL_ASSERTION_BREAK;
break;
} else if (SDL_strncmp(buf, "r", 1) == 0) {
state = SDL_ASSERTION_RETRY;
break;
} else if (SDL_strncmp(buf, "i", 1) == 0) {
state = SDL_ASSERTION_IGNORE;
break;
} else if (SDL_strncmp(buf, "A", 1) == 0) {
state = SDL_ASSERTION_ALWAYS_IGNORE;
break;
}
}
#else
//SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_WARNING, "Assertion Failed", message, window);
#endif // HAVE_STDIO_H
}
// Re-enter fullscreen mode
if (window) {
//SDL_RestoreWindow(window);
}
if (message != stack_buf) {
SDL_free(message);
}
return state;
}
SDL_AssertState SDL_ReportAssertion(SDL_AssertData *data, const char *func, const char *file, int line)
{
SDL_AssertState state = SDL_ASSERTION_IGNORE;
static int assertion_running = 0;
#ifndef SDL_THREADS_DISABLED
static SDL_SpinLock spinlock = 0;
SDL_LockSpinlock(&spinlock);
if (!assertion_mutex) { // never called SDL_Init()?
assertion_mutex = SDL_CreateMutex();
if (!assertion_mutex) {
SDL_UnlockSpinlock(&spinlock);
return SDL_ASSERTION_IGNORE; // oh well, I guess.
}
}
SDL_UnlockSpinlock(&spinlock);
SDL_LockMutex(assertion_mutex);
#endif // !SDL_THREADS_DISABLED
// doing this because Visual C is upset over assigning in the macro.
if (data->trigger_count == 0) {
data->function = func;
data->filename = file;
data->linenum = line;
}
SDL_AddAssertionToReport(data);
assertion_running++;
if (assertion_running > 1) { // assert during assert! Abort.
if (assertion_running == 2) {
SDL_AbortAssertion();
} else if (assertion_running == 3) { // Abort asserted!
SDL_ExitProcess(42);
} else {
while (1) { // do nothing but spin; what else can you do?!
}
}
}
if (!data->always_ignore) {
state = assertion_handler(data, assertion_userdata);
}
switch (state) {
case SDL_ASSERTION_ALWAYS_IGNORE:
state = SDL_ASSERTION_IGNORE;
data->always_ignore = true;
break;
case SDL_ASSERTION_IGNORE:
case SDL_ASSERTION_RETRY:
case SDL_ASSERTION_BREAK:
break; // macro handles these.
case SDL_ASSERTION_ABORT:
SDL_AbortAssertion();
// break; ...shouldn't return, but oh well.
}
assertion_running--;
#ifndef SDL_THREADS_DISABLED
SDL_UnlockMutex(assertion_mutex);
#endif
return state;
}
void SDL_AssertionsQuit(void)
{
#if SDL_ASSERT_LEVEL > 0
SDL_GenerateAssertionReport();
#ifndef SDL_THREADS_DISABLED
if (assertion_mutex) {
SDL_DestroyMutex(assertion_mutex);
assertion_mutex = NULL;
}
#endif
#endif // SDL_ASSERT_LEVEL > 0
}
void SDL_SetAssertionHandler(SDL_AssertionHandler handler, void *userdata)
{
if (handler != NULL) {
assertion_handler = handler;
assertion_userdata = userdata;
} else {
assertion_handler = SDL_PromptAssertion;
assertion_userdata = NULL;
}
}
const SDL_AssertData *SDL_GetAssertionReport(void)
{
return triggered_assertions;
}
void SDL_ResetAssertionReport(void)
{
SDL_AssertData *next = NULL;
SDL_AssertData *item;
for (item = triggered_assertions; item; item = next) {
next = (SDL_AssertData *)item->next;
item->always_ignore = false;
item->trigger_count = 0;
item->next = NULL;
}
triggered_assertions = NULL;
}
SDL_AssertionHandler SDL_GetDefaultAssertionHandler(void)
{
return SDL_PromptAssertion;
}
SDL_AssertionHandler SDL_GetAssertionHandler(void **userdata)
{
if (userdata) {
*userdata = assertion_userdata;
}
return assertion_handler;
}

28
thirdparty/sdl/SDL_assert_c.h vendored Normal file
View file

@ -0,0 +1,28 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#ifndef SDL_assert_c_h_
#define SDL_assert_c_h_
extern void SDL_AssertionsQuit(void);
#endif // SDL_assert_c_h_

112
thirdparty/sdl/SDL_error.c vendored Normal file
View file

@ -0,0 +1,112 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
// Simple error handling in SDL
#include "SDL_error_c.h"
bool SDL_SetError(SDL_PRINTF_FORMAT_STRING const char *fmt, ...)
{
va_list ap;
bool result;
va_start(ap, fmt);
result = SDL_SetErrorV(fmt, ap);
va_end(ap);
return result;
}
bool SDL_SetErrorV(SDL_PRINTF_FORMAT_STRING const char *fmt, va_list ap)
{
// Ignore call if invalid format pointer was passed
if (fmt) {
int result;
SDL_error *error = SDL_GetErrBuf(true);
va_list ap2;
error->error = SDL_ErrorCodeGeneric;
va_copy(ap2, ap);
result = SDL_vsnprintf(error->str, error->len, fmt, ap2);
va_end(ap2);
if (result >= 0 && (size_t)result >= error->len && error->realloc_func) {
size_t len = (size_t)result + 1;
char *str = (char *)error->realloc_func(error->str, len);
if (str) {
error->str = str;
error->len = len;
va_copy(ap2, ap);
(void)SDL_vsnprintf(error->str, error->len, fmt, ap2);
va_end(ap2);
}
}
// Enable this if you want to see all errors printed as they occur.
// Note that there are many recoverable errors that may happen internally and
// can be safely ignored if the public API doesn't return an error code.
#if 0
SDL_LogError(SDL_LOG_CATEGORY_ERROR, "%s", error->str);
#endif
}
return false;
}
const char *SDL_GetError(void)
{
const SDL_error *error = SDL_GetErrBuf(false);
if (!error) {
return "";
}
switch (error->error) {
case SDL_ErrorCodeGeneric:
return error->str;
case SDL_ErrorCodeOutOfMemory:
return "Out of memory";
default:
return "";
}
}
bool SDL_ClearError(void)
{
SDL_error *error = SDL_GetErrBuf(false);
if (error) {
error->error = SDL_ErrorCodeNone;
}
return true;
}
bool SDL_OutOfMemory(void)
{
SDL_error *error = SDL_GetErrBuf(true);
if (error) {
error->error = SDL_ErrorCodeOutOfMemory;
}
return false;
}

61
thirdparty/sdl/SDL_error_c.h vendored Normal file
View file

@ -0,0 +1,61 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
/* This file defines a structure that carries language-independent
error messages
*/
#ifndef SDL_error_c_h_
#define SDL_error_c_h_
typedef enum
{
SDL_ErrorCodeNone,
SDL_ErrorCodeGeneric,
SDL_ErrorCodeOutOfMemory,
} SDL_ErrorCode;
typedef struct SDL_error
{
SDL_ErrorCode error;
char *str;
size_t len;
SDL_realloc_func realloc_func;
SDL_free_func free_func;
} SDL_error;
// Defined in SDL_thread.c
extern SDL_error *SDL_GetErrBuf(bool create);
// Macros to save and restore error values
#define SDL_PushError() \
char *saved_error = SDL_strdup(SDL_GetError())
#define SDL_PopError() \
do { \
if (saved_error) { \
SDL_SetError("%s", saved_error); \
SDL_free(saved_error); \
} \
} while (0)
#endif // SDL_error_c_h_

88
thirdparty/sdl/SDL_guid.c vendored Normal file
View file

@ -0,0 +1,88 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
// convert the guid to a printable string
void SDL_GUIDToString(SDL_GUID guid, char *pszGUID, int cbGUID)
{
static const char k_rgchHexToASCII[] = "0123456789abcdef";
int i;
if ((!pszGUID) || (cbGUID <= 0)) {
return;
}
for (i = 0; i < sizeof(guid.data) && i < (cbGUID - 1) / 2; i++) {
// each input byte writes 2 ascii chars, and might write a null byte.
// If we don't have room for next input byte, stop
unsigned char c = guid.data[i];
*pszGUID++ = k_rgchHexToASCII[c >> 4];
*pszGUID++ = k_rgchHexToASCII[c & 0x0F];
}
*pszGUID = '\0';
}
/*-----------------------------------------------------------------------------
* Purpose: Returns the 4 bit nibble for a hex character
* Input : c -
* Output : unsigned char
*-----------------------------------------------------------------------------*/
static unsigned char nibble(unsigned char c)
{
if ((c >= '0') && (c <= '9')) {
return c - '0';
}
if ((c >= 'A') && (c <= 'F')) {
return c - 'A' + 0x0a;
}
if ((c >= 'a') && (c <= 'f')) {
return c - 'a' + 0x0a;
}
// received an invalid character, and no real way to return an error
// AssertMsg1(false, "Q_nibble invalid hex character '%c' ", c);
return 0;
}
// convert the string version of a guid to the struct
SDL_GUID SDL_StringToGUID(const char *pchGUID)
{
SDL_GUID guid;
int maxoutputbytes = sizeof(guid);
size_t len = SDL_strlen(pchGUID);
Uint8 *p;
size_t i;
// Make sure it's even
len = (len) & ~0x1;
SDL_memset(&guid, 0x00, sizeof(guid));
p = (Uint8 *)&guid;
for (i = 0; (i < len) && ((p - (Uint8 *)&guid) < maxoutputbytes); i += 2, p++) {
*p = (nibble((unsigned char)pchGUID[i]) << 4) | nibble((unsigned char)pchGUID[i + 1]);
}
return guid;
}

543
thirdparty/sdl/SDL_hashtable.c vendored Normal file
View file

@ -0,0 +1,543 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
typedef struct SDL_HashItem
{
// TODO: Splitting off values into a separate array might be more cache-friendly
const void *key;
const void *value;
Uint32 hash;
Uint32 probe_len : 31;
Uint32 live : 1;
} SDL_HashItem;
// Must be a power of 2 >= sizeof(SDL_HashItem)
#define MAX_HASHITEM_SIZEOF 32u
SDL_COMPILE_TIME_ASSERT(sizeof_SDL_HashItem, sizeof(SDL_HashItem) <= MAX_HASHITEM_SIZEOF);
// Anything larger than this will cause integer overflows
#define MAX_HASHTABLE_SIZE (0x80000000u / (MAX_HASHITEM_SIZEOF))
struct SDL_HashTable
{
SDL_RWLock *lock; // NULL if not created threadsafe
SDL_HashItem *table;
SDL_HashCallback hash;
SDL_HashKeyMatchCallback keymatch;
SDL_HashDestroyCallback destroy;
void *userdata;
Uint32 hash_mask;
Uint32 max_probe_len;
Uint32 num_occupied_slots;
};
static Uint32 CalculateHashBucketsFromEstimate(int estimated_capacity)
{
if (estimated_capacity <= 0) {
return 4; // start small, grow as necessary.
}
const Uint32 estimated32 = (Uint32) estimated_capacity;
Uint32 buckets = ((Uint32) 1) << SDL_MostSignificantBitIndex32(estimated32);
if (!SDL_HasExactlyOneBitSet32(estimated32)) {
buckets <<= 1; // need next power of two up to fit overflow capacity bits.
}
return SDL_min(buckets, MAX_HASHTABLE_SIZE);
}
SDL_HashTable *SDL_CreateHashTable(int estimated_capacity, bool threadsafe, SDL_HashCallback hash,
SDL_HashKeyMatchCallback keymatch,
SDL_HashDestroyCallback destroy, void *userdata)
{
const Uint32 num_buckets = CalculateHashBucketsFromEstimate(estimated_capacity);
SDL_HashTable *table = (SDL_HashTable *)SDL_calloc(1, sizeof(SDL_HashTable));
if (!table) {
return NULL;
}
if (threadsafe) {
table->lock = SDL_CreateRWLock();
if (!table->lock) {
SDL_DestroyHashTable(table);
return NULL;
}
}
table->table = (SDL_HashItem *)SDL_calloc(num_buckets, sizeof(SDL_HashItem));
if (!table->table) {
SDL_DestroyHashTable(table);
return NULL;
}
table->hash_mask = num_buckets - 1;
table->userdata = userdata;
table->hash = hash;
table->keymatch = keymatch;
table->destroy = destroy;
return table;
}
static SDL_INLINE Uint32 calc_hash(const SDL_HashTable *table, const void *key)
{
const Uint32 BitMixer = 0x9E3779B1u;
return table->hash(table->userdata, key) * BitMixer;
}
static SDL_INLINE Uint32 get_probe_length(Uint32 zero_idx, Uint32 actual_idx, Uint32 num_buckets)
{
// returns the probe sequence length from zero_idx to actual_idx
if (actual_idx < zero_idx) {
return num_buckets - zero_idx + actual_idx;
}
return actual_idx - zero_idx;
}
static SDL_HashItem *find_item(const SDL_HashTable *ht, const void *key, Uint32 hash, Uint32 *i, Uint32 *probe_len)
{
Uint32 hash_mask = ht->hash_mask;
Uint32 max_probe_len = ht->max_probe_len;
SDL_HashItem *table = ht->table;
while (true) {
SDL_HashItem *item = table + *i;
Uint32 item_hash = item->hash;
if (!item->live) {
return NULL;
}
if (item_hash == hash && ht->keymatch(ht->userdata, item->key, key)) {
return item;
}
Uint32 item_probe_len = item->probe_len;
SDL_assert(item_probe_len == get_probe_length(item_hash & hash_mask, (Uint32)(item - table), hash_mask + 1));
if (*probe_len > item_probe_len) {
return NULL;
}
if (++*probe_len > max_probe_len) {
return NULL;
}
*i = (*i + 1) & hash_mask;
}
}
static SDL_HashItem *find_first_item(const SDL_HashTable *ht, const void *key, Uint32 hash)
{
Uint32 i = hash & ht->hash_mask;
Uint32 probe_len = 0;
return find_item(ht, key, hash, &i, &probe_len);
}
static SDL_HashItem *insert_item(SDL_HashItem *item_to_insert, SDL_HashItem *table, Uint32 hash_mask, Uint32 *max_probe_len_ptr)
{
const Uint32 num_buckets = hash_mask + 1;
Uint32 idx = item_to_insert->hash & hash_mask;
SDL_HashItem *target = NULL;
SDL_HashItem temp_item;
while (true) {
SDL_HashItem *candidate = table + idx;
if (!candidate->live) {
// Found an empty slot. Put it here and we're done.
*candidate = *item_to_insert;
if (target == NULL) {
target = candidate;
}
const Uint32 probe_len = get_probe_length(candidate->hash & hash_mask, idx, num_buckets);
candidate->probe_len = probe_len;
if (*max_probe_len_ptr < probe_len) {
*max_probe_len_ptr = probe_len;
}
break;
}
const Uint32 candidate_probe_len = candidate->probe_len;
SDL_assert(candidate_probe_len == get_probe_length(candidate->hash & hash_mask, idx, num_buckets));
const Uint32 new_probe_len = get_probe_length(item_to_insert->hash & hash_mask, idx, num_buckets);
if (candidate_probe_len < new_probe_len) {
// Robin Hood hashing: the item at idx has a better probe length than our item would at this position.
// Evict it and put our item in its place, then continue looking for a new spot for the displaced item.
// This algorithm significantly reduces clustering in the table, making lookups take very few probes.
temp_item = *candidate;
*candidate = *item_to_insert;
if (target == NULL) {
target = candidate;
}
*item_to_insert = temp_item;
SDL_assert(new_probe_len == get_probe_length(candidate->hash & hash_mask, idx, num_buckets));
candidate->probe_len = new_probe_len;
if (*max_probe_len_ptr < new_probe_len) {
*max_probe_len_ptr = new_probe_len;
}
}
idx = (idx + 1) & hash_mask;
}
return target;
}
static void delete_item(SDL_HashTable *ht, SDL_HashItem *item)
{
const Uint32 hash_mask = ht->hash_mask;
SDL_HashItem *table = ht->table;
if (ht->destroy) {
ht->destroy(ht->userdata, item->key, item->value);
}
SDL_assert(ht->num_occupied_slots > 0);
ht->num_occupied_slots--;
Uint32 idx = (Uint32)(item - ht->table);
while (true) {
idx = (idx + 1) & hash_mask;
SDL_HashItem *next_item = table + idx;
if (next_item->probe_len < 1) {
SDL_zerop(item);
return;
}
*item = *next_item;
item->probe_len -= 1;
SDL_assert(item->probe_len < ht->max_probe_len);
item = next_item;
}
}
static bool resize(SDL_HashTable *ht, Uint32 new_size)
{
const Uint32 new_hash_mask = new_size - 1;
SDL_HashItem *new_table = SDL_calloc(new_size, sizeof(*new_table));
if (!new_table) {
return false;
}
SDL_HashItem *old_table = ht->table;
const Uint32 old_size = ht->hash_mask + 1;
ht->max_probe_len = 0;
ht->hash_mask = new_hash_mask;
ht->table = new_table;
for (Uint32 i = 0; i < old_size; ++i) {
SDL_HashItem *item = old_table + i;
if (item->live) {
insert_item(item, new_table, new_hash_mask, &ht->max_probe_len);
}
}
SDL_free(old_table);
return true;
}
static bool maybe_resize(SDL_HashTable *ht)
{
const Uint32 capacity = ht->hash_mask + 1;
if (capacity >= MAX_HASHTABLE_SIZE) {
return false;
}
const Uint32 max_load_factor = 217; // range: 0-255; 217 is roughly 85%
const Uint32 resize_threshold = (Uint32)((max_load_factor * (Uint64)capacity) >> 8);
if (ht->num_occupied_slots > resize_threshold) {
return resize(ht, capacity * 2);
}
return true;
}
bool SDL_InsertIntoHashTable(SDL_HashTable *table, const void *key, const void *value, bool replace)
{
if (!table) {
return SDL_InvalidParamError("table");
}
bool result = false;
SDL_LockRWLockForWriting(table->lock);
const Uint32 hash = calc_hash(table, key);
SDL_HashItem *item = find_first_item(table, key, hash);
bool do_insert = true;
if (item) {
if (replace) {
delete_item(table, item);
} else {
SDL_SetError("key already exists and replace is disabled");
do_insert = false;
}
}
if (do_insert) {
SDL_HashItem new_item;
new_item.key = key;
new_item.value = value;
new_item.hash = hash;
new_item.live = true;
new_item.probe_len = 0;
table->num_occupied_slots++;
if (!maybe_resize(table)) {
table->num_occupied_slots--;
} else {
// This never returns NULL
insert_item(&new_item, table->table, table->hash_mask, &table->max_probe_len);
result = true;
}
}
SDL_UnlockRWLock(table->lock);
return result;
}
bool SDL_FindInHashTable(const SDL_HashTable *table, const void *key, const void **value)
{
if (!table) {
if (value) {
*value = NULL;
}
return SDL_InvalidParamError("table");
}
SDL_LockRWLockForReading(table->lock);
bool result = false;
const Uint32 hash = calc_hash(table, key);
SDL_HashItem *i = find_first_item(table, key, hash);
if (i) {
if (value) {
*value = i->value;
}
result = true;
}
SDL_UnlockRWLock(table->lock);
return result;
}
bool SDL_RemoveFromHashTable(SDL_HashTable *table, const void *key)
{
if (!table) {
return SDL_InvalidParamError("table");
}
SDL_LockRWLockForWriting(table->lock);
bool result = false;
const Uint32 hash = calc_hash(table, key);
SDL_HashItem *item = find_first_item(table, key, hash);
if (item) {
delete_item(table, item);
result = true;
}
SDL_UnlockRWLock(table->lock);
return result;
}
bool SDL_IterateHashTable(const SDL_HashTable *table, SDL_HashTableIterateCallback callback, void *userdata)
{
if (!table) {
return SDL_InvalidParamError("table");
} else if (!callback) {
return SDL_InvalidParamError("callback");
}
SDL_LockRWLockForReading(table->lock);
SDL_HashItem *end = table->table + (table->hash_mask + 1);
Uint32 num_iterated = 0;
for (SDL_HashItem *item = table->table; item < end; item++) {
if (item->live) {
if (!callback(userdata, table, item->key, item->value)) {
break; // callback requested iteration stop.
} else if (++num_iterated >= table->num_occupied_slots) {
break; // we can drop out early because we've seen all the live items.
}
}
}
SDL_UnlockRWLock(table->lock);
return true;
}
bool SDL_HashTableEmpty(SDL_HashTable *table)
{
if (!table) {
return SDL_InvalidParamError("table");
}
SDL_LockRWLockForReading(table->lock);
const bool retval = (table->num_occupied_slots == 0);
SDL_UnlockRWLock(table->lock);
return retval;
}
static void destroy_all(SDL_HashTable *table)
{
SDL_HashDestroyCallback destroy = table->destroy;
if (destroy) {
void *userdata = table->userdata;
SDL_HashItem *end = table->table + (table->hash_mask + 1);
for (SDL_HashItem *i = table->table; i < end; ++i) {
if (i->live) {
i->live = false;
destroy(userdata, i->key, i->value);
}
}
}
}
void SDL_ClearHashTable(SDL_HashTable *table)
{
if (table) {
SDL_LockRWLockForWriting(table->lock);
{
destroy_all(table);
SDL_memset(table->table, 0, sizeof(*table->table) * (table->hash_mask + 1));
table->num_occupied_slots = 0;
}
SDL_UnlockRWLock(table->lock);
}
}
void SDL_DestroyHashTable(SDL_HashTable *table)
{
if (table) {
destroy_all(table);
if (table->lock) {
SDL_DestroyRWLock(table->lock);
}
SDL_free(table->table);
SDL_free(table);
}
}
// this is djb's xor hashing function.
static SDL_INLINE Uint32 hash_string_djbxor(const char *str, size_t len)
{
Uint32 hash = 5381;
while (len--) {
hash = ((hash << 5) + hash) ^ *(str++);
}
return hash;
}
Uint32 SDL_HashPointer(void *unused, const void *key)
{
(void)unused;
return SDL_murmur3_32(&key, sizeof(key), 0);
}
bool SDL_KeyMatchPointer(void *unused, const void *a, const void *b)
{
(void)unused;
return (a == b);
}
Uint32 SDL_HashString(void *unused, const void *key)
{
(void)unused;
const char *str = (const char *)key;
return hash_string_djbxor(str, SDL_strlen(str));
}
bool SDL_KeyMatchString(void *unused, const void *a, const void *b)
{
const char *a_string = (const char *)a;
const char *b_string = (const char *)b;
(void)unused;
if (a == b) {
return true; // same pointer, must match.
} else if (!a || !b) {
return false; // one pointer is NULL (and first test shows they aren't the same pointer), must not match.
} else if (a_string[0] != b_string[0]) {
return false; // we know they don't match
}
return (SDL_strcmp(a_string, b_string) == 0); // Check against actual string contents.
}
// We assume we can fit the ID in the key directly
SDL_COMPILE_TIME_ASSERT(SDL_HashID_KeySize, sizeof(Uint32) <= sizeof(const void *));
Uint32 SDL_HashID(void *unused, const void *key)
{
(void)unused;
return (Uint32)(uintptr_t)key;
}
bool SDL_KeyMatchID(void *unused, const void *a, const void *b)
{
(void)unused;
return (a == b);
}
void SDL_DestroyHashKeyAndValue(void *unused, const void *key, const void *value)
{
(void)unused;
SDL_free((void *)key);
SDL_free((void *)value);
}
void SDL_DestroyHashKey(void *unused, const void *key, const void *value)
{
(void)value;
(void)unused;
SDL_free((void *)key);
}
void SDL_DestroyHashValue(void *unused, const void *key, const void *value)
{
(void)key;
(void)unused;
SDL_free((void *)value);
}

633
thirdparty/sdl/SDL_hashtable.h vendored Normal file
View file

@ -0,0 +1,633 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
/* this is over-documented because it was almost a public API. Leaving the
full docs here in case it _does_ become public some day. */
/* WIKI CATEGORY: HashTable */
/**
* # CategoryHashTable
*
* SDL offers a hash table implementation, as a convenience for C code that
* needs efficient organization and access of arbitrary data.
*
* Hash tables are a popular data structure, designed to make it quick to
* store and look up arbitrary data. Data is stored with an associated "key."
* While one would look up an element of an array with an index, a hash table
* uses a unique key to find an element later.
*
* A key can be anything, as long as its unique and in a format that the table
* understands. For example, it's popular to use strings as keys: the key
* might be a username, and it is used to lookup account information for that
* user, etc.
*
* Hash tables are named because they "hash" their keys down into simple
* integers that can be used to efficiently organize and access the associated
* data.
*
* As this is a C API, there is one generic interface that is intended to work
* with different data types. This can be a little awkward to set up, but is
* easy to use after that.
*
* Hashtables are generated by a call to SDL_CreateHashTable(). This function
* requires several callbacks to be provided (for hashing keys, comparing
* entries, and cleaning up entries when removed). These are necessary to
* allow the hash to manage any arbitrary data type.
*
* Once a hash table is created, the common tasks are inserting data into the
* table, (SDL_InsertIntoHashTable), looking up previously inserted data
* (SDL_FindInHashTable), and removing data (SDL_RemoveFromHashTable and
* SDL_ClearHashTable). Less common but still useful is the ability to
* iterate through all the items in the table (SDL_IterateHashTable).
*
* The underlying hash table implementation is always subject to change, but
* at the time of writing, it uses open addressing and Robin Hood hashing.
* The technical details are explained [here](https://github.com/libsdl-org/SDL/pull/10897).
*
* Hashtables keep an SDL_RWLock internally, so multiple threads can perform
* hash lookups in parallel, while changes to the table will safely serialize
* access between threads.
*
* SDL provides a layer on top of this hash table implementation that might be
* more pleasant to use. SDL_PropertiesID maps a string to arbitrary data of
* various types in the same table, which could be both easier to use and more
* flexible. Refer to [CategoryProperties](CategoryProperties) for details.
*/
#ifndef SDL_hashtable_h_
#define SDL_hashtable_h_
#include <SDL3/SDL_stdinc.h>
#include <SDL3/SDL_begin_code.h>
/* Set up for C function definitions, even when using C++ */
#ifdef __cplusplus
extern "C" {
#endif
/**
* The opaque type that represents a hash table.
*
* This is hidden behind an opaque pointer because not only does the table
* need to store arbitrary data types, but the hash table implementation may
* change in the future.
*
* \since This struct is available since SDL 3.4.0.
*
* \sa SDL_CreateHashTable
*/
typedef struct SDL_HashTable SDL_HashTable;
/**
* A function pointer representing a hash table hashing callback.
*
* This is called by SDL_HashTable when it needs to look up a key in
* its dataset. It generates a hash value from that key, and then uses that
* value as a basis for an index into an internal array.
*
* There are no rules on what hashing algorithm is used, so long as it
* can produce a reliable 32-bit value from `key`, and ideally distributes
* those values well across the 32-bit value space. The quality of a
* hashing algorithm is directly related to how well a hash table performs.
*
* Hashing can be a complicated subject, and often times what works best
* for one dataset will be suboptimal for another. There is a good discussion
* of the field [on Wikipedia](https://en.wikipedia.org/wiki/Hash_function).
*
* Also: do you _need_ to write a hashing function? SDL provides generic
* functions for strings (SDL_HashString), generic integer IDs (SDL_HashID),
* and generic pointers (SDL_HashPointer). Often you should use one of these
* before writing your own.
*
* \param userdata what was passed as `userdata` to SDL_CreateHashTable().
* \param key the key to be hashed.
* \returns a 32-bit value that represents a hash of `key`.
*
* \threadsafety This function must be thread safe if the hash table is used
* from multiple threads at the same time.
*
* \since This datatype is available since SDL 3.4.0.
*
* \sa SDL_CreateHashTable
* \sa SDL_HashString
* \sa SDL_HashID
* \sa SDL_HashPointer
*/
typedef Uint32 (SDLCALL *SDL_HashCallback)(void *userdata, const void *key);
/**
* A function pointer representing a hash table matching callback.
*
* This is called by SDL_HashTable when it needs to look up a key in its
* dataset. After hashing the key, it looks for items stored in relation to
* that hash value. Since there can be more than one item found through the
* same hash value, this function verifies a specific value is actually
* correct before choosing it.
*
* So this function needs to compare the keys at `a` and `b` and decide if
* they are actually the same.
*
* For example, if the keys are C strings, this function might just be:
*
* ```c
* return (SDL_strcmp((const char *) a, const char *b) == 0);`
* ```
*
* Also: do you _need_ to write a matching function? SDL provides generic
* functions for strings (SDL_KeyMatchString), generic integer IDs
* (SDL_KeyMatchID), and generic pointers (SDL_KeyMatchPointer). Often you
* should use one of these before writing your own.
*
* \param userdata what was passed as `userdata` to SDL_CreateHashTable().
* \param a the first key to be compared.
* \param b the second key to be compared.
* \returns true if two keys are identical, false otherwise.
*
* \threadsafety This function must be thread safe if the hash table is used
* from multiple threads at the same time.
*
* \since This datatype is available since SDL 3.4.0.
*
* \sa SDL_CreateHashTable
*/
typedef bool (SDLCALL *SDL_HashKeyMatchCallback)(void *userdata, const void *a, const void *b);
/**
* A function pointer representing a hash table cleanup callback.
*
* This is called by SDL_HashTable when removing items from the hash, or
* destroying the hash table. It is used to optionally deallocate the
* key/value pairs.
*
* This is not required to do anything, if all the data in the table is
* static or POD data, but it can also do more than a simple free: for
* example, if the hash table is storing open files, it can close them here.
* It can also free only the key or only the value; it depends on what the
* hash table contains.
*
* \param userdata what was passed as `userdata` to SDL_CreateHashTable().
* \param key the key to deallocate.
* \param value the value to deallocate.
*
* \threadsafety This function must be thread safe if the hash table is used
* from multiple threads at the same time.
*
* \since This datatype is available since SDL 3.4.0.
*
* \sa SDL_CreateHashTable
*/
typedef void (SDLCALL *SDL_HashDestroyCallback)(void *userdata, const void *key, const void *value);
/**
* A function pointer representing a hash table iterator callback.
*
* This function is called once for each key/value pair to be considered
* when iterating a hash table.
*
* Iteration continues as long as there are more items to examine and this
* callback continues to return true.
*
* Do not attempt to modify the hash table during this callback, as it will
* cause incorrect behavior and possibly crashes.
*
* \param userdata what was passed as `userdata` to an iterator function.
* \param table the hash table being iterated.
* \param key the current key being iterated.
* \param value the current value being iterated.
* \returns true to keep iterating, false to stop iteration.
*
* \threadsafety A read lock is held during iteration, so other threads can
* still access the the hash table, but threads attempting to
* make changes will be blocked until iteration completes. If
* this is a concern, do as little in the callback as possible
* and finish iteration quickly.
*
* \since This datatype is available since SDL 3.4.0.
*
* \sa SDL_IterateHashTable
*/
typedef bool (SDLCALL *SDL_HashTableIterateCallback)(void *userdata, const SDL_HashTable *table, const void *key, const void *value);
/**
* Create a new hash table.
*
* To deal with different datatypes and needs of the caller, hash tables
* require several callbacks that deal with some specifics: how to hash a key,
* how to compare a key for equality, and how to clean up keys and values.
* SDL provides a few generic functions that can be used for these callbacks:
*
* - SDL_HashString and SDL_KeyMatchString for C strings.
* - SDL_HashPointer and SDL_KeyMatchPointer for generic pointers.
* - SDL_HashID and SDL_KeyMatchID for generic (possibly small) integers.
*
* Oftentimes, these are all you need for any hash table, but depending on
* your dataset, custom implementations might make more sense.
*
* You can specify an estimate of the number of items expected to be stored
* in the table, which can help make the table run more efficiently. The table
* will preallocate resources to accomodate this number of items, which is
* most useful if you intend to fill the table with a lot of data right after
* creating it. Otherwise, it might make more sense to specify the _minimum_
* you expect the table to hold and let it grow as necessary from there. This
* number is only a hint, and the table will be able to handle any amount of
* data--as long as the system doesn't run out of resources--so a perfect
* answer is not required. A value of 0 signifies no guess at all, and the
* table will start small and reallocate as necessary; often this is the
* correct thing to do.
*
* !!! FIXME: add note about `threadsafe` here. And update `threadsafety` tags.
* !!! FIXME: note that `threadsafe` tables can't be recursively locked, so
* !!! FIXME: you can't use `destroy` callbacks that might end up relocking.
*
* Note that SDL provides a higher-level option built on its hash tables:
* SDL_PropertiesID lets you map strings to various datatypes, and this
* might be easier to use. It only allows strings for keys, however. Those are
* created with SDL_CreateProperties().
*
* The returned hash table should be destroyed with SDL_DestroyHashTable()
* when no longer needed.
*
* \param estimated_capacity the approximate maximum number of items to be held
* in the hash table, or 0 for no estimate.
* \param threadsafe true to create an internal rwlock for this table.
* \param hash the function to use to hash keys.
* \param keymatch the function to use to compare keys.
* \param destroy the function to use to clean up keys and values, may be NULL.
* \param userdata a pointer that is passed to the callbacks.
* \returns a newly-created hash table, or NULL if there was an error; call
* SDL_GetError() for more information.
*
* \threadsafety It is safe to call this function from any thread.
*
* \since This function is available since SDL 3.4.0.
*
* \sa SDL_DestroyHashTable
*/
extern SDL_HashTable * SDL_CreateHashTable(int estimated_capacity,
bool threadsafe,
SDL_HashCallback hash,
SDL_HashKeyMatchCallback keymatch,
SDL_HashDestroyCallback destroy,
void *userdata);
/**
* Destroy a hash table.
*
* This will call the hash table's SDL_HashDestroyCallback for each item in
* the table, removing all inserted items, before deallocating the table
* itself.
*
* The table becomes invalid once this function is called, and no other thread
* should be accessing this table once this function has started.
*
* \param table the hash table to destroy.
*
* \threadsafety It is safe to call this function from any thread.
*
* \since This function is available since SDL 3.4.0.
*/
extern void SDL_DestroyHashTable(SDL_HashTable *table);
/**
* Add an item to a hash table.
*
* All keys in the table must be unique. If attempting to insert a key that
* already exists in the hash table, what will be done depends on the
* `replace` value:
*
* - If `replace` is false, this function will return false without modifying
* the table.
* - If `replace` is true, SDL will remove the previous item first, so the new
* value is the only one associated with that key. This will call the hash
* table's SDL_HashDestroyCallback for the previous item.
*
* \param table the hash table to insert into.
* \param key the key of the new item to insert.
* \param value the value of the new item to insert.
* \param replace true if a duplicate key should replace the previous value.
* \returns true if the new item was inserted, false otherwise.
*
* \threadsafety It is safe to call this function from any thread.
*
* \since This function is available since SDL 3.4.0.
*/
extern bool SDL_InsertIntoHashTable(SDL_HashTable *table, const void *key, const void *value, bool replace);
/**
* Look up an item in a hash table.
*
* On return, the value associated with `key` is stored to `*value`.
* If the key does not exist in the table, `*value` will be set to NULL.
*
* It is legal for `value` to be NULL, to not retrieve the key's value. In
* this case, the return value is still useful for reporting if the key exists
* in the table at all.
*
* \param table the hash table to search.
* \param key the key to search for in the table.
* \param value the found value will be stored here. Can be NULL.
* \returns true if key exists in the table, false otherwise.
*
* \threadsafety It is safe to call this function from any thread.
*
* \since This function is available since SDL 3.4.0.
*
* \sa SDL_InsertIntoHashTable
*/
extern bool SDL_FindInHashTable(const SDL_HashTable *table, const void *key, const void **value);
/**
* Remove an item from a hash table.
*
* If there is an item that matches `key`, it is removed from the table. This
* will call the hash table's SDL_HashDestroyCallback for the item to be
* removed.
*
* \param table the hash table to remove from.
* \param key the key of the item to remove from the table.
* \returns true if a key was removed, false if the key was not found.
*
* \threadsafety It is safe to call this function from any thread.
*
* \since This function is available since SDL 3.4.0.
*/
extern bool SDL_RemoveFromHashTable(SDL_HashTable *table, const void *key);
/**
* Remove all items in a hash table.
*
* This will call the hash table's SDL_HashDestroyCallback for each item in
* the table, removing all inserted items.
*
* When this function returns, the hash table will be empty.
*
* \param table the hash table to clear.
*
* \threadsafety It is safe to call this function from any thread.
*
* \since This function is available since SDL 3.4.0.
*/
extern void SDL_ClearHashTable(SDL_HashTable *table);
/**
* Check if any items are currently stored in a hash table.
*
* If there are no items stored (the table is completely empty), this will
* return true.
*
* \param table the hash table to check.
* \returns true if the table is completely empty, false otherwise.
*
* \threadsafety It is safe to call this function from any thread.
*
* \since This function is available since SDL 3.4.0.
*
* \sa SDL_ClearHashTable
*/
extern bool SDL_HashTableEmpty(SDL_HashTable *table);
/**
* Iterate all key/value pairs in a hash table.
*
* This function will call `callback` once for each key/value pair in the
* table, until either all pairs have been presented to the callback, or the
* callback has returned false to signal it is done.
*
* There is no guarantee what order results will be returned in.
*
* \param table the hash table to iterate.
* \param callback the function pointer to call for each value.
* \param userdata a pointer that is passed to `callback`.
* \returns true if iteration happened, false if not (bogus parameter, etc).
*
* \since This function is available since SDL 3.4.0.
*/
extern bool SDL_IterateHashTable(const SDL_HashTable *table, SDL_HashTableIterateCallback callback, void *userdata);
/* Helper functions for SDL_CreateHashTable callbacks... */
/**
* Generate a hash from a generic pointer.
*
* The key is intended to be a unique pointer to any datatype.
*
* This is intended to be used as one of the callbacks to SDL_CreateHashTable,
* if this is useful to the type of keys to be used with the hash table.
*
* Note that the implementation may change in the future; do not expect
* the results to be stable vs future SDL releases. Use this in a hash table
* in the current process and don't store them to disk for the future.
*
* \param unused this parameter is ignored.
* \param key the key to hash as a generic pointer.
* \returns a 32-bit hash of the key.
*
* \threadsafety It is safe to call this function from any thread.
*
* \since This function is available since SDL 3.4.0.
*
* \sa SDL_CreateHashTable
*/
extern Uint32 SDL_HashPointer(void *unused, const void *key);
/**
* Compare two generic pointers as hash table keys.
*
* This is intended to be used as one of the callbacks to SDL_CreateHashTable,
* if this is useful to the type of keys to be used with the hash table.
*
* \param unused this parameter is ignored.
* \param a the first generic pointer to compare.
* \param b the second generic pointer to compare.
* \returns true if the pointers are the same, false otherwise.
*
* \threadsafety It is safe to call this function from any thread.
*
* \since This function is available since SDL 3.4.0.
*
* \sa SDL_CreateHashTable
*/
extern bool SDL_KeyMatchPointer(void *unused, const void *a, const void *b);
/**
* Generate a hash from a C string.
*
* The key is intended to be a NULL-terminated string, in UTF-8 format.
*
* This is intended to be used as one of the callbacks to SDL_CreateHashTable,
* if this is useful to the type of keys to be used with the hash table.
*
* Note that the implementation may change in the future; do not expect
* the results to be stable vs future SDL releases. Use this in a hash table
* in the current process and don't store them to disk for the future.
*
* \param unused this parameter is ignored.
* \param key the key to hash as a generic pointer.
* \returns a 32-bit hash of the key.
*
* \threadsafety It is safe to call this function from any thread.
*
* \since This function is available since SDL 3.4.0.
*
* \sa SDL_CreateHashTable
*/
extern Uint32 SDL_HashString(void *unused, const void *key);
/**
* Compare two C strings as hash table keys.
*
* Strings will be compared in a case-sensitive manner. More specifically,
* they'll be compared as NULL-terminated arrays of bytes.
*
* This is intended to be used as one of the callbacks to SDL_CreateHashTable,
* if this is useful to the type of keys to be used with the hash table.
*
* \param unused this parameter is ignored.
* \param a the first string to compare.
* \param b the second string to compare.
* \returns true if the strings are the same, false otherwise.
*
* \threadsafety It is safe to call this function from any thread.
*
* \since This function is available since SDL 3.4.0.
*
* \sa SDL_CreateHashTable
*/
extern bool SDL_KeyMatchString(void *unused, const void *a, const void *b);
/**
* Generate a hash from an integer ID.
*
* The key is intended to a unique integer, possibly within a small range.
*
* This is intended to be used as one of the callbacks to SDL_CreateHashTable,
* if this is useful to the type of keys to be used with the hash table.
*
* Note that the implementation may change in the future; do not expect
* the results to be stable vs future SDL releases. Use this in a hash table
* in the current process and don't store them to disk for the future.
*
* \param unused this parameter is ignored.
* \param key the key to hash as a generic pointer.
* \returns a 32-bit hash of the key.
*
* \threadsafety It is safe to call this function from any thread.
*
* \since This function is available since SDL 3.4.0.
*
* \sa SDL_CreateHashTable
*/
extern Uint32 SDL_HashID(void *unused, const void *key);
/**
* Compare two integer IDs as hash table keys.
*
* This is intended to be used as one of the callbacks to SDL_CreateHashTable,
* if this is useful to the type of keys to be used with the hash table.
*
* \param unused this parameter is ignored.
* \param a the first ID to compare.
* \param b the second ID to compare.
* \returns true if the IDs are the same, false otherwise.
*
* \threadsafety It is safe to call this function from any thread.
*
* \since This function is available since SDL 3.4.0.
*
* \sa SDL_CreateHashTable
*/
extern bool SDL_KeyMatchID(void *unused, const void *a, const void *b);
/**
* Free both the key and value pointers of a hash table item.
*
* This is intended to be used as one of the callbacks to SDL_CreateHashTable,
* if this is useful to the type of data to be used with the hash table.
*
* This literally calls `SDL_free(key);` and `SDL_free(value);`.
*
* \param unused this parameter is ignored.
* \param key the key to be destroyed.
* \param value the value to be destroyed.
*
* \threadsafety It is safe to call this function from any thread.
*
* \since This function is available since SDL 3.4.0.
*
* \sa SDL_CreateHashTable
*/
extern void SDL_DestroyHashKeyAndValue(void *unused, const void *key, const void *value);
/**
* Free just the value pointer of a hash table item.
*
* This is intended to be used as one of the callbacks to SDL_CreateHashTable,
* if this is useful to the type of data to be used with the hash table.
*
* This literally calls `SDL_free(key);` and leaves `value` alone.
*
* \param unused this parameter is ignored.
* \param key the key to be destroyed.
* \param value the value to be destroyed.
*
* \threadsafety It is safe to call this function from any thread.
*
* \since This function is available since SDL 3.4.0.
*
* \sa SDL_CreateHashTable
*/
extern void SDL_DestroyHashKey(void *unused, const void *key, const void *value);
/**
* Free just the value pointer of a hash table item.
*
* This is intended to be used as one of the callbacks to SDL_CreateHashTable,
* if this is useful to the type of data to be used with the hash table.
*
* This literally calls `SDL_free(value);` and leaves `key` alone.
*
* \param unused this parameter is ignored.
* \param key the key to be destroyed.
* \param value the value to be destroyed.
*
* \threadsafety It is safe to call this function from any thread.
*
* \since This function is available since SDL 3.4.0.
*
* \sa SDL_CreateHashTable
*/
extern void SDL_DestroyHashValue(void *unused, const void *key, const void *value);
/* Ends C function definitions when using C++ */
#ifdef __cplusplus
}
#endif
#include <SDL3/SDL_close_code.h>
#endif /* SDL_hashtable_h_ */

403
thirdparty/sdl/SDL_hints.c vendored Normal file
View file

@ -0,0 +1,403 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#include "SDL_hints_c.h"
#ifdef SDL_PLATFORM_ANDROID
#include "core/android/SDL_android.h"
#endif
typedef struct SDL_HintWatch
{
SDL_HintCallback callback;
void *userdata;
struct SDL_HintWatch *next;
} SDL_HintWatch;
typedef struct SDL_Hint
{
char *value;
SDL_HintPriority priority;
SDL_HintWatch *callbacks;
} SDL_Hint;
static SDL_AtomicU32 SDL_hint_props;
void SDL_InitHints(void)
{
}
void SDL_QuitHints(void)
{
SDL_PropertiesID props;
do {
props = SDL_GetAtomicU32(&SDL_hint_props);
} while (!SDL_CompareAndSwapAtomicU32(&SDL_hint_props, props, 0));
if (props) {
SDL_DestroyProperties(props);
}
}
static SDL_PropertiesID GetHintProperties(bool create)
{
SDL_PropertiesID props = SDL_GetAtomicU32(&SDL_hint_props);
if (!props && create) {
props = SDL_CreateProperties();
if (!SDL_CompareAndSwapAtomicU32(&SDL_hint_props, 0, props)) {
// Somebody else created hint properties before us, just use those
SDL_DestroyProperties(props);
props = SDL_GetAtomicU32(&SDL_hint_props);
}
}
return props;
}
static void SDLCALL CleanupHintProperty(void *userdata, void *value)
{
SDL_Hint *hint = (SDL_Hint *) value;
SDL_free(hint->value);
SDL_HintWatch *entry = hint->callbacks;
while (entry) {
SDL_HintWatch *freeable = entry;
entry = entry->next;
SDL_free(freeable);
}
SDL_free(hint);
}
static const char* GetHintEnvironmentVariable(const char *name)
{
const char *result = SDL_getenv(name);
if (!result && name && *name) {
// fall back to old (SDL2) names of environment variables that
// are important to users (e.g. many use SDL_VIDEODRIVER=wayland)
if (SDL_strcmp(name, SDL_HINT_VIDEO_DRIVER) == 0) {
result = SDL_getenv("SDL_VIDEODRIVER");
} else if (SDL_strcmp(name, SDL_HINT_AUDIO_DRIVER) == 0) {
result = SDL_getenv("SDL_AUDIODRIVER");
}
}
return result;
}
bool SDL_SetHintWithPriority(const char *name, const char *value, SDL_HintPriority priority)
{
if (!name || !*name) {
return SDL_InvalidParamError("name");
}
const char *env = GetHintEnvironmentVariable(name);
if (env && (priority < SDL_HINT_OVERRIDE)) {
return SDL_SetError("An environment variable is taking priority");
}
const SDL_PropertiesID hints = GetHintProperties(true);
if (!hints) {
return false;
}
bool result = false;
SDL_LockProperties(hints);
SDL_Hint *hint = (SDL_Hint *)SDL_GetPointerProperty(hints, name, NULL);
if (hint) {
if (priority >= hint->priority) {
if (hint->value != value && (!value || !hint->value || SDL_strcmp(hint->value, value) != 0)) {
char *old_value = hint->value;
hint->value = value ? SDL_strdup(value) : NULL;
SDL_HintWatch *entry = hint->callbacks;
while (entry) {
// Save the next entry in case this one is deleted
SDL_HintWatch *next = entry->next;
entry->callback(entry->userdata, name, old_value, value);
entry = next;
}
SDL_free(old_value);
}
hint->priority = priority;
result = true;
}
} else { // Couldn't find the hint? Add a new one.
hint = (SDL_Hint *)SDL_malloc(sizeof(*hint));
if (hint) {
hint->value = value ? SDL_strdup(value) : NULL;
hint->priority = priority;
hint->callbacks = NULL;
result = SDL_SetPointerPropertyWithCleanup(hints, name, hint, CleanupHintProperty, NULL);
}
}
#ifdef SDL_PLATFORM_ANDROID
if (SDL_strcmp(name, SDL_HINT_ANDROID_ALLOW_RECREATE_ACTIVITY) == 0) {
// Special handling for this hint, which needs to persist outside the normal application flow
Android_SetAllowRecreateActivity(SDL_GetStringBoolean(value, false));
}
#endif // SDL_PLATFORM_ANDROID
SDL_UnlockProperties(hints);
return result;
}
bool SDL_ResetHint(const char *name)
{
if (!name || !*name) {
return SDL_InvalidParamError("name");
}
const char *env = GetHintEnvironmentVariable(name);
const SDL_PropertiesID hints = GetHintProperties(false);
if (!hints) {
return false;
}
bool result = false;
SDL_LockProperties(hints);
SDL_Hint *hint = (SDL_Hint *)SDL_GetPointerProperty(hints, name, NULL);
if (hint) {
if ((!env && hint->value) || (env && !hint->value) || (env && SDL_strcmp(env, hint->value) != 0)) {
for (SDL_HintWatch *entry = hint->callbacks; entry;) {
// Save the next entry in case this one is deleted
SDL_HintWatch *next = entry->next;
entry->callback(entry->userdata, name, hint->value, env);
entry = next;
}
}
SDL_free(hint->value);
hint->value = NULL;
hint->priority = SDL_HINT_DEFAULT;
result = true;
}
#ifdef SDL_PLATFORM_ANDROID
if (SDL_strcmp(name, SDL_HINT_ANDROID_ALLOW_RECREATE_ACTIVITY) == 0) {
// Special handling for this hint, which needs to persist outside the normal application flow
if (env) {
Android_SetAllowRecreateActivity(SDL_GetStringBoolean(env, false));
} else {
Android_SetAllowRecreateActivity(false);
}
}
#endif // SDL_PLATFORM_ANDROID
SDL_UnlockProperties(hints);
return result;
}
static void SDLCALL ResetHintsCallback(void *userdata, SDL_PropertiesID hints, const char *name)
{
SDL_Hint *hint = (SDL_Hint *)SDL_GetPointerProperty(hints, name, NULL);
if (!hint) {
return; // uh...okay.
}
const char *env = GetHintEnvironmentVariable(name);
if ((!env && hint->value) || (env && !hint->value) || (env && SDL_strcmp(env, hint->value) != 0)) {
SDL_HintWatch *entry = hint->callbacks;
while (entry) {
// Save the next entry in case this one is deleted
SDL_HintWatch *next = entry->next;
entry->callback(entry->userdata, name, hint->value, env);
entry = next;
}
}
SDL_free(hint->value);
hint->value = NULL;
hint->priority = SDL_HINT_DEFAULT;
#ifdef SDL_PLATFORM_ANDROID
if (SDL_strcmp(name, SDL_HINT_ANDROID_ALLOW_RECREATE_ACTIVITY) == 0) {
// Special handling for this hint, which needs to persist outside the normal application flow
if (env) {
Android_SetAllowRecreateActivity(SDL_GetStringBoolean(env, false));
} else {
Android_SetAllowRecreateActivity(false);
}
}
#endif // SDL_PLATFORM_ANDROID
}
void SDL_ResetHints(void)
{
SDL_EnumerateProperties(GetHintProperties(false), ResetHintsCallback, NULL);
}
bool SDL_SetHint(const char *name, const char *value)
{
return SDL_SetHintWithPriority(name, value, SDL_HINT_NORMAL);
}
const char *SDL_GetHint(const char *name)
{
if (!name) {
return NULL;
}
const char *result = GetHintEnvironmentVariable(name);
const SDL_PropertiesID hints = GetHintProperties(false);
if (hints) {
SDL_LockProperties(hints);
SDL_Hint *hint = (SDL_Hint *)SDL_GetPointerProperty(hints, name, NULL);
if (hint) {
if (!result || hint->priority == SDL_HINT_OVERRIDE) {
result = SDL_GetPersistentString(hint->value);
}
}
SDL_UnlockProperties(hints);
}
return result;
}
int SDL_GetStringInteger(const char *value, int default_value)
{
if (!value || !*value) {
return default_value;
}
if (SDL_strcasecmp(value, "false") == 0) {
return 0;
}
if (SDL_strcasecmp(value, "true") == 0) {
return 1;
}
if (*value == '-' || SDL_isdigit(*value)) {
return SDL_atoi(value);
}
return default_value;
}
bool SDL_GetStringBoolean(const char *value, bool default_value)
{
if (!value || !*value) {
return default_value;
}
if (*value == '0' || SDL_strcasecmp(value, "false") == 0) {
return false;
}
return true;
}
bool SDL_GetHintBoolean(const char *name, bool default_value)
{
const char *hint = SDL_GetHint(name);
return SDL_GetStringBoolean(hint, default_value);
}
bool SDL_AddHintCallback(const char *name, SDL_HintCallback callback, void *userdata)
{
if (!name || !*name) {
return SDL_InvalidParamError("name");
} else if (!callback) {
return SDL_InvalidParamError("callback");
}
const SDL_PropertiesID hints = GetHintProperties(true);
if (!hints) {
return false;
}
SDL_HintWatch *entry = (SDL_HintWatch *)SDL_malloc(sizeof(*entry));
if (!entry) {
return false;
}
entry->callback = callback;
entry->userdata = userdata;
bool result = false;
SDL_LockProperties(hints);
SDL_RemoveHintCallback(name, callback, userdata);
SDL_Hint *hint = (SDL_Hint *)SDL_GetPointerProperty(hints, name, NULL);
if (hint) {
result = true;
} else { // Need to add a hint entry for this watcher
hint = (SDL_Hint *)SDL_malloc(sizeof(*hint));
if (!hint) {
SDL_free(entry);
SDL_UnlockProperties(hints);
return false;
} else {
hint->value = NULL;
hint->priority = SDL_HINT_DEFAULT;
hint->callbacks = NULL;
result = SDL_SetPointerPropertyWithCleanup(hints, name, hint, CleanupHintProperty, NULL);
}
}
// Add it to the callbacks for this hint
entry->next = hint->callbacks;
hint->callbacks = entry;
// Now call it with the current value
const char *value = SDL_GetHint(name);
callback(userdata, name, value, value);
SDL_UnlockProperties(hints);
return result;
}
void SDL_RemoveHintCallback(const char *name, SDL_HintCallback callback, void *userdata)
{
if (!name || !*name) {
return;
}
const SDL_PropertiesID hints = GetHintProperties(false);
if (!hints) {
return;
}
SDL_LockProperties(hints);
SDL_Hint *hint = (SDL_Hint *)SDL_GetPointerProperty(hints, name, NULL);
if (hint) {
SDL_HintWatch *prev = NULL;
for (SDL_HintWatch *entry = hint->callbacks; entry; entry = entry->next) {
if ((callback == entry->callback) && (userdata == entry->userdata)) {
if (prev) {
prev->next = entry->next;
} else {
hint->callbacks = entry->next;
}
SDL_free(entry);
break;
}
prev = entry;
}
}
SDL_UnlockProperties(hints);
}

33
thirdparty/sdl/SDL_hints_c.h vendored Normal file
View file

@ -0,0 +1,33 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
// This file defines useful function for working with SDL hints
#ifndef SDL_hints_c_h_
#define SDL_hints_c_h_
extern void SDL_InitHints(void);
extern bool SDL_GetStringBoolean(const char *value, bool default_value);
extern int SDL_GetStringInteger(const char *value, int default_value);
extern void SDL_QuitHints(void);
#endif // SDL_hints_c_h_

289
thirdparty/sdl/SDL_internal.h vendored Normal file
View file

@ -0,0 +1,289 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#ifndef SDL_internal_h_
#define SDL_internal_h_
// Many of SDL's features require _GNU_SOURCE on various platforms
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
// Need this so Linux systems define fseek64o, ftell64o and off64_t
#ifndef _LARGEFILE64_SOURCE
#define _LARGEFILE64_SOURCE 1
#endif
/* This is for a variable-length array at the end of a struct:
struct x { int y; char z[SDL_VARIABLE_LENGTH_ARRAY]; };
Use this because GCC 2 needs different magic than other compilers. */
#if (defined(__GNUC__) && (__GNUC__ <= 2)) || defined(__CC_ARM) || defined(__cplusplus)
#define SDL_VARIABLE_LENGTH_ARRAY 1
#else
#define SDL_VARIABLE_LENGTH_ARRAY
#endif
#if (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))) || defined(__clang__)
#define HAVE_GCC_DIAGNOSTIC_PRAGMA 1
#endif
#ifdef _MSC_VER // We use constant comparison for generated code
#pragma warning(disable : 6326)
#endif
#ifdef _MSC_VER // SDL_MAX_SMALL_ALLOC_STACKSIZE is smaller than _ALLOCA_S_THRESHOLD and should be generally safe
#pragma warning(disable : 6255)
#endif
#define SDL_MAX_SMALL_ALLOC_STACKSIZE 128
#define SDL_small_alloc(type, count, pisstack) ((*(pisstack) = ((sizeof(type) * (count)) < SDL_MAX_SMALL_ALLOC_STACKSIZE)), (*(pisstack) ? SDL_stack_alloc(type, count) : (type *)SDL_malloc(sizeof(type) * (count))))
#define SDL_small_free(ptr, isstack) \
if ((isstack)) { \
SDL_stack_free(ptr); \
} else { \
SDL_free(ptr); \
}
#include "SDL_build_config.h"
//#include "dynapi/SDL_dynapi.h"
#if SDL_DYNAMIC_API
#include "dynapi/SDL_dynapi_overrides.h"
/* force SDL_DECLSPEC off...it's all internal symbols now.
These will have actual #defines during SDL_dynapi.c only */
#ifdef SDL_DECLSPEC
#undef SDL_DECLSPEC
#endif
#define SDL_DECLSPEC
#endif
#ifdef SDL_PLATFORM_APPLE
#ifndef _DARWIN_C_SOURCE
#define _DARWIN_C_SOURCE 1 // for memset_pattern4()
#endif
#include <Availability.h>
#ifndef __IPHONE_OS_VERSION_MAX_ALLOWED
#define __IPHONE_OS_VERSION_MAX_ALLOWED 0
#endif
#ifndef __APPLETV_OS_VERSION_MAX_ALLOWED
#define __APPLETV_OS_VERSION_MAX_ALLOWED 0
#endif
#ifndef __MAC_OS_X_VERSION_MAX_ALLOWED
#define __MAC_OS_X_VERSION_MAX_ALLOWED 0
#endif
#endif // SDL_PLATFORM_APPLE
#ifdef HAVE_SYS_TYPES_H
#include <sys/types.h>
#endif
#ifdef HAVE_STDIO_H
#include <stdio.h>
#endif
#ifdef HAVE_STDLIB_H
#include <stdlib.h>
#elif defined(HAVE_MALLOC_H)
#include <malloc.h>
#endif
#ifdef HAVE_STDDEF_H
#include <stddef.h>
#endif
#ifdef HAVE_STDARG_H
#include <stdarg.h>
#endif
#ifdef HAVE_STRING_H
#ifdef HAVE_MEMORY_H
#include <memory.h>
#endif
#include <string.h>
#endif
#ifdef HAVE_STRINGS_H
#include <strings.h>
#endif
#ifdef HAVE_WCHAR_H
#include <wchar.h>
#endif
#ifdef HAVE_INTTYPES_H
#include <inttypes.h>
#elif defined(HAVE_STDINT_H)
#include <stdint.h>
#endif
#ifdef HAVE_MATH_H
#include <math.h>
#endif
#ifdef HAVE_FLOAT_H
#include <float.h>
#endif
// If you run into a warning that O_CLOEXEC is redefined, update the SDL configuration header for your platform to add HAVE_O_CLOEXEC
#ifndef HAVE_O_CLOEXEC
#define O_CLOEXEC 0
#endif
/* A few #defines to reduce SDL footprint.
Only effective when library is statically linked. */
/* Optimized functions from 'SDL_blit_0.c'
- blit with source bits_per_pixel < 8, palette */
#if !defined(SDL_HAVE_BLIT_0) && !defined(SDL_LEAN_AND_MEAN)
#define SDL_HAVE_BLIT_0 1
#endif
/* Optimized functions from 'SDL_blit_1.c'
- blit with source bytes_per_pixel == 1, palette */
#if !defined(SDL_HAVE_BLIT_1) && !defined(SDL_LEAN_AND_MEAN)
#define SDL_HAVE_BLIT_1 1
#endif
/* Optimized functions from 'SDL_blit_A.c'
- blit with 'SDL_BLENDMODE_BLEND' blending mode */
#if !defined(SDL_HAVE_BLIT_A) && !defined(SDL_LEAN_AND_MEAN)
#define SDL_HAVE_BLIT_A 1
#endif
/* Optimized functions from 'SDL_blit_N.c'
- blit with COLORKEY mode, or nothing */
#if !defined(SDL_HAVE_BLIT_N) && !defined(SDL_LEAN_AND_MEAN)
#define SDL_HAVE_BLIT_N 1
#endif
/* Optimized functions from 'SDL_blit_N.c'
- RGB565 conversion with Lookup tables */
#if !defined(SDL_HAVE_BLIT_N_RGB565) && !defined(SDL_LEAN_AND_MEAN)
#define SDL_HAVE_BLIT_N_RGB565 1
#endif
/* Optimized functions from 'SDL_blit_AUTO.c'
- blit with modulate color, modulate alpha, any blending mode
- scaling or not */
#if !defined(SDL_HAVE_BLIT_AUTO) && !defined(SDL_LEAN_AND_MEAN)
#define SDL_HAVE_BLIT_AUTO 1
#endif
/* Run-Length-Encoding
- SDL_SetSurfaceColorKey() called with SDL_RLEACCEL flag */
#if !defined(SDL_HAVE_RLE) && !defined(SDL_LEAN_AND_MEAN)
#define SDL_HAVE_RLE 1
#endif
/* Software SDL_Renderer
- creation of software renderer
- *not* general blitting functions
- {blend,draw}{fillrect,line,point} internal functions */
#if !defined(SDL_VIDEO_RENDER_SW) && !defined(SDL_LEAN_AND_MEAN)
#define SDL_VIDEO_RENDER_SW 1
#endif
/* STB image conversion */
#if !defined(SDL_HAVE_STB) && !defined(SDL_LEAN_AND_MEAN)
#define SDL_HAVE_STB 1
#endif
/* YUV formats
- handling of YUV surfaces
- blitting and conversion functions */
#if !defined(SDL_HAVE_YUV) && !defined(SDL_LEAN_AND_MEAN)
#define SDL_HAVE_YUV 1
#endif
#ifdef SDL_CAMERA_DISABLED
#undef SDL_CAMERA_DRIVER_ANDROID
#undef SDL_CAMERA_DRIVER_COREMEDIA
#undef SDL_CAMERA_DRIVER_DUMMY
#undef SDL_CAMERA_DRIVER_EMSCRIPTEN
#undef SDL_CAMERA_DRIVER_MEDIAFOUNDATION
#undef SDL_CAMERA_DRIVER_PIPEWIRE
#undef SDL_CAMERA_DRIVER_V4L2
#undef SDL_CAMERA_DRIVER_VITA
#endif
#ifdef SDL_RENDER_DISABLED
#undef SDL_VIDEO_RENDER_SW
#undef SDL_VIDEO_RENDER_D3D
#undef SDL_VIDEO_RENDER_D3D11
#undef SDL_VIDEO_RENDER_D3D12
#undef SDL_VIDEO_RENDER_GPU
#undef SDL_VIDEO_RENDER_METAL
#undef SDL_VIDEO_RENDER_OGL
#undef SDL_VIDEO_RENDER_OGL_ES2
#undef SDL_VIDEO_RENDER_PS2
#undef SDL_VIDEO_RENDER_PSP
#undef SDL_VIDEO_RENDER_VITA_GXM
#undef SDL_VIDEO_RENDER_VULKAN
#endif // SDL_RENDER_DISABLED
#ifdef SDL_GPU_DISABLED
#undef SDL_GPU_D3D12
#undef SDL_GPU_METAL
#undef SDL_GPU_VULKAN
#undef SDL_VIDEO_RENDER_GPU
#endif // SDL_GPU_DISABLED
#if !defined(HAVE_LIBC)
// If not using _any_ C runtime, these have to be defined before SDL_thread.h
// gets included, so internal SDL_CreateThread calls will not try to reference
// the (unavailable and unneeded) _beginthreadex/_endthreadex functions.
#define SDL_BeginThreadFunction NULL
#define SDL_EndThreadFunction NULL
#endif
#ifdef SDL_NOLONGLONG
#error We cannot build a valid SDL3 library without long long support
#endif
/* Enable internal definitions in SDL API headers */
#define SDL_INTERNAL
#include <SDL3/SDL.h>
#include <SDL3/SDL_intrin.h>
#define SDL_MAIN_NOIMPL // don't drag in header-only implementation of SDL_main
#include <SDL3/SDL_main.h>
// Set up for C function definitions, even when using C++
#ifdef __cplusplus
extern "C" {
#endif
#include "SDL_utils_c.h"
#include "SDL_hashtable.h"
#define PUSH_SDL_ERROR() \
{ char *_error = SDL_strdup(SDL_GetError());
#define POP_SDL_ERROR() \
SDL_SetError("%s", _error); SDL_free(_error); }
// Do any initialization that needs to happen before threads are started
extern void SDL_InitMainThread(void);
/* The internal implementations of these functions have up to nanosecond precision.
We can expose these functions as part of the API if we want to later.
*/
extern bool SDLCALL SDL_WaitSemaphoreTimeoutNS(SDL_Semaphore *sem, Sint64 timeoutNS);
extern bool SDLCALL SDL_WaitConditionTimeoutNS(SDL_Condition *cond, SDL_Mutex *mutex, Sint64 timeoutNS);
extern bool SDLCALL SDL_WaitEventTimeoutNS(SDL_Event *event, Sint64 timeoutNS);
// Ends C function definitions when using C++
#ifdef __cplusplus
}
#endif
#endif // SDL_internal_h_

86
thirdparty/sdl/SDL_list.c vendored Normal file
View file

@ -0,0 +1,86 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#include "./SDL_list.h"
// Push
bool SDL_ListAdd(SDL_ListNode **head, void *ent)
{
SDL_ListNode *node = (SDL_ListNode *)SDL_malloc(sizeof(*node));
if (!node) {
return false;
}
node->entry = ent;
node->next = *head;
*head = node;
return true;
}
// Pop from end as a FIFO (if add with SDL_ListAdd)
void SDL_ListPop(SDL_ListNode **head, void **ent)
{
SDL_ListNode **ptr = head;
// Invalid or empty
if (!head || !*head) {
return;
}
while ((*ptr)->next) {
ptr = &(*ptr)->next;
}
if (ent) {
*ent = (*ptr)->entry;
}
SDL_free(*ptr);
*ptr = NULL;
}
void SDL_ListRemove(SDL_ListNode **head, void *ent)
{
SDL_ListNode **ptr = head;
while (*ptr) {
if ((*ptr)->entry == ent) {
SDL_ListNode *tmp = *ptr;
*ptr = (*ptr)->next;
SDL_free(tmp);
return;
}
ptr = &(*ptr)->next;
}
}
void SDL_ListClear(SDL_ListNode **head)
{
SDL_ListNode *l = *head;
*head = NULL;
while (l) {
SDL_ListNode *tmp = l;
l = l->next;
SDL_free(tmp);
}
}

36
thirdparty/sdl/SDL_list.h vendored Normal file
View file

@ -0,0 +1,36 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#ifndef SDL_list_h_
#define SDL_list_h_
typedef struct SDL_ListNode
{
void *entry;
struct SDL_ListNode *next;
} SDL_ListNode;
bool SDL_ListAdd(SDL_ListNode **head, void *ent);
void SDL_ListPop(SDL_ListNode **head, void **ent);
void SDL_ListRemove(SDL_ListNode **head, void *ent);
void SDL_ListClear(SDL_ListNode **head);
#endif // SDL_list_h_

805
thirdparty/sdl/SDL_log.c vendored Normal file
View file

@ -0,0 +1,805 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#if defined(SDL_PLATFORM_WINDOWS)
#include "core/windows/SDL_windows.h"
#endif
// Simple log messages in SDL
#include "SDL_log_c.h"
#ifdef HAVE_STDIO_H
#include <stdio.h>
#endif
#ifdef SDL_PLATFORM_ANDROID
#include <android/log.h>
#endif
#include "stdlib/SDL_vacopy.h"
// The size of the stack buffer to use for rendering log messages.
#define SDL_MAX_LOG_MESSAGE_STACK 256
#define DEFAULT_CATEGORY -1
typedef struct SDL_LogLevel
{
int category;
SDL_LogPriority priority;
struct SDL_LogLevel *next;
} SDL_LogLevel;
// The default log output function
static void SDLCALL SDL_LogOutput(void *userdata, int category, SDL_LogPriority priority, const char *message);
static void CleanupLogPriorities(void);
static void CleanupLogPrefixes(void);
static SDL_InitState SDL_log_init;
static SDL_Mutex *SDL_log_lock;
static SDL_Mutex *SDL_log_function_lock;
static SDL_LogLevel *SDL_loglevels SDL_GUARDED_BY(SDL_log_lock);
static SDL_LogPriority SDL_log_priorities[SDL_LOG_CATEGORY_CUSTOM] SDL_GUARDED_BY(SDL_log_lock);
static SDL_LogPriority SDL_log_default_priority SDL_GUARDED_BY(SDL_log_lock);
static SDL_LogOutputFunction SDL_log_function SDL_GUARDED_BY(SDL_log_function_lock) = SDL_LogOutput;
static void *SDL_log_userdata SDL_GUARDED_BY(SDL_log_function_lock) = NULL;
#ifdef HAVE_GCC_DIAGNOSTIC_PRAGMA
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-variable"
#endif
// If this list changes, update the documentation for SDL_HINT_LOGGING
static const char * const SDL_priority_names[] = {
NULL,
"TRACE",
"VERBOSE",
"DEBUG",
"INFO",
"WARN",
"ERROR",
"CRITICAL"
};
SDL_COMPILE_TIME_ASSERT(priority_names, SDL_arraysize(SDL_priority_names) == SDL_LOG_PRIORITY_COUNT);
// This is guarded by SDL_log_function_lock because it's the logging function that calls GetLogPriorityPrefix()
static char *SDL_priority_prefixes[SDL_LOG_PRIORITY_COUNT] SDL_GUARDED_BY(SDL_log_function_lock);
// If this list changes, update the documentation for SDL_HINT_LOGGING
static const char * const SDL_category_names[] = {
"APP",
"ERROR",
"ASSERT",
"SYSTEM",
"AUDIO",
"VIDEO",
"RENDER",
"INPUT",
"TEST",
"GPU"
};
SDL_COMPILE_TIME_ASSERT(category_names, SDL_arraysize(SDL_category_names) == SDL_LOG_CATEGORY_RESERVED2);
#ifdef HAVE_GCC_DIAGNOSTIC_PRAGMA
#pragma GCC diagnostic pop
#endif
#ifdef SDL_PLATFORM_ANDROID
static int SDL_android_priority[] = {
ANDROID_LOG_UNKNOWN,
ANDROID_LOG_VERBOSE,
ANDROID_LOG_VERBOSE,
ANDROID_LOG_DEBUG,
ANDROID_LOG_INFO,
ANDROID_LOG_WARN,
ANDROID_LOG_ERROR,
ANDROID_LOG_FATAL
};
SDL_COMPILE_TIME_ASSERT(android_priority, SDL_arraysize(SDL_android_priority) == SDL_LOG_PRIORITY_COUNT);
#endif // SDL_PLATFORM_ANDROID
static void SDLCALL SDL_LoggingChanged(void *userdata, const char *name, const char *oldValue, const char *hint)
{
SDL_ResetLogPriorities();
}
void SDL_InitLog(void)
{
if (!SDL_ShouldInit(&SDL_log_init)) {
return;
}
// If these fail we'll continue without them.
SDL_log_lock = SDL_CreateMutex();
SDL_log_function_lock = SDL_CreateMutex();
SDL_AddHintCallback(SDL_HINT_LOGGING, SDL_LoggingChanged, NULL);
SDL_SetInitialized(&SDL_log_init, true);
}
void SDL_QuitLog(void)
{
if (!SDL_ShouldQuit(&SDL_log_init)) {
return;
}
SDL_RemoveHintCallback(SDL_HINT_LOGGING, SDL_LoggingChanged, NULL);
CleanupLogPriorities();
CleanupLogPrefixes();
if (SDL_log_lock) {
SDL_DestroyMutex(SDL_log_lock);
SDL_log_lock = NULL;
}
if (SDL_log_function_lock) {
SDL_DestroyMutex(SDL_log_function_lock);
SDL_log_function_lock = NULL;
}
SDL_SetInitialized(&SDL_log_init, false);
}
static void SDL_CheckInitLog(void)
{
int status = SDL_GetAtomicInt(&SDL_log_init.status);
if (status == SDL_INIT_STATUS_INITIALIZED ||
(status == SDL_INIT_STATUS_INITIALIZING && SDL_log_init.thread == SDL_GetCurrentThreadID())) {
return;
}
SDL_InitLog();
}
static void CleanupLogPriorities(void)
{
while (SDL_loglevels) {
SDL_LogLevel *entry = SDL_loglevels;
SDL_loglevels = entry->next;
SDL_free(entry);
}
}
void SDL_SetLogPriorities(SDL_LogPriority priority)
{
SDL_CheckInitLog();
SDL_LockMutex(SDL_log_lock);
{
CleanupLogPriorities();
SDL_log_default_priority = priority;
for (int i = 0; i < SDL_arraysize(SDL_log_priorities); ++i) {
SDL_log_priorities[i] = priority;
}
}
SDL_UnlockMutex(SDL_log_lock);
}
void SDL_SetLogPriority(int category, SDL_LogPriority priority)
{
SDL_LogLevel *entry;
SDL_CheckInitLog();
SDL_LockMutex(SDL_log_lock);
{
if (category >= 0 && category < SDL_arraysize(SDL_log_priorities)) {
SDL_log_priorities[category] = priority;
} else {
for (entry = SDL_loglevels; entry; entry = entry->next) {
if (entry->category == category) {
entry->priority = priority;
break;
}
}
if (!entry) {
entry = (SDL_LogLevel *)SDL_malloc(sizeof(*entry));
if (entry) {
entry->category = category;
entry->priority = priority;
entry->next = SDL_loglevels;
SDL_loglevels = entry;
}
}
}
}
SDL_UnlockMutex(SDL_log_lock);
}
SDL_LogPriority SDL_GetLogPriority(int category)
{
SDL_LogLevel *entry;
SDL_LogPriority priority = SDL_LOG_PRIORITY_INVALID;
SDL_CheckInitLog();
// Bypass the lock for known categories
// Technically if the priority was set on a different CPU the value might not
// be visible on this CPU for a while, but in practice it's fast enough that
// this performance improvement is worthwhile.
if (category >= 0 && category < SDL_arraysize(SDL_log_priorities)) {
return SDL_log_priorities[category];
}
SDL_LockMutex(SDL_log_lock);
{
if (category >= 0 && category < SDL_arraysize(SDL_log_priorities)) {
priority = SDL_log_priorities[category];
} else {
for (entry = SDL_loglevels; entry; entry = entry->next) {
if (entry->category == category) {
priority = entry->priority;
break;
}
}
if (priority == SDL_LOG_PRIORITY_INVALID) {
priority = SDL_log_default_priority;
}
}
}
SDL_UnlockMutex(SDL_log_lock);
return priority;
}
static bool ParseLogCategory(const char *string, size_t length, int *category)
{
int i;
if (SDL_isdigit(*string)) {
*category = SDL_atoi(string);
return true;
}
if (*string == '*') {
*category = DEFAULT_CATEGORY;
return true;
}
for (i = 0; i < SDL_arraysize(SDL_category_names); ++i) {
if (SDL_strncasecmp(string, SDL_category_names[i], length) == 0) {
*category = i;
return true;
}
}
return false;
}
static bool ParseLogPriority(const char *string, size_t length, SDL_LogPriority *priority)
{
int i;
if (SDL_isdigit(*string)) {
i = SDL_atoi(string);
if (i == 0) {
// 0 has a special meaning of "disable this category"
*priority = SDL_LOG_PRIORITY_COUNT;
return true;
}
if (i > SDL_LOG_PRIORITY_INVALID && i < SDL_LOG_PRIORITY_COUNT) {
*priority = (SDL_LogPriority)i;
return true;
}
return false;
}
if (SDL_strncasecmp(string, "quiet", length) == 0) {
*priority = SDL_LOG_PRIORITY_COUNT;
return true;
}
for (i = SDL_LOG_PRIORITY_INVALID + 1; i < SDL_LOG_PRIORITY_COUNT; ++i) {
if (SDL_strncasecmp(string, SDL_priority_names[i], length) == 0) {
*priority = (SDL_LogPriority)i;
return true;
}
}
return false;
}
static void ParseLogPriorities(const char *hint)
{
const char *name, *next;
int category = DEFAULT_CATEGORY;
SDL_LogPriority priority = SDL_LOG_PRIORITY_INVALID;
if (SDL_strchr(hint, '=') == NULL) {
if (ParseLogPriority(hint, SDL_strlen(hint), &priority)) {
SDL_SetLogPriorities(priority);
}
return;
}
for (name = hint; name; name = next) {
const char *sep = SDL_strchr(name, '=');
if (!sep) {
break;
}
next = SDL_strchr(sep, ',');
if (next) {
++next;
}
if (ParseLogCategory(name, (sep - name), &category)) {
const char *value = sep + 1;
size_t len;
if (next) {
len = (next - value - 1);
} else {
len = SDL_strlen(value);
}
if (ParseLogPriority(value, len, &priority)) {
if (category == DEFAULT_CATEGORY) {
for (int i = 0; i < SDL_arraysize(SDL_log_priorities); ++i) {
if (SDL_log_priorities[i] == SDL_LOG_PRIORITY_INVALID) {
SDL_log_priorities[i] = priority;
}
}
SDL_log_default_priority = priority;
} else {
SDL_SetLogPriority(category, priority);
}
}
}
}
}
void SDL_ResetLogPriorities(void)
{
SDL_CheckInitLog();
SDL_LockMutex(SDL_log_lock);
{
CleanupLogPriorities();
SDL_log_default_priority = SDL_LOG_PRIORITY_INVALID;
for (int i = 0; i < SDL_arraysize(SDL_log_priorities); ++i) {
SDL_log_priorities[i] = SDL_LOG_PRIORITY_INVALID;
}
const char *hint = SDL_GetHint(SDL_HINT_LOGGING);
if (hint) {
ParseLogPriorities(hint);
}
if (SDL_log_default_priority == SDL_LOG_PRIORITY_INVALID) {
SDL_log_default_priority = SDL_LOG_PRIORITY_ERROR;
}
for (int i = 0; i < SDL_arraysize(SDL_log_priorities); ++i) {
if (SDL_log_priorities[i] != SDL_LOG_PRIORITY_INVALID) {
continue;
}
switch (i) {
case SDL_LOG_CATEGORY_APPLICATION:
SDL_log_priorities[i] = SDL_LOG_PRIORITY_INFO;
break;
case SDL_LOG_CATEGORY_ASSERT:
SDL_log_priorities[i] = SDL_LOG_PRIORITY_WARN;
break;
case SDL_LOG_CATEGORY_TEST:
SDL_log_priorities[i] = SDL_LOG_PRIORITY_VERBOSE;
break;
default:
SDL_log_priorities[i] = SDL_LOG_PRIORITY_ERROR;
break;
}
}
}
SDL_UnlockMutex(SDL_log_lock);
}
static void CleanupLogPrefixes(void)
{
for (int i = 0; i < SDL_arraysize(SDL_priority_prefixes); ++i) {
if (SDL_priority_prefixes[i]) {
SDL_free(SDL_priority_prefixes[i]);
SDL_priority_prefixes[i] = NULL;
}
}
}
static const char *GetLogPriorityPrefix(SDL_LogPriority priority)
{
if (priority <= SDL_LOG_PRIORITY_INVALID || priority >= SDL_LOG_PRIORITY_COUNT) {
return "";
}
if (SDL_priority_prefixes[priority]) {
return SDL_priority_prefixes[priority];
}
switch (priority) {
case SDL_LOG_PRIORITY_WARN:
return "WARNING: ";
case SDL_LOG_PRIORITY_ERROR:
return "ERROR: ";
case SDL_LOG_PRIORITY_CRITICAL:
return "ERROR: ";
default:
return "";
}
}
bool SDL_SetLogPriorityPrefix(SDL_LogPriority priority, const char *prefix)
{
char *prefix_copy;
if (priority <= SDL_LOG_PRIORITY_INVALID || priority >= SDL_LOG_PRIORITY_COUNT) {
return SDL_InvalidParamError("priority");
}
if (!prefix || !*prefix) {
prefix_copy = SDL_strdup("");
} else {
prefix_copy = SDL_strdup(prefix);
}
if (!prefix_copy) {
return false;
}
SDL_LockMutex(SDL_log_function_lock);
{
if (SDL_priority_prefixes[priority]) {
SDL_free(SDL_priority_prefixes[priority]);
}
SDL_priority_prefixes[priority] = prefix_copy;
}
SDL_UnlockMutex(SDL_log_function_lock);
return true;
}
void SDL_Log(SDL_PRINTF_FORMAT_STRING const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
SDL_LogMessageV(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_INFO, fmt, ap);
va_end(ap);
}
void SDL_LogTrace(int category, SDL_PRINTF_FORMAT_STRING const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
SDL_LogMessageV(category, SDL_LOG_PRIORITY_TRACE, fmt, ap);
va_end(ap);
}
void SDL_LogVerbose(int category, SDL_PRINTF_FORMAT_STRING const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
SDL_LogMessageV(category, SDL_LOG_PRIORITY_VERBOSE, fmt, ap);
va_end(ap);
}
void SDL_LogDebug(int category, SDL_PRINTF_FORMAT_STRING const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
SDL_LogMessageV(category, SDL_LOG_PRIORITY_DEBUG, fmt, ap);
va_end(ap);
}
void SDL_LogInfo(int category, SDL_PRINTF_FORMAT_STRING const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
SDL_LogMessageV(category, SDL_LOG_PRIORITY_INFO, fmt, ap);
va_end(ap);
}
void SDL_LogWarn(int category, SDL_PRINTF_FORMAT_STRING const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
SDL_LogMessageV(category, SDL_LOG_PRIORITY_WARN, fmt, ap);
va_end(ap);
}
void SDL_LogError(int category, SDL_PRINTF_FORMAT_STRING const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
SDL_LogMessageV(category, SDL_LOG_PRIORITY_ERROR, fmt, ap);
va_end(ap);
}
void SDL_LogCritical(int category, SDL_PRINTF_FORMAT_STRING const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
SDL_LogMessageV(category, SDL_LOG_PRIORITY_CRITICAL, fmt, ap);
va_end(ap);
}
void SDL_LogMessage(int category, SDL_LogPriority priority, SDL_PRINTF_FORMAT_STRING const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
SDL_LogMessageV(category, priority, fmt, ap);
va_end(ap);
}
#ifdef SDL_PLATFORM_ANDROID
static const char *GetCategoryPrefix(int category)
{
if (category < SDL_LOG_CATEGORY_RESERVED2) {
return SDL_category_names[category];
}
if (category < SDL_LOG_CATEGORY_CUSTOM) {
return "RESERVED";
}
return "CUSTOM";
}
#endif // SDL_PLATFORM_ANDROID
void SDL_LogMessageV(int category, SDL_LogPriority priority, SDL_PRINTF_FORMAT_STRING const char *fmt, va_list ap)
{
char *message = NULL;
char stack_buf[SDL_MAX_LOG_MESSAGE_STACK];
size_t len_plus_term;
int len;
va_list aq;
// Nothing to do if we don't have an output function
if (!SDL_log_function) {
return;
}
// See if we want to do anything with this message
if (priority < SDL_GetLogPriority(category)) {
return;
}
// Render into stack buffer
va_copy(aq, ap);
len = SDL_vsnprintf(stack_buf, sizeof(stack_buf), fmt, aq);
va_end(aq);
if (len < 0) {
return;
}
// If message truncated, allocate and re-render
if (len >= sizeof(stack_buf) && SDL_size_add_check_overflow(len, 1, &len_plus_term)) {
// Allocate exactly what we need, including the zero-terminator
message = (char *)SDL_malloc(len_plus_term);
if (!message) {
return;
}
va_copy(aq, ap);
len = SDL_vsnprintf(message, len_plus_term, fmt, aq);
va_end(aq);
} else {
message = stack_buf;
}
// Chop off final endline.
if ((len > 0) && (message[len - 1] == '\n')) {
message[--len] = '\0';
if ((len > 0) && (message[len - 1] == '\r')) { // catch "\r\n", too.
message[--len] = '\0';
}
}
SDL_LockMutex(SDL_log_function_lock);
{
SDL_log_function(SDL_log_userdata, category, priority, message);
}
SDL_UnlockMutex(SDL_log_function_lock);
// Free only if dynamically allocated
if (message != stack_buf) {
SDL_free(message);
}
}
#if defined(SDL_PLATFORM_WIN32) && !defined(SDL_PLATFORM_GDK)
enum {
CONSOLE_UNATTACHED = 0,
CONSOLE_ATTACHED_CONSOLE = 1,
CONSOLE_ATTACHED_FILE = 2,
CONSOLE_ATTACHED_ERROR = -1,
} consoleAttached = CONSOLE_UNATTACHED;
// Handle to stderr output of console.
static HANDLE stderrHandle = NULL;
#endif
static void SDLCALL SDL_LogOutput(void *userdata, int category, SDL_LogPriority priority,
const char *message)
{
#if defined(SDL_PLATFORM_WINDOWS)
// Way too many allocations here, urgh
// Note: One can't call SDL_SetError here, since that function itself logs.
{
char *output;
size_t length;
LPTSTR tstr;
bool isstack;
#if !defined(SDL_PLATFORM_GDK)
BOOL attachResult;
DWORD attachError;
DWORD consoleMode;
DWORD charsWritten;
// Maybe attach console and get stderr handle
if (consoleAttached == CONSOLE_UNATTACHED) {
attachResult = AttachConsole(ATTACH_PARENT_PROCESS);
if (!attachResult) {
attachError = GetLastError();
if (attachError == ERROR_INVALID_HANDLE) {
// This is expected when running from Visual Studio
// OutputDebugString(TEXT("Parent process has no console\r\n"));
consoleAttached = CONSOLE_ATTACHED_ERROR;
} else if (attachError == ERROR_GEN_FAILURE) {
OutputDebugString(TEXT("Could not attach to console of parent process\r\n"));
consoleAttached = CONSOLE_ATTACHED_ERROR;
} else if (attachError == ERROR_ACCESS_DENIED) {
// Already attached
consoleAttached = CONSOLE_ATTACHED_CONSOLE;
} else {
OutputDebugString(TEXT("Error attaching console\r\n"));
consoleAttached = CONSOLE_ATTACHED_ERROR;
}
} else {
// Newly attached
consoleAttached = CONSOLE_ATTACHED_CONSOLE;
}
if (consoleAttached == CONSOLE_ATTACHED_CONSOLE) {
stderrHandle = GetStdHandle(STD_ERROR_HANDLE);
if (GetConsoleMode(stderrHandle, &consoleMode) == 0) {
// WriteConsole fails if the output is redirected to a file. Must use WriteFile instead.
consoleAttached = CONSOLE_ATTACHED_FILE;
}
}
}
#endif // !defined(SDL_PLATFORM_GDK)
length = SDL_strlen(GetLogPriorityPrefix(priority)) + SDL_strlen(message) + 1 + 1 + 1;
output = SDL_small_alloc(char, length, &isstack);
if (!output) {
return;
}
(void)SDL_snprintf(output, length, "%s%s\r\n", GetLogPriorityPrefix(priority), message);
tstr = WIN_UTF8ToString(output);
// Output to debugger
OutputDebugString(tstr);
#if !defined(SDL_PLATFORM_GDK)
// Screen output to stderr, if console was attached.
if (consoleAttached == CONSOLE_ATTACHED_CONSOLE) {
if (!WriteConsole(stderrHandle, tstr, (DWORD)SDL_tcslen(tstr), &charsWritten, NULL)) {
OutputDebugString(TEXT("Error calling WriteConsole\r\n"));
if (GetLastError() == ERROR_NOT_ENOUGH_MEMORY) {
OutputDebugString(TEXT("Insufficient heap memory to write message\r\n"));
}
}
} else if (consoleAttached == CONSOLE_ATTACHED_FILE) {
if (!WriteFile(stderrHandle, output, (DWORD)SDL_strlen(output), &charsWritten, NULL)) {
OutputDebugString(TEXT("Error calling WriteFile\r\n"));
}
}
#endif // !defined(SDL_PLATFORM_GDK)
SDL_free(tstr);
SDL_small_free(output, isstack);
}
#elif defined(SDL_PLATFORM_ANDROID)
{
char tag[32];
SDL_snprintf(tag, SDL_arraysize(tag), "SDL/%s", GetCategoryPrefix(category));
__android_log_write(SDL_android_priority[priority], tag, message);
}
#elif defined(SDL_PLATFORM_APPLE) && (defined(SDL_VIDEO_DRIVER_COCOA) || defined(SDL_VIDEO_DRIVER_UIKIT))
/* Technically we don't need Cocoa/UIKit, but that's where this function is defined for now.
*/
extern void SDL_NSLog(const char *prefix, const char *text);
{
SDL_NSLog(GetLogPriorityPrefix(priority), message);
return;
}
#elif defined(SDL_PLATFORM_PSP) || defined(SDL_PLATFORM_PS2)
{
FILE *pFile;
pFile = fopen("SDL_Log.txt", "a");
if (pFile) {
(void)fprintf(pFile, "%s%s\n", GetLogPriorityPrefix(priority), message);
(void)fclose(pFile);
}
}
#elif defined(SDL_PLATFORM_VITA)
{
FILE *pFile;
pFile = fopen("ux0:/data/SDL_Log.txt", "a");
if (pFile) {
(void)fprintf(pFile, "%s%s\n", GetLogPriorityPrefix(priority), message);
(void)fclose(pFile);
}
}
#elif defined(SDL_PLATFORM_3DS)
{
FILE *pFile;
pFile = fopen("sdmc:/3ds/SDL_Log.txt", "a");
if (pFile) {
(void)fprintf(pFile, "%s%s\n", GetLogPriorityPrefix(priority), message);
(void)fclose(pFile);
}
}
#endif
#if defined(HAVE_STDIO_H) && \
!(defined(SDL_PLATFORM_APPLE) && (defined(SDL_VIDEO_DRIVER_COCOA) || defined(SDL_VIDEO_DRIVER_UIKIT))) && \
!(defined(SDL_PLATFORM_WIN32))
(void)fprintf(stderr, "%s%s\n", GetLogPriorityPrefix(priority), message);
#endif
}
SDL_LogOutputFunction SDL_GetDefaultLogOutputFunction(void)
{
return SDL_LogOutput;
}
void SDL_GetLogOutputFunction(SDL_LogOutputFunction *callback, void **userdata)
{
SDL_LockMutex(SDL_log_function_lock);
{
if (callback) {
*callback = SDL_log_function;
}
if (userdata) {
*userdata = SDL_log_userdata;
}
}
SDL_UnlockMutex(SDL_log_function_lock);
}
void SDL_SetLogOutputFunction(SDL_LogOutputFunction callback, void *userdata)
{
SDL_LockMutex(SDL_log_function_lock);
{
SDL_log_function = callback;
SDL_log_userdata = userdata;
}
SDL_UnlockMutex(SDL_log_function_lock);
}

31
thirdparty/sdl/SDL_log_c.h vendored Normal file
View file

@ -0,0 +1,31 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
// This file defines useful function for working with SDL logging
#ifndef SDL_log_c_h_
#define SDL_log_c_h_
extern void SDL_InitLog(void);
extern void SDL_QuitLog(void);
#endif // SDL_log_c_h_

824
thirdparty/sdl/SDL_properties.c vendored Normal file
View file

@ -0,0 +1,824 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#include "SDL_hints_c.h"
#include "SDL_properties_c.h"
typedef struct
{
SDL_PropertyType type;
union {
void *pointer_value;
char *string_value;
Sint64 number_value;
float float_value;
bool boolean_value;
} value;
char *string_storage;
SDL_CleanupPropertyCallback cleanup;
void *userdata;
} SDL_Property;
typedef struct
{
SDL_HashTable *props;
SDL_Mutex *lock;
} SDL_Properties;
static SDL_InitState SDL_properties_init;
static SDL_HashTable *SDL_properties;
static SDL_AtomicU32 SDL_last_properties_id;
static SDL_AtomicU32 SDL_global_properties;
static void SDL_FreePropertyWithCleanup(const void *key, const void *value, void *data, bool cleanup)
{
SDL_Property *property = (SDL_Property *)value;
if (property) {
switch (property->type) {
case SDL_PROPERTY_TYPE_POINTER:
if (property->cleanup && cleanup) {
property->cleanup(property->userdata, property->value.pointer_value);
}
break;
case SDL_PROPERTY_TYPE_STRING:
SDL_free(property->value.string_value);
break;
default:
break;
}
SDL_free(property->string_storage);
}
SDL_free((void *)key);
SDL_free((void *)value);
}
static void SDLCALL SDL_FreeProperty(void *data, const void *key, const void *value)
{
SDL_FreePropertyWithCleanup(key, value, data, true);
}
static void SDL_FreeProperties(SDL_Properties *properties)
{
if (properties) {
SDL_DestroyHashTable(properties->props);
SDL_DestroyMutex(properties->lock);
SDL_free(properties);
}
}
bool SDL_InitProperties(void)
{
if (!SDL_ShouldInit(&SDL_properties_init)) {
return true;
}
SDL_properties = SDL_CreateHashTable(0, true, SDL_HashID, SDL_KeyMatchID, NULL, NULL);
const bool initialized = (SDL_properties != NULL);
SDL_SetInitialized(&SDL_properties_init, initialized);
return initialized;
}
static bool SDLCALL FreeOneProperties(void *userdata, const SDL_HashTable *table, const void *key, const void *value)
{
SDL_FreeProperties((SDL_Properties *)value);
return true; // keep iterating.
}
void SDL_QuitProperties(void)
{
if (!SDL_ShouldQuit(&SDL_properties_init)) {
return;
}
SDL_PropertiesID props;
do {
props = SDL_GetAtomicU32(&SDL_global_properties);
} while (!SDL_CompareAndSwapAtomicU32(&SDL_global_properties, props, 0));
if (props) {
SDL_DestroyProperties(props);
}
// this can't just DestroyHashTable with SDL_FreeProperties as the destructor, because
// other destructors under this might cause use to attempt a recursive lock on SDL_properties,
// which isn't allowed with rwlocks. So manually iterate and free everything.
SDL_HashTable *properties = SDL_properties;
SDL_properties = NULL;
SDL_IterateHashTable(properties, FreeOneProperties, NULL);
SDL_DestroyHashTable(properties);
SDL_SetInitialized(&SDL_properties_init, false);
}
static bool SDL_CheckInitProperties(void)
{
return SDL_InitProperties();
}
SDL_PropertiesID SDL_GetGlobalProperties(void)
{
SDL_PropertiesID props = SDL_GetAtomicU32(&SDL_global_properties);
if (!props) {
props = SDL_CreateProperties();
if (!SDL_CompareAndSwapAtomicU32(&SDL_global_properties, 0, props)) {
// Somebody else created global properties before us, just use those
SDL_DestroyProperties(props);
props = SDL_GetAtomicU32(&SDL_global_properties);
}
}
return props;
}
SDL_PropertiesID SDL_CreateProperties(void)
{
if (!SDL_CheckInitProperties()) {
return 0;
}
SDL_Properties *properties = (SDL_Properties *)SDL_calloc(1, sizeof(*properties));
if (!properties) {
return 0;
}
properties->lock = SDL_CreateMutex();
if (!properties->lock) {
SDL_free(properties);
return 0;
}
properties->props = SDL_CreateHashTable(0, false, SDL_HashString, SDL_KeyMatchString, SDL_FreeProperty, NULL);
if (!properties->props) {
SDL_DestroyMutex(properties->lock);
SDL_free(properties);
return 0;
}
SDL_PropertiesID props = 0;
while (true) {
props = (SDL_GetAtomicU32(&SDL_last_properties_id) + 1);
if (props == 0) {
continue;
} else if (SDL_CompareAndSwapAtomicU32(&SDL_last_properties_id, props - 1, props)) {
break;
}
}
SDL_assert(!SDL_FindInHashTable(SDL_properties, (const void *)(uintptr_t)props, NULL)); // should NOT be in the hash table already.
if (!SDL_InsertIntoHashTable(SDL_properties, (const void *)(uintptr_t)props, properties, false)) {
SDL_FreeProperties(properties);
return 0;
}
return props; // All done!
}
typedef struct CopyOnePropertyData
{
SDL_Properties *dst_properties;
bool result;
} CopyOnePropertyData;
static bool SDLCALL CopyOneProperty(void *userdata, const SDL_HashTable *table, const void *key, const void *value)
{
const SDL_Property *src_property = (const SDL_Property *)value;
if (src_property->cleanup) {
// Can't copy properties with cleanup functions, we don't know how to duplicate the data
return true; // keep iterating.
}
CopyOnePropertyData *data = (CopyOnePropertyData *) userdata;
SDL_Properties *dst_properties = data->dst_properties;
const char *src_name = (const char *)key;
SDL_Property *dst_property;
char *dst_name = SDL_strdup(src_name);
if (!dst_name) {
data->result = false;
return true; // keep iterating (I guess...?)
}
dst_property = (SDL_Property *)SDL_malloc(sizeof(*dst_property));
if (!dst_property) {
SDL_free(dst_name);
data->result = false;
return true; // keep iterating (I guess...?)
}
SDL_copyp(dst_property, src_property);
if (src_property->type == SDL_PROPERTY_TYPE_STRING) {
dst_property->value.string_value = SDL_strdup(src_property->value.string_value);
if (!dst_property->value.string_value) {
SDL_free(dst_name);
SDL_free(dst_property);
data->result = false;
return true; // keep iterating (I guess...?)
}
}
if (!SDL_InsertIntoHashTable(dst_properties->props, dst_name, dst_property, true)) {
SDL_FreePropertyWithCleanup(dst_name, dst_property, NULL, false);
data->result = false;
}
return true; // keep iterating.
}
bool SDL_CopyProperties(SDL_PropertiesID src, SDL_PropertiesID dst)
{
if (!src) {
return SDL_InvalidParamError("src");
}
if (!dst) {
return SDL_InvalidParamError("dst");
}
SDL_Properties *src_properties = NULL;
SDL_Properties *dst_properties = NULL;
SDL_FindInHashTable(SDL_properties, (const void *)(uintptr_t)src, (const void **)&src_properties);
if (!src_properties) {
return SDL_InvalidParamError("src");
}
SDL_FindInHashTable(SDL_properties, (const void *)(uintptr_t)dst, (const void **)&dst_properties);
if (!dst_properties) {
return SDL_InvalidParamError("dst");
}
bool result = true;
SDL_LockMutex(src_properties->lock);
SDL_LockMutex(dst_properties->lock);
{
CopyOnePropertyData data = { dst_properties, true };
SDL_IterateHashTable(src_properties->props, CopyOneProperty, &data);
result = data.result;
}
SDL_UnlockMutex(dst_properties->lock);
SDL_UnlockMutex(src_properties->lock);
return result;
}
bool SDL_LockProperties(SDL_PropertiesID props)
{
SDL_Properties *properties = NULL;
if (!props) {
return SDL_InvalidParamError("props");
}
SDL_FindInHashTable(SDL_properties, (const void *)(uintptr_t)props, (const void **)&properties);
if (!properties) {
return SDL_InvalidParamError("props");
}
SDL_LockMutex(properties->lock);
return true;
}
void SDL_UnlockProperties(SDL_PropertiesID props)
{
SDL_Properties *properties = NULL;
if (!props) {
return;
}
SDL_FindInHashTable(SDL_properties, (const void *)(uintptr_t)props, (const void **)&properties);
if (!properties) {
return;
}
SDL_UnlockMutex(properties->lock);
}
static bool SDL_PrivateSetProperty(SDL_PropertiesID props, const char *name, SDL_Property *property)
{
SDL_Properties *properties = NULL;
bool result = true;
if (!props) {
SDL_FreePropertyWithCleanup(NULL, property, NULL, true);
return SDL_InvalidParamError("props");
}
if (!name || !*name) {
SDL_FreePropertyWithCleanup(NULL, property, NULL, true);
return SDL_InvalidParamError("name");
}
SDL_FindInHashTable(SDL_properties, (const void *)(uintptr_t)props, (const void **)&properties);
if (!properties) {
SDL_FreePropertyWithCleanup(NULL, property, NULL, true);
return SDL_InvalidParamError("props");
}
SDL_LockMutex(properties->lock);
{
SDL_RemoveFromHashTable(properties->props, name);
if (property) {
char *key = SDL_strdup(name);
if (!key || !SDL_InsertIntoHashTable(properties->props, key, property, false)) {
SDL_FreePropertyWithCleanup(key, property, NULL, true);
result = false;
}
}
}
SDL_UnlockMutex(properties->lock);
return result;
}
bool SDL_SetPointerPropertyWithCleanup(SDL_PropertiesID props, const char *name, void *value, SDL_CleanupPropertyCallback cleanup, void *userdata)
{
SDL_Property *property;
if (!value) {
if (cleanup) {
cleanup(userdata, value);
}
return SDL_ClearProperty(props, name);
}
property = (SDL_Property *)SDL_calloc(1, sizeof(*property));
if (!property) {
if (cleanup) {
cleanup(userdata, value);
}
SDL_FreePropertyWithCleanup(NULL, property, NULL, false);
return false;
}
property->type = SDL_PROPERTY_TYPE_POINTER;
property->value.pointer_value = value;
property->cleanup = cleanup;
property->userdata = userdata;
return SDL_PrivateSetProperty(props, name, property);
}
bool SDL_SetPointerProperty(SDL_PropertiesID props, const char *name, void *value)
{
SDL_Property *property;
if (!value) {
return SDL_ClearProperty(props, name);
}
property = (SDL_Property *)SDL_calloc(1, sizeof(*property));
if (!property) {
return false;
}
property->type = SDL_PROPERTY_TYPE_POINTER;
property->value.pointer_value = value;
return SDL_PrivateSetProperty(props, name, property);
}
static void SDLCALL CleanupFreeableProperty(void *userdata, void *value)
{
SDL_free(value);
}
bool SDL_SetFreeableProperty(SDL_PropertiesID props, const char *name, void *value)
{
return SDL_SetPointerPropertyWithCleanup(props, name, value, CleanupFreeableProperty, NULL);
}
static void SDLCALL CleanupSurface(void *userdata, void *value)
{
SDL_Surface *surface = (SDL_Surface *)value;
//SDL_DestroySurface(surface);
}
bool SDL_SetSurfaceProperty(SDL_PropertiesID props, const char *name, SDL_Surface *surface)
{
return SDL_SetPointerPropertyWithCleanup(props, name, surface, CleanupSurface, NULL);
}
bool SDL_SetStringProperty(SDL_PropertiesID props, const char *name, const char *value)
{
SDL_Property *property;
if (!value) {
return SDL_ClearProperty(props, name);
}
property = (SDL_Property *)SDL_calloc(1, sizeof(*property));
if (!property) {
return false;
}
property->type = SDL_PROPERTY_TYPE_STRING;
property->value.string_value = SDL_strdup(value);
if (!property->value.string_value) {
SDL_free(property);
return false;
}
return SDL_PrivateSetProperty(props, name, property);
}
bool SDL_SetNumberProperty(SDL_PropertiesID props, const char *name, Sint64 value)
{
SDL_Property *property = (SDL_Property *)SDL_calloc(1, sizeof(*property));
if (!property) {
return false;
}
property->type = SDL_PROPERTY_TYPE_NUMBER;
property->value.number_value = value;
return SDL_PrivateSetProperty(props, name, property);
}
bool SDL_SetFloatProperty(SDL_PropertiesID props, const char *name, float value)
{
SDL_Property *property = (SDL_Property *)SDL_calloc(1, sizeof(*property));
if (!property) {
return false;
}
property->type = SDL_PROPERTY_TYPE_FLOAT;
property->value.float_value = value;
return SDL_PrivateSetProperty(props, name, property);
}
bool SDL_SetBooleanProperty(SDL_PropertiesID props, const char *name, bool value)
{
SDL_Property *property = (SDL_Property *)SDL_calloc(1, sizeof(*property));
if (!property) {
return false;
}
property->type = SDL_PROPERTY_TYPE_BOOLEAN;
property->value.boolean_value = value ? true : false;
return SDL_PrivateSetProperty(props, name, property);
}
bool SDL_HasProperty(SDL_PropertiesID props, const char *name)
{
return (SDL_GetPropertyType(props, name) != SDL_PROPERTY_TYPE_INVALID);
}
SDL_PropertyType SDL_GetPropertyType(SDL_PropertiesID props, const char *name)
{
SDL_Properties *properties = NULL;
SDL_PropertyType type = SDL_PROPERTY_TYPE_INVALID;
if (!props) {
return SDL_PROPERTY_TYPE_INVALID;
}
if (!name || !*name) {
return SDL_PROPERTY_TYPE_INVALID;
}
SDL_FindInHashTable(SDL_properties, (const void *)(uintptr_t)props, (const void **)&properties);
if (!properties) {
return SDL_PROPERTY_TYPE_INVALID;
}
SDL_LockMutex(properties->lock);
{
SDL_Property *property = NULL;
if (SDL_FindInHashTable(properties->props, name, (const void **)&property)) {
type = property->type;
}
}
SDL_UnlockMutex(properties->lock);
return type;
}
void *SDL_GetPointerProperty(SDL_PropertiesID props, const char *name, void *default_value)
{
SDL_Properties *properties = NULL;
void *value = default_value;
if (!props) {
return value;
}
if (!name || !*name) {
return value;
}
SDL_FindInHashTable(SDL_properties, (const void *)(uintptr_t)props, (const void **)&properties);
if (!properties) {
return value;
}
// Note that taking the lock here only guarantees that we won't read the
// hashtable while it's being modified. The value itself can easily be
// freed from another thread after it is returned here.
SDL_LockMutex(properties->lock);
{
SDL_Property *property = NULL;
if (SDL_FindInHashTable(properties->props, name, (const void **)&property)) {
if (property->type == SDL_PROPERTY_TYPE_POINTER) {
value = property->value.pointer_value;
}
}
}
SDL_UnlockMutex(properties->lock);
return value;
}
const char *SDL_GetStringProperty(SDL_PropertiesID props, const char *name, const char *default_value)
{
SDL_Properties *properties = NULL;
const char *value = default_value;
if (!props) {
return value;
}
if (!name || !*name) {
return value;
}
SDL_FindInHashTable(SDL_properties, (const void *)(uintptr_t)props, (const void **)&properties);
if (!properties) {
return value;
}
SDL_LockMutex(properties->lock);
{
SDL_Property *property = NULL;
if (SDL_FindInHashTable(properties->props, name, (const void **)&property)) {
switch (property->type) {
case SDL_PROPERTY_TYPE_STRING:
value = property->value.string_value;
break;
case SDL_PROPERTY_TYPE_NUMBER:
if (property->string_storage) {
value = property->string_storage;
} else {
SDL_asprintf(&property->string_storage, "%" SDL_PRIs64, property->value.number_value);
if (property->string_storage) {
value = property->string_storage;
}
}
break;
case SDL_PROPERTY_TYPE_FLOAT:
if (property->string_storage) {
value = property->string_storage;
} else {
SDL_asprintf(&property->string_storage, "%f", property->value.float_value);
if (property->string_storage) {
value = property->string_storage;
}
}
break;
case SDL_PROPERTY_TYPE_BOOLEAN:
value = property->value.boolean_value ? "true" : "false";
break;
default:
break;
}
}
}
SDL_UnlockMutex(properties->lock);
return value;
}
Sint64 SDL_GetNumberProperty(SDL_PropertiesID props, const char *name, Sint64 default_value)
{
SDL_Properties *properties = NULL;
Sint64 value = default_value;
if (!props) {
return value;
}
if (!name || !*name) {
return value;
}
SDL_FindInHashTable(SDL_properties, (const void *)(uintptr_t)props, (const void **)&properties);
if (!properties) {
return value;
}
SDL_LockMutex(properties->lock);
{
SDL_Property *property = NULL;
if (SDL_FindInHashTable(properties->props, name, (const void **)&property)) {
switch (property->type) {
case SDL_PROPERTY_TYPE_STRING:
value = (Sint64)SDL_strtoll(property->value.string_value, NULL, 0);
break;
case SDL_PROPERTY_TYPE_NUMBER:
value = property->value.number_value;
break;
case SDL_PROPERTY_TYPE_FLOAT:
value = (Sint64)SDL_round((double)property->value.float_value);
break;
case SDL_PROPERTY_TYPE_BOOLEAN:
value = property->value.boolean_value;
break;
default:
break;
}
}
}
SDL_UnlockMutex(properties->lock);
return value;
}
float SDL_GetFloatProperty(SDL_PropertiesID props, const char *name, float default_value)
{
SDL_Properties *properties = NULL;
float value = default_value;
if (!props) {
return value;
}
if (!name || !*name) {
return value;
}
SDL_FindInHashTable(SDL_properties, (const void *)(uintptr_t)props, (const void **)&properties);
if (!properties) {
return value;
}
SDL_LockMutex(properties->lock);
{
SDL_Property *property = NULL;
if (SDL_FindInHashTable(properties->props, name, (const void **)&property)) {
switch (property->type) {
case SDL_PROPERTY_TYPE_STRING:
value = (float)SDL_atof(property->value.string_value);
break;
case SDL_PROPERTY_TYPE_NUMBER:
value = (float)property->value.number_value;
break;
case SDL_PROPERTY_TYPE_FLOAT:
value = property->value.float_value;
break;
case SDL_PROPERTY_TYPE_BOOLEAN:
value = (float)property->value.boolean_value;
break;
default:
break;
}
}
}
SDL_UnlockMutex(properties->lock);
return value;
}
bool SDL_GetBooleanProperty(SDL_PropertiesID props, const char *name, bool default_value)
{
SDL_Properties *properties = NULL;
bool value = default_value ? true : false;
if (!props) {
return value;
}
if (!name || !*name) {
return value;
}
SDL_FindInHashTable(SDL_properties, (const void *)(uintptr_t)props, (const void **)&properties);
if (!properties) {
return value;
}
SDL_LockMutex(properties->lock);
{
SDL_Property *property = NULL;
if (SDL_FindInHashTable(properties->props, name, (const void **)&property)) {
switch (property->type) {
case SDL_PROPERTY_TYPE_STRING:
value = SDL_GetStringBoolean(property->value.string_value, default_value);
break;
case SDL_PROPERTY_TYPE_NUMBER:
value = (property->value.number_value != 0);
break;
case SDL_PROPERTY_TYPE_FLOAT:
value = (property->value.float_value != 0.0f);
break;
case SDL_PROPERTY_TYPE_BOOLEAN:
value = property->value.boolean_value;
break;
default:
break;
}
}
}
SDL_UnlockMutex(properties->lock);
return value;
}
bool SDL_ClearProperty(SDL_PropertiesID props, const char *name)
{
return SDL_PrivateSetProperty(props, name, NULL);
}
typedef struct EnumerateOnePropertyData
{
SDL_EnumeratePropertiesCallback callback;
void *userdata;
SDL_PropertiesID props;
} EnumerateOnePropertyData;
static bool SDLCALL EnumerateOneProperty(void *userdata, const SDL_HashTable *table, const void *key, const void *value)
{
(void) table;
(void) value;
const EnumerateOnePropertyData *data = (const EnumerateOnePropertyData *) userdata;
data->callback(data->userdata, data->props, (const char *)key);
return true; // keep iterating.
}
bool SDL_EnumerateProperties(SDL_PropertiesID props, SDL_EnumeratePropertiesCallback callback, void *userdata)
{
SDL_Properties *properties = NULL;
if (!props) {
return SDL_InvalidParamError("props");
}
if (!callback) {
return SDL_InvalidParamError("callback");
}
SDL_FindInHashTable(SDL_properties, (const void *)(uintptr_t)props, (const void **)&properties);
if (!properties) {
return SDL_InvalidParamError("props");
}
SDL_LockMutex(properties->lock);
{
EnumerateOnePropertyData data = { callback, userdata, props };
SDL_IterateHashTable(properties->props, EnumerateOneProperty, &data);
}
SDL_UnlockMutex(properties->lock);
return true;
}
static void SDLCALL SDL_DumpPropertiesCallback(void *userdata, SDL_PropertiesID props, const char *name)
{
switch (SDL_GetPropertyType(props, name)) {
case SDL_PROPERTY_TYPE_POINTER:
SDL_Log("%s: %p", name, SDL_GetPointerProperty(props, name, NULL));
break;
case SDL_PROPERTY_TYPE_STRING:
SDL_Log("%s: \"%s\"", name, SDL_GetStringProperty(props, name, ""));
break;
case SDL_PROPERTY_TYPE_NUMBER:
{
Sint64 value = SDL_GetNumberProperty(props, name, 0);
SDL_Log("%s: %" SDL_PRIs64 " (%" SDL_PRIx64 ")", name, value, value);
}
break;
case SDL_PROPERTY_TYPE_FLOAT:
SDL_Log("%s: %g", name, SDL_GetFloatProperty(props, name, 0.0f));
break;
case SDL_PROPERTY_TYPE_BOOLEAN:
SDL_Log("%s: %s", name, SDL_GetBooleanProperty(props, name, false) ? "true" : "false");
break;
default:
SDL_Log("%s UNKNOWN TYPE", name);
break;
}
}
bool SDL_DumpProperties(SDL_PropertiesID props)
{
return SDL_EnumerateProperties(props, SDL_DumpPropertiesCallback, NULL);
}
void SDL_DestroyProperties(SDL_PropertiesID props)
{
if (props) {
// this can't just use RemoveFromHashTable with SDL_FreeProperties as the destructor, because
// other destructors under this might cause use to attempt a recursive lock on SDL_properties,
// which isn't allowed with rwlocks. So manually look it up and remove/free it.
SDL_Properties *properties = NULL;
if (SDL_FindInHashTable(SDL_properties, (const void *)(uintptr_t)props, (const void **)&properties)) {
SDL_FreeProperties(properties);
SDL_RemoveFromHashTable(SDL_properties, (const void *)(uintptr_t)props);
}
}
}

26
thirdparty/sdl/SDL_properties_c.h vendored Normal file
View file

@ -0,0 +1,26 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
extern bool SDL_InitProperties(void);
extern bool SDL_SetFreeableProperty(SDL_PropertiesID props, const char *name, void *value);
extern bool SDL_SetSurfaceProperty(SDL_PropertiesID props, const char *name, SDL_Surface *surface);
extern bool SDL_DumpProperties(SDL_PropertiesID props);
extern void SDL_QuitProperties(void);

554
thirdparty/sdl/SDL_utils.c vendored Normal file
View file

@ -0,0 +1,554 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#if defined(HAVE_GETHOSTNAME) && !defined(SDL_PLATFORM_WINDOWS)
#include <unistd.h>
#endif
#include "joystick/SDL_joystick_c.h" // For SDL_GetGamepadTypeFromVIDPID()
// Common utility functions that aren't in the public API
int SDL_powerof2(int x)
{
int value;
if (x <= 0) {
// Return some sane value - we shouldn't hit this in our use cases
return 1;
}
// This trick works for 32-bit values
{
SDL_COMPILE_TIME_ASSERT(SDL_powerof2, sizeof(x) == sizeof(Uint32));
}
value = x;
value -= 1;
value |= value >> 1;
value |= value >> 2;
value |= value >> 4;
value |= value >> 8;
value |= value >> 16;
value += 1;
return value;
}
Uint32 SDL_CalculateGCD(Uint32 a, Uint32 b)
{
if (b == 0) {
return a;
}
return SDL_CalculateGCD(b, (a % b));
}
// Algorithm adapted with thanks from John Cook's blog post:
// http://www.johndcook.com/blog/2010/10/20/best-rational-approximation
void SDL_CalculateFraction(float x, int *numerator, int *denominator)
{
const int N = 1000;
int a = 0, b = 1;
int c = 1, d = 0;
while (b <= N && d <= N) {
float mediant = (float)(a + c) / (b + d);
if (x == mediant) {
if (b + d <= N) {
*numerator = a + c;
*denominator = b + d;
} else if (d > b) {
*numerator = c;
*denominator = d;
} else {
*numerator = a;
*denominator = b;
}
return;
} else if (x > mediant) {
a = a + c;
b = b + d;
} else {
c = a + c;
d = b + d;
}
}
if (b > N) {
*numerator = c;
*denominator = d;
} else {
*numerator = a;
*denominator = b;
}
}
bool SDL_startswith(const char *string, const char *prefix)
{
if (SDL_strncmp(string, prefix, SDL_strlen(prefix)) == 0) {
return true;
}
return false;
}
bool SDL_endswith(const char *string, const char *suffix)
{
size_t string_length = string ? SDL_strlen(string) : 0;
size_t suffix_length = suffix ? SDL_strlen(suffix) : 0;
if (suffix_length > 0 && suffix_length <= string_length) {
if (SDL_memcmp(string + string_length - suffix_length, suffix, suffix_length) == 0) {
return true;
}
}
return false;
}
SDL_COMPILE_TIME_ASSERT(sizeof_object_id, sizeof(int) == sizeof(Uint32));
Uint32 SDL_GetNextObjectID(void)
{
static SDL_AtomicInt last_id;
Uint32 id = (Uint32)SDL_AtomicIncRef(&last_id) + 1;
if (id == 0) {
id = (Uint32)SDL_AtomicIncRef(&last_id) + 1;
}
return id;
}
static SDL_InitState SDL_objects_init;
static SDL_HashTable *SDL_objects;
static Uint32 SDLCALL SDL_HashObject(void *unused, const void *key)
{
return (Uint32)(uintptr_t)key;
}
static bool SDL_KeyMatchObject(void *unused, const void *a, const void *b)
{
return (a == b);
}
void SDL_SetObjectValid(void *object, SDL_ObjectType type, bool valid)
{
SDL_assert(object != NULL);
if (SDL_ShouldInit(&SDL_objects_init)) {
SDL_objects = SDL_CreateHashTable(0, true, SDL_HashObject, SDL_KeyMatchObject, NULL, NULL);
const bool initialized = (SDL_objects != NULL);
SDL_SetInitialized(&SDL_objects_init, initialized);
if (!initialized) {
return;
}
}
if (valid) {
SDL_InsertIntoHashTable(SDL_objects, object, (void *)(uintptr_t)type, true);
} else {
SDL_RemoveFromHashTable(SDL_objects, object);
}
}
bool SDL_ObjectValid(void *object, SDL_ObjectType type)
{
if (!object) {
return false;
}
const void *object_type;
if (!SDL_FindInHashTable(SDL_objects, object, &object_type)) {
return false;
}
return (((SDL_ObjectType)(uintptr_t)object_type) == type);
}
typedef struct GetOneObjectData
{
const SDL_ObjectType type;
void **objects;
const int count;
int num_objects;
} GetOneObjectData;
static bool SDLCALL GetOneObject(void *userdata, const SDL_HashTable *table, const void *object, const void *object_type)
{
GetOneObjectData *data = (GetOneObjectData *) userdata;
if ((SDL_ObjectType)(uintptr_t)object_type == data->type) {
if (data->num_objects < data->count) {
data->objects[data->num_objects] = (void *)object;
}
++data->num_objects;
}
return true; // keep iterating.
}
int SDL_GetObjects(SDL_ObjectType type, void **objects, int count)
{
GetOneObjectData data = { type, objects, count, 0 };
SDL_IterateHashTable(SDL_objects, GetOneObject, &data);
return data.num_objects;
}
static bool SDLCALL LogOneLeakedObject(void *userdata, const SDL_HashTable *table, const void *object, const void *object_type)
{
const char *type = "unknown object";
switch ((SDL_ObjectType)(uintptr_t)object_type) {
#define SDLOBJTYPECASE(typ, name) case SDL_OBJECT_TYPE_##typ: type = name; break
SDLOBJTYPECASE(WINDOW, "SDL_Window");
SDLOBJTYPECASE(RENDERER, "SDL_Renderer");
SDLOBJTYPECASE(TEXTURE, "SDL_Texture");
SDLOBJTYPECASE(JOYSTICK, "SDL_Joystick");
SDLOBJTYPECASE(GAMEPAD, "SDL_Gamepad");
SDLOBJTYPECASE(HAPTIC, "SDL_Haptic");
SDLOBJTYPECASE(SENSOR, "SDL_Sensor");
SDLOBJTYPECASE(HIDAPI_DEVICE, "hidapi device");
SDLOBJTYPECASE(HIDAPI_JOYSTICK, "hidapi joystick");
SDLOBJTYPECASE(THREAD, "thread");
SDLOBJTYPECASE(TRAY, "SDL_Tray");
#undef SDLOBJTYPECASE
default: break;
}
SDL_Log("Leaked %s (%p)", type, object);
return true; // keep iterating.
}
void SDL_SetObjectsInvalid(void)
{
if (SDL_ShouldQuit(&SDL_objects_init)) {
// Log any leaked objects
SDL_IterateHashTable(SDL_objects, LogOneLeakedObject, NULL);
SDL_assert(SDL_HashTableEmpty(SDL_objects));
SDL_DestroyHashTable(SDL_objects);
SDL_objects = NULL;
SDL_SetInitialized(&SDL_objects_init, false);
}
}
static int SDL_URIDecode(const char *src, char *dst, int len)
{
int ri, wi, di;
char decode = '\0';
if (!src || !dst || len < 0) {
return -1;
}
if (len == 0) {
len = (int)SDL_strlen(src);
}
for (ri = 0, wi = 0, di = 0; ri < len && wi < len; ri += 1) {
if (di == 0) {
// start decoding
if (src[ri] == '%') {
decode = '\0';
di += 1;
continue;
}
// normal write
dst[wi] = src[ri];
wi += 1;
} else if (di == 1 || di == 2) {
char off = '\0';
char isa = src[ri] >= 'a' && src[ri] <= 'f';
char isA = src[ri] >= 'A' && src[ri] <= 'F';
char isn = src[ri] >= '0' && src[ri] <= '9';
if (!(isa || isA || isn)) {
// not a hexadecimal
int sri;
for (sri = ri - di; sri <= ri; sri += 1) {
dst[wi] = src[sri];
wi += 1;
}
di = 0;
continue;
}
// itsy bitsy magicsy
if (isn) {
off = 0 - '0';
} else if (isa) {
off = 10 - 'a';
} else if (isA) {
off = 10 - 'A';
}
decode |= (src[ri] + off) << (2 - di) * 4;
if (di == 2) {
dst[wi] = decode;
wi += 1;
di = 0;
} else {
di += 1;
}
}
}
dst[wi] = '\0';
return wi;
}
int SDL_URIToLocal(const char *src, char *dst)
{
if (SDL_memcmp(src, "file:/", 6) == 0) {
src += 6; // local file?
} else if (SDL_strstr(src, ":/") != NULL) {
return -1; // wrong scheme
}
bool local = src[0] != '/' || (src[0] != '\0' && src[1] == '/');
// Check the hostname, if present. RFC 3986 states that the hostname component of a URI is not case-sensitive.
if (!local && src[0] == '/' && src[2] != '/') {
char *hostname_end = SDL_strchr(src + 1, '/');
if (hostname_end) {
const size_t src_len = hostname_end - (src + 1);
size_t hostname_len;
#if defined(HAVE_GETHOSTNAME) && !defined(SDL_PLATFORM_WINDOWS)
char hostname[257];
if (gethostname(hostname, 255) == 0) {
hostname[256] = '\0';
hostname_len = SDL_strlen(hostname);
if (hostname_len == src_len && SDL_strncasecmp(src + 1, hostname, src_len) == 0) {
src = hostname_end + 1;
local = true;
}
}
#endif
if (!local) {
static const char *localhost = "localhost";
hostname_len = SDL_strlen(localhost);
if (hostname_len == src_len && SDL_strncasecmp(src + 1, localhost, src_len) == 0) {
src = hostname_end + 1;
local = true;
}
}
}
}
if (local) {
// Convert URI escape sequences to real characters
if (src[0] == '/') {
src++;
} else {
src--;
}
return SDL_URIDecode(src, dst, 0);
}
return -1;
}
// This is a set of per-thread persistent strings that we can return from the SDL API.
// This is used for short strings that might persist past the lifetime of the object
// they are related to.
static SDL_TLSID SDL_string_storage;
static void SDL_FreePersistentStrings( void *value )
{
SDL_HashTable *strings = (SDL_HashTable *)value;
SDL_DestroyHashTable(strings);
}
const char *SDL_GetPersistentString(const char *string)
{
if (!string) {
return NULL;
}
if (!*string) {
return "";
}
SDL_HashTable *strings = (SDL_HashTable *)SDL_GetTLS(&SDL_string_storage);
if (!strings) {
strings = SDL_CreateHashTable(0, false, SDL_HashString, SDL_KeyMatchString, SDL_DestroyHashValue, NULL);
if (!strings) {
return NULL;
}
SDL_SetTLS(&SDL_string_storage, strings, SDL_FreePersistentStrings);
}
const char *result;
if (!SDL_FindInHashTable(strings, string, (const void **)&result)) {
char *new_string = SDL_strdup(string);
if (!new_string) {
return NULL;
}
// If the hash table insert fails, at least we can return the string we allocated
SDL_InsertIntoHashTable(strings, new_string, new_string, false);
result = new_string;
}
return result;
}
static int PrefixMatch(const char *a, const char *b)
{
int matchlen = 0;
// Fixes the "HORI HORl Taiko No Tatsujin Drum Controller"
if (SDL_strncmp(a, "HORI ", 5) == 0 && SDL_strncmp(b, "HORl ", 5) == 0) {
return 5;
}
while (*a && *b) {
if (SDL_tolower((unsigned char)*a++) == SDL_tolower((unsigned char)*b++)) {
++matchlen;
} else {
break;
}
}
return matchlen;
}
char *SDL_CreateDeviceName(Uint16 vendor, Uint16 product, const char *vendor_name, const char *product_name, const char *default_name)
{
static struct
{
const char *prefix;
const char *replacement;
} replacements[] = {
{ "8BitDo Tech Ltd", "8BitDo" },
{ "ASTRO Gaming", "ASTRO" },
{ "Bensussen Deutsch & Associates,Inc.(BDA)", "BDA" },
{ "Guangzhou Chicken Run Network Technology Co., Ltd.", "GameSir" },
{ "HORI CO.,LTD.", "HORI" },
{ "HORI CO.,LTD", "HORI" },
{ "Mad Catz Inc.", "Mad Catz" },
{ "Nintendo Co., Ltd.", "Nintendo" },
{ "NVIDIA Corporation ", "" },
{ "Performance Designed Products", "PDP" },
{ "QANBA USA, LLC", "Qanba" },
{ "QANBA USA,LLC", "Qanba" },
{ "Unknown ", "" },
};
char *name = NULL;
size_t i, len;
if (!vendor_name) {
vendor_name = "";
}
if (!product_name) {
product_name = "";
}
while (*vendor_name == ' ') {
++vendor_name;
}
while (*product_name == ' ') {
++product_name;
}
if (*vendor_name && *product_name) {
len = (SDL_strlen(vendor_name) + 1 + SDL_strlen(product_name) + 1);
name = (char *)SDL_malloc(len);
if (name) {
(void)SDL_snprintf(name, len, "%s %s", vendor_name, product_name);
}
} else if (*product_name) {
name = SDL_strdup(product_name);
} else if (vendor || product) {
// Couldn't find a controller name, try to give it one based on device type
switch (SDL_GetGamepadTypeFromVIDPID(vendor, product, NULL, true)) {
case SDL_GAMEPAD_TYPE_XBOX360:
name = SDL_strdup("Xbox 360 Controller");
break;
case SDL_GAMEPAD_TYPE_XBOXONE:
name = SDL_strdup("Xbox One Controller");
break;
case SDL_GAMEPAD_TYPE_PS3:
name = SDL_strdup("PS3 Controller");
break;
case SDL_GAMEPAD_TYPE_PS4:
name = SDL_strdup("PS4 Controller");
break;
case SDL_GAMEPAD_TYPE_PS5:
name = SDL_strdup("DualSense Wireless Controller");
break;
case SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_PRO:
name = SDL_strdup("Nintendo Switch Pro Controller");
break;
default:
len = (6 + 1 + 6 + 1);
name = (char *)SDL_malloc(len);
if (name) {
(void)SDL_snprintf(name, len, "0x%.4x/0x%.4x", vendor, product);
}
break;
}
} else if (default_name) {
name = SDL_strdup(default_name);
}
if (!name) {
return NULL;
}
// Trim trailing whitespace
for (len = SDL_strlen(name); (len > 0 && name[len - 1] == ' '); --len) {
// continue
}
name[len] = '\0';
// Compress duplicate spaces
for (i = 0; i < (len - 1);) {
if (name[i] == ' ' && name[i + 1] == ' ') {
SDL_memmove(&name[i], &name[i + 1], (len - i));
--len;
} else {
++i;
}
}
// Perform any manufacturer replacements
for (i = 0; i < SDL_arraysize(replacements); ++i) {
size_t prefixlen = SDL_strlen(replacements[i].prefix);
if (SDL_strncasecmp(name, replacements[i].prefix, prefixlen) == 0) {
size_t replacementlen = SDL_strlen(replacements[i].replacement);
if (replacementlen <= prefixlen) {
SDL_memcpy(name, replacements[i].replacement, replacementlen);
SDL_memmove(name + replacementlen, name + prefixlen, (len - prefixlen) + 1);
len -= (prefixlen - replacementlen);
} else {
// FIXME: Need to handle the expand case by reallocating the string
}
break;
}
}
/* Remove duplicate manufacturer or product in the name
* e.g. Razer Razer Raiju Tournament Edition Wired
*/
for (i = 1; i < (len - 1); ++i) {
int matchlen = PrefixMatch(name, &name[i]);
while (matchlen > 0) {
if (name[matchlen] == ' ' || name[matchlen] == '-') {
SDL_memmove(name, name + matchlen + 1, len - matchlen);
break;
}
--matchlen;
}
if (matchlen > 0) {
// We matched the manufacturer's name and removed it
break;
}
}
return name;
}

78
thirdparty/sdl/SDL_utils_c.h vendored Normal file
View file

@ -0,0 +1,78 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
// This is included in SDL_internal.h
//#include "SDL_internal.h"
#ifndef SDL_utils_h_
#define SDL_utils_h_
// Common utility functions that aren't in the public API
// Return the smallest power of 2 greater than or equal to 'x'
extern int SDL_powerof2(int x);
extern Uint32 SDL_CalculateGCD(Uint32 a, Uint32 b);
extern void SDL_CalculateFraction(float x, int *numerator, int *denominator);
extern bool SDL_startswith(const char *string, const char *prefix);
extern bool SDL_endswith(const char *string, const char *suffix);
/** Convert URI to a local filename, stripping the "file://"
* preamble and hostname if present, and writes the result
* to the dst buffer. Since URI-encoded characters take
* three times the space of normal characters, src and dst
* can safely point to the same buffer for in situ conversion.
*
* Returns the number of decoded bytes that wound up in
* the destination buffer, excluding the terminating NULL byte.
*
* On error, -1 is returned.
*/
extern int SDL_URIToLocal(const char *src, char *dst);
typedef enum
{
SDL_OBJECT_TYPE_UNKNOWN,
SDL_OBJECT_TYPE_WINDOW,
SDL_OBJECT_TYPE_RENDERER,
SDL_OBJECT_TYPE_TEXTURE,
SDL_OBJECT_TYPE_JOYSTICK,
SDL_OBJECT_TYPE_GAMEPAD,
SDL_OBJECT_TYPE_HAPTIC,
SDL_OBJECT_TYPE_SENSOR,
SDL_OBJECT_TYPE_HIDAPI_DEVICE,
SDL_OBJECT_TYPE_HIDAPI_JOYSTICK,
SDL_OBJECT_TYPE_THREAD,
SDL_OBJECT_TYPE_TRAY,
} SDL_ObjectType;
extern Uint32 SDL_GetNextObjectID(void);
extern void SDL_SetObjectValid(void *object, SDL_ObjectType type, bool valid);
extern bool SDL_ObjectValid(void *object, SDL_ObjectType type);
extern int SDL_GetObjects(SDL_ObjectType type, void **objects, int count);
extern void SDL_SetObjectsInvalid(void);
extern const char *SDL_GetPersistentString(const char *string);
extern char *SDL_CreateDeviceName(Uint16 vendor, Uint16 product, const char *vendor_name, const char *product_name, const char *default_name);
#endif // SDL_utils_h_

382
thirdparty/sdl/atomic/SDL_atomic.c vendored Normal file
View file

@ -0,0 +1,382 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#if defined(_MSC_VER) && (_MSC_VER >= 1900)
#include <intrin.h>
#define HAVE_MSC_ATOMICS 1
#endif
#ifdef SDL_PLATFORM_MACOS // !!! FIXME: should we favor gcc atomics?
#include <libkern/OSAtomic.h>
#endif
#if !defined(HAVE_GCC_ATOMICS) && defined(SDL_PLATFORM_SOLARIS)
#include <atomic.h>
#endif
// The __atomic_load_n() intrinsic showed up in different times for different compilers.
#ifdef __clang__
#if __has_builtin(__atomic_load_n) || defined(HAVE_GCC_ATOMICS)
/* !!! FIXME: this advertises as available in the NDK but uses an external symbol we don't have.
It might be in a later NDK or we might need an extra library? --ryan. */
#ifndef SDL_PLATFORM_ANDROID
#define HAVE_ATOMIC_LOAD_N 1
#endif
#endif
#elif defined(__GNUC__)
#if (__GNUC__ >= 5)
#define HAVE_ATOMIC_LOAD_N 1
#endif
#endif
/* *INDENT-OFF* */ // clang-format off
#if defined(__WATCOMC__) && defined(__386__)
SDL_COMPILE_TIME_ASSERT(intsize, 4==sizeof(int));
#define HAVE_WATCOM_ATOMICS
extern __inline int _SDL_xchg_watcom(volatile int *a, int v);
#pragma aux _SDL_xchg_watcom = \
"lock xchg [ecx], eax" \
parm [ecx] [eax] \
value [eax] \
modify exact [eax];
extern __inline unsigned char _SDL_cmpxchg_watcom(volatile int *a, int newval, int oldval);
#pragma aux _SDL_cmpxchg_watcom = \
"lock cmpxchg [edx], ecx" \
"setz al" \
parm [edx] [ecx] [eax] \
value [al] \
modify exact [eax];
extern __inline int _SDL_xadd_watcom(volatile int *a, int v);
#pragma aux _SDL_xadd_watcom = \
"lock xadd [ecx], eax" \
parm [ecx] [eax] \
value [eax] \
modify exact [eax];
#endif // __WATCOMC__ && __386__
/* *INDENT-ON* */ // clang-format on
/*
If any of the operations are not provided then we must emulate some
of them. That means we need a nice implementation of spin locks
that avoids the "one big lock" problem. We use a vector of spin
locks and pick which one to use based on the address of the operand
of the function.
To generate the index of the lock we first shift by 3 bits to get
rid on the zero bits that result from 32 and 64 bit alignment of
data. We then mask off all but 5 bits and use those 5 bits as an
index into the table.
Picking the lock this way insures that accesses to the same data at
the same time will go to the same lock. OTOH, accesses to different
data have only a 1/32 chance of hitting the same lock. That should
pretty much eliminate the chances of several atomic operations on
different data from waiting on the same "big lock". If it isn't
then the table of locks can be expanded to a new size so long as
the new size is a power of two.
Contributed by Bob Pendleton, bob@pendleton.com
*/
#if !defined(HAVE_MSC_ATOMICS) && !defined(HAVE_GCC_ATOMICS) && !defined(SDL_PLATFORM_MACOS) && !defined(SDL_PLATFORM_SOLARIS) && !defined(HAVE_WATCOM_ATOMICS)
#define EMULATE_CAS 1
#endif
#ifdef EMULATE_CAS
static SDL_SpinLock locks[32];
static SDL_INLINE void enterLock(void *a)
{
uintptr_t index = ((((uintptr_t)a) >> 3) & 0x1f);
SDL_LockSpinlock(&locks[index]);
}
static SDL_INLINE void leaveLock(void *a)
{
uintptr_t index = ((((uintptr_t)a) >> 3) & 0x1f);
SDL_UnlockSpinlock(&locks[index]);
}
#endif
bool SDL_CompareAndSwapAtomicInt(SDL_AtomicInt *a, int oldval, int newval)
{
#ifdef HAVE_MSC_ATOMICS
SDL_COMPILE_TIME_ASSERT(atomic_cas, sizeof(long) == sizeof(a->value));
return _InterlockedCompareExchange((long *)&a->value, (long)newval, (long)oldval) == (long)oldval;
#elif defined(HAVE_WATCOM_ATOMICS)
return _SDL_cmpxchg_watcom((volatile int *)&a->value, newval, oldval);
#elif defined(HAVE_GCC_ATOMICS)
return __sync_bool_compare_and_swap(&a->value, oldval, newval);
#elif defined(SDL_PLATFORM_MACOS) // this is deprecated in 10.12 sdk; favor gcc atomics.
return OSAtomicCompareAndSwap32Barrier(oldval, newval, &a->value);
#elif defined(SDL_PLATFORM_SOLARIS)
SDL_COMPILE_TIME_ASSERT(atomic_cas, sizeof(uint_t) == sizeof(a->value));
return ((int)atomic_cas_uint((volatile uint_t *)&a->value, (uint_t)oldval, (uint_t)newval) == oldval);
#elif defined(EMULATE_CAS)
bool result = false;
enterLock(a);
if (a->value == oldval) {
a->value = newval;
result = true;
}
leaveLock(a);
return result;
#else
#error Please define your platform.
#endif
}
bool SDL_CompareAndSwapAtomicU32(SDL_AtomicU32 *a, Uint32 oldval, Uint32 newval)
{
#ifdef HAVE_MSC_ATOMICS
SDL_COMPILE_TIME_ASSERT(atomic_cas, sizeof(long) == sizeof(a->value));
return _InterlockedCompareExchange((long *)&a->value, (long)newval, (long)oldval) == (long)oldval;
#elif defined(HAVE_WATCOM_ATOMICS)
SDL_COMPILE_TIME_ASSERT(atomic_cas, sizeof(int) == sizeof(a->value));
return _SDL_cmpxchg_watcom((volatile int *)&a->value, (int)newval, (int)oldval);
#elif defined(HAVE_GCC_ATOMICS)
return __sync_bool_compare_and_swap(&a->value, oldval, newval);
#elif defined(SDL_PLATFORM_MACOS) // this is deprecated in 10.12 sdk; favor gcc atomics.
return OSAtomicCompareAndSwap32Barrier((int32_t)oldval, (int32_t)newval, (int32_t*)&a->value);
#elif defined(SDL_PLATFORM_SOLARIS)
SDL_COMPILE_TIME_ASSERT(atomic_cas, sizeof(uint_t) == sizeof(a->value));
return ((Uint32)atomic_cas_uint((volatile uint_t *)&a->value, (uint_t)oldval, (uint_t)newval) == oldval);
#elif defined(EMULATE_CAS)
bool result = false;
enterLock(a);
if (a->value == oldval) {
a->value = newval;
result = true;
}
leaveLock(a);
return result;
#else
#error Please define your platform.
#endif
}
bool SDL_CompareAndSwapAtomicPointer(void **a, void *oldval, void *newval)
{
#ifdef HAVE_MSC_ATOMICS
return _InterlockedCompareExchangePointer(a, newval, oldval) == oldval;
#elif defined(HAVE_WATCOM_ATOMICS)
return _SDL_cmpxchg_watcom((int *)a, (long)newval, (long)oldval);
#elif defined(HAVE_GCC_ATOMICS)
return __sync_bool_compare_and_swap(a, oldval, newval);
#elif defined(SDL_PLATFORM_MACOS) && defined(__LP64__) // this is deprecated in 10.12 sdk; favor gcc atomics.
return OSAtomicCompareAndSwap64Barrier((int64_t)oldval, (int64_t)newval, (int64_t *)a);
#elif defined(SDL_PLATFORM_MACOS) && !defined(__LP64__) // this is deprecated in 10.12 sdk; favor gcc atomics.
return OSAtomicCompareAndSwap32Barrier((int32_t)oldval, (int32_t)newval, (int32_t *)a);
#elif defined(SDL_PLATFORM_SOLARIS)
return (atomic_cas_ptr(a, oldval, newval) == oldval);
#elif defined(EMULATE_CAS)
bool result = false;
enterLock(a);
if (*a == oldval) {
*a = newval;
result = true;
}
leaveLock(a);
return result;
#else
#error Please define your platform.
#endif
}
int SDL_SetAtomicInt(SDL_AtomicInt *a, int v)
{
#ifdef HAVE_MSC_ATOMICS
SDL_COMPILE_TIME_ASSERT(atomic_set, sizeof(long) == sizeof(a->value));
return _InterlockedExchange((long *)&a->value, v);
#elif defined(HAVE_WATCOM_ATOMICS)
return _SDL_xchg_watcom(&a->value, v);
#elif defined(HAVE_GCC_ATOMICS)
return __sync_lock_test_and_set(&a->value, v);
#elif defined(SDL_PLATFORM_SOLARIS)
SDL_COMPILE_TIME_ASSERT(atomic_set, sizeof(uint_t) == sizeof(a->value));
return (int)atomic_swap_uint((volatile uint_t *)&a->value, v);
#else
int value;
do {
value = a->value;
} while (!SDL_CompareAndSwapAtomicInt(a, value, v));
return value;
#endif
}
Uint32 SDL_SetAtomicU32(SDL_AtomicU32 *a, Uint32 v)
{
#ifdef HAVE_MSC_ATOMICS
SDL_COMPILE_TIME_ASSERT(atomic_set, sizeof(long) == sizeof(a->value));
return _InterlockedExchange((long *)&a->value, v);
#elif defined(HAVE_WATCOM_ATOMICS)
return _SDL_xchg_watcom(&a->value, v);
#elif defined(HAVE_GCC_ATOMICS)
return __sync_lock_test_and_set(&a->value, v);
#elif defined(SDL_PLATFORM_SOLARIS)
SDL_COMPILE_TIME_ASSERT(atomic_set, sizeof(uint_t) == sizeof(a->value));
return (Uint32)atomic_swap_uint((volatile uint_t *)&a->value, v);
#else
Uint32 value;
do {
value = a->value;
} while (!SDL_CompareAndSwapAtomicU32(a, value, v));
return value;
#endif
}
void *SDL_SetAtomicPointer(void **a, void *v)
{
#ifdef HAVE_MSC_ATOMICS
return _InterlockedExchangePointer(a, v);
#elif defined(HAVE_WATCOM_ATOMICS)
return (void *)_SDL_xchg_watcom((int *)a, (long)v);
#elif defined(HAVE_GCC_ATOMICS)
return __sync_lock_test_and_set(a, v);
#elif defined(SDL_PLATFORM_SOLARIS)
return atomic_swap_ptr(a, v);
#else
void *value;
do {
value = *a;
} while (!SDL_CompareAndSwapAtomicPointer(a, value, v));
return value;
#endif
}
int SDL_AddAtomicInt(SDL_AtomicInt *a, int v)
{
#ifdef HAVE_MSC_ATOMICS
SDL_COMPILE_TIME_ASSERT(atomic_add, sizeof(long) == sizeof(a->value));
return _InterlockedExchangeAdd((long *)&a->value, v);
#elif defined(HAVE_WATCOM_ATOMICS)
SDL_COMPILE_TIME_ASSERT(atomic_add, sizeof(int) == sizeof(a->value));
return _SDL_xadd_watcom((volatile int *)&a->value, v);
#elif defined(HAVE_GCC_ATOMICS)
return __sync_fetch_and_add(&a->value, v);
#elif defined(SDL_PLATFORM_SOLARIS)
int pv = a->value;
membar_consumer();
atomic_add_int((volatile uint_t *)&a->value, v);
return pv;
#else
int value;
do {
value = a->value;
} while (!SDL_CompareAndSwapAtomicInt(a, value, (value + v)));
return value;
#endif
}
int SDL_GetAtomicInt(SDL_AtomicInt *a)
{
#ifdef HAVE_ATOMIC_LOAD_N
return __atomic_load_n(&a->value, __ATOMIC_SEQ_CST);
#elif defined(HAVE_MSC_ATOMICS)
SDL_COMPILE_TIME_ASSERT(atomic_get, sizeof(long) == sizeof(a->value));
return _InterlockedOr((long *)&a->value, 0);
#elif defined(HAVE_WATCOM_ATOMICS)
return _SDL_xadd_watcom(&a->value, 0);
#elif defined(HAVE_GCC_ATOMICS)
return __sync_or_and_fetch(&a->value, 0);
#elif defined(SDL_PLATFORM_MACOS) // this is deprecated in 10.12 sdk; favor gcc atomics.
return sizeof(a->value) == sizeof(uint32_t) ? OSAtomicOr32Barrier(0, (volatile uint32_t *)&a->value) : OSAtomicAdd64Barrier(0, (volatile int64_t *)&a->value);
#elif defined(SDL_PLATFORM_SOLARIS)
return atomic_or_uint_nv((volatile uint_t *)&a->value, 0);
#else
int value;
do {
value = a->value;
} while (!SDL_CompareAndSwapAtomicInt(a, value, value));
return value;
#endif
}
Uint32 SDL_GetAtomicU32(SDL_AtomicU32 *a)
{
#ifdef HAVE_ATOMIC_LOAD_N
return __atomic_load_n(&a->value, __ATOMIC_SEQ_CST);
#elif defined(HAVE_MSC_ATOMICS)
SDL_COMPILE_TIME_ASSERT(atomic_get, sizeof(long) == sizeof(a->value));
return (Uint32)_InterlockedOr((long *)&a->value, 0);
#elif defined(HAVE_WATCOM_ATOMICS)
SDL_COMPILE_TIME_ASSERT(atomic_get, sizeof(int) == sizeof(a->value));
return (Uint32)_SDL_xadd_watcom((volatile int *)&a->value, 0);
#elif defined(HAVE_GCC_ATOMICS)
return __sync_or_and_fetch(&a->value, 0);
#elif defined(SDL_PLATFORM_MACOS) // this is deprecated in 10.12 sdk; favor gcc atomics.
return OSAtomicOr32Barrier(0, (volatile uint32_t *)&a->value);
#elif defined(SDL_PLATFORM_SOLARIS)
SDL_COMPILE_TIME_ASSERT(atomic_get, sizeof(uint_t) == sizeof(a->value));
return (Uint32)atomic_or_uint_nv((volatile uint_t *)&a->value, 0);
#else
Uint32 value;
do {
value = a->value;
} while (!SDL_CompareAndSwapAtomicU32(a, value, value));
return value;
#endif
}
void *SDL_GetAtomicPointer(void **a)
{
#ifdef HAVE_ATOMIC_LOAD_N
return __atomic_load_n(a, __ATOMIC_SEQ_CST);
#elif defined(HAVE_MSC_ATOMICS)
return _InterlockedCompareExchangePointer(a, NULL, NULL);
#elif defined(HAVE_GCC_ATOMICS)
return __sync_val_compare_and_swap(a, (void *)0, (void *)0);
#elif defined(SDL_PLATFORM_SOLARIS)
return atomic_cas_ptr(a, (void *)0, (void *)0);
#else
void *value;
do {
value = *a;
} while (!SDL_CompareAndSwapAtomicPointer(a, value, value));
return value;
#endif
}
#ifdef SDL_MEMORY_BARRIER_USES_FUNCTION
#error This file should be built in arm mode so the mcr instruction is available for memory barriers
#endif
void SDL_MemoryBarrierReleaseFunction(void)
{
SDL_MemoryBarrierRelease();
}
void SDL_MemoryBarrierAcquireFunction(void)
{
SDL_MemoryBarrierAcquire();
}

203
thirdparty/sdl/atomic/SDL_spinlock.c vendored Normal file
View file

@ -0,0 +1,203 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#if defined(SDL_PLATFORM_WINDOWS)
#include "../core/windows/SDL_windows.h"
#endif
#if !defined(HAVE_GCC_ATOMICS) && defined(SDL_PLATFORM_SOLARIS)
#include <atomic.h>
#endif
#if !defined(HAVE_GCC_ATOMICS) && defined(SDL_PLATFORM_RISCOS)
#include <unixlib/local.h>
#endif
#if defined(_MSC_VER) && (defined(_M_IX86) || defined(_M_X64))
#include <xmmintrin.h>
#endif
#ifdef PS2
#include <kernel.h>
#endif
#if !defined(HAVE_GCC_ATOMICS) && defined(SDL_PLATFORM_MACOS)
#include <libkern/OSAtomic.h>
#endif
/* *INDENT-OFF* */ // clang-format off
#if defined(__WATCOMC__) && defined(__386__)
SDL_COMPILE_TIME_ASSERT(locksize, 4==sizeof(SDL_SpinLock));
extern __inline int _SDL_xchg_watcom(volatile int *a, int v);
#pragma aux _SDL_xchg_watcom = \
"lock xchg [ecx], eax" \
parm [ecx] [eax] \
value [eax] \
modify exact [eax];
#endif // __WATCOMC__ && __386__
/* *INDENT-ON* */ // clang-format on
// This function is where all the magic happens...
bool SDL_TryLockSpinlock(SDL_SpinLock *lock)
{
#if defined(HAVE_GCC_ATOMICS) || defined(HAVE_GCC_SYNC_LOCK_TEST_AND_SET)
return __sync_lock_test_and_set(lock, 1) == 0;
#elif defined(_MSC_VER) && (defined(_M_ARM) || defined(_M_ARM64))
return _InterlockedExchange_acq(lock, 1) == 0;
#elif defined(_MSC_VER)
SDL_COMPILE_TIME_ASSERT(locksize, sizeof(*lock) == sizeof(long));
return InterlockedExchange((long *)lock, 1) == 0;
#elif defined(__WATCOMC__) && defined(__386__)
return _SDL_xchg_watcom(lock, 1) == 0;
#elif defined(__GNUC__) && defined(__arm__) && \
(defined(__ARM_ARCH_3__) || defined(__ARM_ARCH_3M__) || \
defined(__ARM_ARCH_4__) || defined(__ARM_ARCH_4T__) || \
defined(__ARM_ARCH_5__) || defined(__ARM_ARCH_5TE__) || \
defined(__ARM_ARCH_5TEJ__))
int result;
#ifdef SDL_PLATFORM_RISCOS
if (__cpucap_have_rex()) {
__asm__ __volatile__(
"ldrex %0, [%2]\nteq %0, #0\nstrexeq %0, %1, [%2]"
: "=&r"(result)
: "r"(1), "r"(lock)
: "cc", "memory");
return result == 0;
}
#endif
__asm__ __volatile__(
"swp %0, %1, [%2]\n"
: "=&r,&r"(result)
: "r,0"(1), "r,r"(lock)
: "memory");
return result == 0;
#elif defined(__GNUC__) && defined(__arm__)
int result;
__asm__ __volatile__(
"ldrex %0, [%2]\nteq %0, #0\nstrexeq %0, %1, [%2]"
: "=&r"(result)
: "r"(1), "r"(lock)
: "cc", "memory");
return result == 0;
#elif defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__))
int result;
__asm__ __volatile__(
"lock ; xchgl %0, (%1)\n"
: "=r"(result)
: "r"(lock), "0"(1)
: "cc", "memory");
return result == 0;
#elif defined(SDL_PLATFORM_MACOS) || defined(SDL_PLATFORM_IOS) || defined(SDL_PLATFORM_TVOS)
// Maybe used for PowerPC, but the Intel asm or gcc atomics are favored.
return OSAtomicCompareAndSwap32Barrier(0, 1, lock);
#elif defined(SDL_PLATFORM_SOLARIS) && defined(_LP64)
// Used for Solaris with non-gcc compilers.
return ((int)atomic_cas_64((volatile uint64_t *)lock, 0, 1) == 0);
#elif defined(SDL_PLATFORM_SOLARIS) && !defined(_LP64)
// Used for Solaris with non-gcc compilers.
return ((int)atomic_cas_32((volatile uint32_t *)lock, 0, 1) == 0);
#elif defined(PS2)
uint32_t oldintr;
bool res = false;
// disable interruption
oldintr = DIntr();
if (*lock == 0) {
*lock = 1;
res = true;
}
// enable interruption
if (oldintr) {
EIntr();
}
return res;
#else
// Terrible terrible damage
static SDL_Mutex *_spinlock_mutex;
if (!_spinlock_mutex) {
// Race condition on first lock...
_spinlock_mutex = SDL_CreateMutex();
}
SDL_LockMutex(_spinlock_mutex);
if (*lock == 0) {
*lock = 1;
SDL_UnlockMutex(_spinlock_mutex);
return true;
} else {
SDL_UnlockMutex(_spinlock_mutex);
return false;
}
#endif
}
void SDL_LockSpinlock(SDL_SpinLock *lock)
{
int iterations = 0;
// FIXME: Should we have an eventual timeout?
while (!SDL_TryLockSpinlock(lock)) {
if (iterations < 32) {
iterations++;
SDL_CPUPauseInstruction();
} else {
// !!! FIXME: this doesn't definitely give up the current timeslice, it does different things on various platforms.
SDL_Delay(0);
}
}
}
void SDL_UnlockSpinlock(SDL_SpinLock *lock)
{
#if defined(HAVE_GCC_ATOMICS) || defined(HAVE_GCC_SYNC_LOCK_TEST_AND_SET)
__sync_lock_release(lock);
#elif defined(_MSC_VER) && (defined(_M_ARM) || defined(_M_ARM64))
_InterlockedExchange_rel(lock, 0);
#elif defined(_MSC_VER)
_ReadWriteBarrier();
*lock = 0;
#elif defined(__WATCOMC__) && defined(__386__)
SDL_CompilerBarrier();
*lock = 0;
#elif defined(SDL_PLATFORM_SOLARIS)
// Used for Solaris when not using gcc.
*lock = 0;
membar_producer();
#else
*lock = 0;
#endif
}

641
thirdparty/sdl/core/linux/SDL_dbus.c vendored Normal file
View file

@ -0,0 +1,641 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#include "SDL_dbus.h"
#include "../../stdlib/SDL_vacopy.h"
#ifdef SDL_USE_LIBDBUS
// we never link directly to libdbus.
static const char *dbus_library = "libdbus-1.so.3";
static SDL_SharedObject *dbus_handle = NULL;
static char *inhibit_handle = NULL;
static unsigned int screensaver_cookie = 0;
static SDL_DBusContext dbus;
static bool LoadDBUSSyms(void)
{
#define SDL_DBUS_SYM2_OPTIONAL(TYPE, x, y) \
dbus.x = (TYPE)SDL_LoadFunction(dbus_handle, #y)
#define SDL_DBUS_SYM2(TYPE, x, y) \
if (!(dbus.x = (TYPE)SDL_LoadFunction(dbus_handle, #y))) \
return false
#define SDL_DBUS_SYM_OPTIONAL(TYPE, x) \
SDL_DBUS_SYM2_OPTIONAL(TYPE, x, dbus_##x)
#define SDL_DBUS_SYM(TYPE, x) \
SDL_DBUS_SYM2(TYPE, x, dbus_##x)
SDL_DBUS_SYM(DBusConnection *(*)(DBusBusType, DBusError *), bus_get_private);
SDL_DBUS_SYM(dbus_bool_t (*)(DBusConnection *, DBusError *), bus_register);
SDL_DBUS_SYM(void (*)(DBusConnection *, const char *, DBusError *), bus_add_match);
SDL_DBUS_SYM(DBusConnection *(*)(const char *, DBusError *), connection_open_private);
SDL_DBUS_SYM(void (*)(DBusConnection *, dbus_bool_t), connection_set_exit_on_disconnect);
SDL_DBUS_SYM(dbus_bool_t (*)(DBusConnection *), connection_get_is_connected);
SDL_DBUS_SYM(dbus_bool_t (*)(DBusConnection *, DBusHandleMessageFunction, void *, DBusFreeFunction), connection_add_filter);
SDL_DBUS_SYM(dbus_bool_t (*)(DBusConnection *, DBusHandleMessageFunction, void *), connection_remove_filter);
SDL_DBUS_SYM(dbus_bool_t (*)(DBusConnection *, const char *, const DBusObjectPathVTable *, void *, DBusError *), connection_try_register_object_path);
SDL_DBUS_SYM(dbus_bool_t (*)(DBusConnection *, DBusMessage *, dbus_uint32_t *), connection_send);
SDL_DBUS_SYM(DBusMessage *(*)(DBusConnection *, DBusMessage *, int, DBusError *), connection_send_with_reply_and_block);
SDL_DBUS_SYM(void (*)(DBusConnection *), connection_close);
SDL_DBUS_SYM(void (*)(DBusConnection *), connection_ref);
SDL_DBUS_SYM(void (*)(DBusConnection *), connection_unref);
SDL_DBUS_SYM(void (*)(DBusConnection *), connection_flush);
SDL_DBUS_SYM(dbus_bool_t (*)(DBusConnection *, int), connection_read_write);
SDL_DBUS_SYM(DBusDispatchStatus (*)(DBusConnection *), connection_dispatch);
SDL_DBUS_SYM(dbus_bool_t (*)(DBusMessage *, const char *, const char *), message_is_signal);
SDL_DBUS_SYM(dbus_bool_t (*)(DBusMessage *, const char *), message_has_path);
SDL_DBUS_SYM(DBusMessage *(*)(const char *, const char *, const char *, const char *), message_new_method_call);
SDL_DBUS_SYM(dbus_bool_t (*)(DBusMessage *, int, ...), message_append_args);
SDL_DBUS_SYM(dbus_bool_t (*)(DBusMessage *, int, va_list), message_append_args_valist);
SDL_DBUS_SYM(void (*)(DBusMessage *, DBusMessageIter *), message_iter_init_append);
SDL_DBUS_SYM(dbus_bool_t (*)(DBusMessageIter *, int, const char *, DBusMessageIter *), message_iter_open_container);
SDL_DBUS_SYM(dbus_bool_t (*)(DBusMessageIter *, int, const void *), message_iter_append_basic);
SDL_DBUS_SYM(dbus_bool_t (*)(DBusMessageIter *, DBusMessageIter *), message_iter_close_container);
SDL_DBUS_SYM(dbus_bool_t (*)(DBusMessage *, DBusError *, int, ...), message_get_args);
SDL_DBUS_SYM(dbus_bool_t (*)(DBusMessage *, DBusError *, int, va_list), message_get_args_valist);
SDL_DBUS_SYM(dbus_bool_t (*)(DBusMessage *, DBusMessageIter *), message_iter_init);
SDL_DBUS_SYM(dbus_bool_t (*)(DBusMessageIter *), message_iter_next);
SDL_DBUS_SYM(void (*)(DBusMessageIter *, void *), message_iter_get_basic);
SDL_DBUS_SYM(int (*)(DBusMessageIter *), message_iter_get_arg_type);
SDL_DBUS_SYM(void (*)(DBusMessageIter *, DBusMessageIter *), message_iter_recurse);
SDL_DBUS_SYM(void (*)(DBusMessage *), message_unref);
SDL_DBUS_SYM(dbus_bool_t (*)(void), threads_init_default);
SDL_DBUS_SYM(void (*)(DBusError *), error_init);
SDL_DBUS_SYM(dbus_bool_t (*)(const DBusError *), error_is_set);
SDL_DBUS_SYM(void (*)(DBusError *), error_free);
SDL_DBUS_SYM(char *(*)(void), get_local_machine_id);
SDL_DBUS_SYM_OPTIONAL(char *(*)(DBusError *), try_get_local_machine_id);
SDL_DBUS_SYM(void (*)(void *), free);
SDL_DBUS_SYM(void (*)(char **), free_string_array);
SDL_DBUS_SYM(void (*)(void), shutdown);
#undef SDL_DBUS_SYM
#undef SDL_DBUS_SYM2
return true;
}
static void UnloadDBUSLibrary(void)
{
if (dbus_handle) {
SDL_UnloadObject(dbus_handle);
dbus_handle = NULL;
}
}
static bool LoadDBUSLibrary(void)
{
bool result = true;
if (!dbus_handle) {
dbus_handle = SDL_LoadObject(dbus_library);
if (!dbus_handle) {
result = false;
// Don't call SDL_SetError(): SDL_LoadObject already did.
} else {
result = LoadDBUSSyms();
if (!result) {
UnloadDBUSLibrary();
}
}
}
return result;
}
static SDL_InitState dbus_init;
void SDL_DBus_Init(void)
{
static bool is_dbus_available = true;
if (!is_dbus_available) {
return; // don't keep trying if this fails.
}
if (!SDL_ShouldInit(&dbus_init)) {
return;
}
if (!LoadDBUSLibrary()) {
goto error;
}
if (!dbus.threads_init_default()) {
goto error;
}
DBusError err;
dbus.error_init(&err);
// session bus is required
dbus.session_conn = dbus.bus_get_private(DBUS_BUS_SESSION, &err);
if (dbus.error_is_set(&err)) {
dbus.error_free(&err);
goto error;
}
dbus.connection_set_exit_on_disconnect(dbus.session_conn, 0);
// system bus is optional
dbus.system_conn = dbus.bus_get_private(DBUS_BUS_SYSTEM, &err);
if (!dbus.error_is_set(&err)) {
dbus.connection_set_exit_on_disconnect(dbus.system_conn, 0);
}
dbus.error_free(&err);
SDL_SetInitialized(&dbus_init, true);
return;
error:
is_dbus_available = false;
SDL_SetInitialized(&dbus_init, true);
SDL_DBus_Quit();
}
void SDL_DBus_Quit(void)
{
if (!SDL_ShouldQuit(&dbus_init)) {
return;
}
if (dbus.system_conn) {
dbus.connection_close(dbus.system_conn);
dbus.connection_unref(dbus.system_conn);
}
if (dbus.session_conn) {
dbus.connection_close(dbus.session_conn);
dbus.connection_unref(dbus.session_conn);
}
if (SDL_GetHintBoolean(SDL_HINT_SHUTDOWN_DBUS_ON_QUIT, false)) {
if (dbus.shutdown) {
dbus.shutdown();
}
UnloadDBUSLibrary();
} else {
/* Leaving libdbus loaded when skipping dbus_shutdown() avoids
* spurious leak warnings from LeakSanitizer on internal D-Bus
* allocations that would be freed by dbus_shutdown(). */
dbus_handle = NULL;
}
SDL_zero(dbus);
if (inhibit_handle) {
SDL_free(inhibit_handle);
inhibit_handle = NULL;
}
SDL_SetInitialized(&dbus_init, false);
}
SDL_DBusContext *SDL_DBus_GetContext(void)
{
if (!dbus_handle || !dbus.session_conn) {
SDL_DBus_Init();
}
return (dbus_handle && dbus.session_conn) ? &dbus : NULL;
}
static bool SDL_DBus_CallMethodInternal(DBusConnection *conn, const char *node, const char *path, const char *interface, const char *method, va_list ap)
{
bool result = false;
if (conn) {
DBusMessage *msg = dbus.message_new_method_call(node, path, interface, method);
if (msg) {
int firstarg;
va_list ap_reply;
va_copy(ap_reply, ap); // copy the arg list so we don't compete with D-Bus for it
firstarg = va_arg(ap, int);
if ((firstarg == DBUS_TYPE_INVALID) || dbus.message_append_args_valist(msg, firstarg, ap)) {
DBusMessage *reply = dbus.connection_send_with_reply_and_block(conn, msg, 300, NULL);
if (reply) {
// skip any input args, get to output args.
while ((firstarg = va_arg(ap_reply, int)) != DBUS_TYPE_INVALID) {
// we assume D-Bus already validated all this.
{
void *dumpptr = va_arg(ap_reply, void *);
(void)dumpptr;
}
if (firstarg == DBUS_TYPE_ARRAY) {
{
const int dumpint = va_arg(ap_reply, int);
(void)dumpint;
}
}
}
firstarg = va_arg(ap_reply, int);
if ((firstarg == DBUS_TYPE_INVALID) || dbus.message_get_args_valist(reply, NULL, firstarg, ap_reply)) {
result = true;
}
dbus.message_unref(reply);
}
}
va_end(ap_reply);
dbus.message_unref(msg);
}
}
return result;
}
bool SDL_DBus_CallMethodOnConnection(DBusConnection *conn, const char *node, const char *path, const char *interface, const char *method, ...)
{
bool result;
va_list ap;
va_start(ap, method);
result = SDL_DBus_CallMethodInternal(conn, node, path, interface, method, ap);
va_end(ap);
return result;
}
bool SDL_DBus_CallMethod(const char *node, const char *path, const char *interface, const char *method, ...)
{
bool result;
va_list ap;
va_start(ap, method);
result = SDL_DBus_CallMethodInternal(dbus.session_conn, node, path, interface, method, ap);
va_end(ap);
return result;
}
static bool SDL_DBus_CallVoidMethodInternal(DBusConnection *conn, const char *node, const char *path, const char *interface, const char *method, va_list ap)
{
bool result = false;
if (conn) {
DBusMessage *msg = dbus.message_new_method_call(node, path, interface, method);
if (msg) {
int firstarg = va_arg(ap, int);
if ((firstarg == DBUS_TYPE_INVALID) || dbus.message_append_args_valist(msg, firstarg, ap)) {
if (dbus.connection_send(conn, msg, NULL)) {
dbus.connection_flush(conn);
result = true;
}
}
dbus.message_unref(msg);
}
}
return result;
}
static bool SDL_DBus_CallWithBasicReply(DBusConnection *conn, DBusMessage *msg, const int expectedtype, void *result)
{
bool retval = false;
DBusMessage *reply = dbus.connection_send_with_reply_and_block(conn, msg, 300, NULL);
if (reply) {
DBusMessageIter iter, actual_iter;
dbus.message_iter_init(reply, &iter);
if (dbus.message_iter_get_arg_type(&iter) == DBUS_TYPE_VARIANT) {
dbus.message_iter_recurse(&iter, &actual_iter);
} else {
actual_iter = iter;
}
if (dbus.message_iter_get_arg_type(&actual_iter) == expectedtype) {
dbus.message_iter_get_basic(&actual_iter, result);
retval = true;
}
dbus.message_unref(reply);
}
return retval;
}
bool SDL_DBus_CallVoidMethodOnConnection(DBusConnection *conn, const char *node, const char *path, const char *interface, const char *method, ...)
{
bool result;
va_list ap;
va_start(ap, method);
result = SDL_DBus_CallVoidMethodInternal(conn, node, path, interface, method, ap);
va_end(ap);
return result;
}
bool SDL_DBus_CallVoidMethod(const char *node, const char *path, const char *interface, const char *method, ...)
{
bool result;
va_list ap;
va_start(ap, method);
result = SDL_DBus_CallVoidMethodInternal(dbus.session_conn, node, path, interface, method, ap);
va_end(ap);
return result;
}
bool SDL_DBus_QueryPropertyOnConnection(DBusConnection *conn, const char *node, const char *path, const char *interface, const char *property, int expectedtype, void *result)
{
bool retval = false;
if (conn) {
DBusMessage *msg = dbus.message_new_method_call(node, path, "org.freedesktop.DBus.Properties", "Get");
if (msg) {
if (dbus.message_append_args(msg, DBUS_TYPE_STRING, &interface, DBUS_TYPE_STRING, &property, DBUS_TYPE_INVALID)) {
retval = SDL_DBus_CallWithBasicReply(conn, msg, expectedtype, result);
}
dbus.message_unref(msg);
}
}
return retval;
}
bool SDL_DBus_QueryProperty(const char *node, const char *path, const char *interface, const char *property, int expectedtype, void *result)
{
return SDL_DBus_QueryPropertyOnConnection(dbus.session_conn, node, path, interface, property, expectedtype, result);
}
void SDL_DBus_ScreensaverTickle(void)
{
if (screensaver_cookie == 0 && !inhibit_handle) { // no need to tickle if we're inhibiting.
// org.gnome.ScreenSaver is the legacy interface, but it'll either do nothing or just be a second harmless tickle on newer systems, so we leave it for now.
SDL_DBus_CallVoidMethod("org.gnome.ScreenSaver", "/org/gnome/ScreenSaver", "org.gnome.ScreenSaver", "SimulateUserActivity", DBUS_TYPE_INVALID);
SDL_DBus_CallVoidMethod("org.freedesktop.ScreenSaver", "/org/freedesktop/ScreenSaver", "org.freedesktop.ScreenSaver", "SimulateUserActivity", DBUS_TYPE_INVALID);
}
}
static bool SDL_DBus_AppendDictWithKeysAndValues(DBusMessageIter *iterInit, const char **keys, const char **values, int count)
{
DBusMessageIter iterDict;
if (!dbus.message_iter_open_container(iterInit, DBUS_TYPE_ARRAY, "{sv}", &iterDict)) {
goto failed;
}
for (int i = 0; i < count; i++) {
DBusMessageIter iterEntry, iterValue;
const char *key = keys[i];
const char *value = values[i];
if (!dbus.message_iter_open_container(&iterDict, DBUS_TYPE_DICT_ENTRY, NULL, &iterEntry)) {
goto failed;
}
if (!dbus.message_iter_append_basic(&iterEntry, DBUS_TYPE_STRING, &key)) {
goto failed;
}
if (!dbus.message_iter_open_container(&iterEntry, DBUS_TYPE_VARIANT, DBUS_TYPE_STRING_AS_STRING, &iterValue)) {
goto failed;
}
if (!dbus.message_iter_append_basic(&iterValue, DBUS_TYPE_STRING, &value)) {
goto failed;
}
if (!dbus.message_iter_close_container(&iterEntry, &iterValue) || !dbus.message_iter_close_container(&iterDict, &iterEntry)) {
goto failed;
}
}
if (!dbus.message_iter_close_container(iterInit, &iterDict)) {
goto failed;
}
return true;
failed:
/* message_iter_abandon_container_if_open() and message_iter_abandon_container() might be
* missing if libdbus is too old. Instead, we just return without cleaning up any eventual
* open container */
return false;
}
static bool SDL_DBus_AppendDictWithKeyValue(DBusMessageIter *iterInit, const char *key, const char *value)
{
const char *keys[1];
const char *values[1];
keys[0] = key;
values[0] = value;
return SDL_DBus_AppendDictWithKeysAndValues(iterInit, keys, values, 1);
}
bool SDL_DBus_ScreensaverInhibit(bool inhibit)
{
const char *default_inhibit_reason = "Playing a game";
if ((inhibit && (screensaver_cookie != 0 || inhibit_handle)) || (!inhibit && (screensaver_cookie == 0 && !inhibit_handle))) {
return true;
}
if (!dbus.session_conn) {
/* We either lost connection to the session bus or were not able to
* load the D-Bus library at all. */
return false;
}
if (SDL_GetSandbox() != SDL_SANDBOX_NONE) {
const char *bus_name = "org.freedesktop.portal.Desktop";
const char *path = "/org/freedesktop/portal/desktop";
const char *interface = "org.freedesktop.portal.Inhibit";
const char *window = ""; // As a future improvement we could gather the X11 XID or Wayland surface identifier
static const unsigned int INHIBIT_IDLE = 8; // Taken from the portal API reference
DBusMessageIter iterInit;
if (inhibit) {
DBusMessage *msg;
bool result = false;
const char *key = "reason";
const char *reply = NULL;
const char *reason = SDL_GetHint(SDL_HINT_SCREENSAVER_INHIBIT_ACTIVITY_NAME);
if (!reason || !reason[0]) {
reason = default_inhibit_reason;
}
msg = dbus.message_new_method_call(bus_name, path, interface, "Inhibit");
if (!msg) {
return false;
}
if (!dbus.message_append_args(msg, DBUS_TYPE_STRING, &window, DBUS_TYPE_UINT32, &INHIBIT_IDLE, DBUS_TYPE_INVALID)) {
dbus.message_unref(msg);
return false;
}
dbus.message_iter_init_append(msg, &iterInit);
// a{sv}
if (!SDL_DBus_AppendDictWithKeyValue(&iterInit, key, reason)) {
dbus.message_unref(msg);
return false;
}
if (SDL_DBus_CallWithBasicReply(dbus.session_conn, msg, DBUS_TYPE_OBJECT_PATH, &reply)) {
inhibit_handle = SDL_strdup(reply);
result = true;
}
dbus.message_unref(msg);
return result;
} else {
if (!SDL_DBus_CallVoidMethod(bus_name, inhibit_handle, "org.freedesktop.portal.Request", "Close", DBUS_TYPE_INVALID)) {
return false;
}
SDL_free(inhibit_handle);
inhibit_handle = NULL;
}
} else {
const char *bus_name = "org.freedesktop.ScreenSaver";
const char *path = "/org/freedesktop/ScreenSaver";
const char *interface = "org.freedesktop.ScreenSaver";
if (inhibit) {
const char *app = SDL_GetAppMetadataProperty(SDL_PROP_APP_METADATA_NAME_STRING);
const char *reason = SDL_GetHint(SDL_HINT_SCREENSAVER_INHIBIT_ACTIVITY_NAME);
if (!reason || !reason[0]) {
reason = default_inhibit_reason;
}
if (!SDL_DBus_CallMethod(bus_name, path, interface, "Inhibit",
DBUS_TYPE_STRING, &app, DBUS_TYPE_STRING, &reason, DBUS_TYPE_INVALID,
DBUS_TYPE_UINT32, &screensaver_cookie, DBUS_TYPE_INVALID)) {
return false;
}
return (screensaver_cookie != 0);
} else {
if (!SDL_DBus_CallVoidMethod(bus_name, path, interface, "UnInhibit", DBUS_TYPE_UINT32, &screensaver_cookie, DBUS_TYPE_INVALID)) {
return false;
}
screensaver_cookie = 0;
}
}
return true;
}
void SDL_DBus_PumpEvents(void)
{
if (dbus.session_conn) {
dbus.connection_read_write(dbus.session_conn, 0);
while (dbus.connection_dispatch(dbus.session_conn) == DBUS_DISPATCH_DATA_REMAINS) {
// Do nothing, actual work happens in DBus_MessageFilter
SDL_DelayNS(SDL_US_TO_NS(10));
}
}
}
/*
* Get the machine ID if possible. Result must be freed with dbus->free().
*/
char *SDL_DBus_GetLocalMachineId(void)
{
DBusError err;
char *result;
dbus.error_init(&err);
if (dbus.try_get_local_machine_id) {
// Available since dbus 1.12.0, has proper error-handling
result = dbus.try_get_local_machine_id(&err);
} else {
/* Available since time immemorial, but has no error-handling:
* if the machine ID can't be read, many versions of libdbus will
* treat that as a fatal mis-installation and abort() */
result = dbus.get_local_machine_id();
}
if (result) {
return result;
}
if (dbus.error_is_set(&err)) {
SDL_SetError("%s: %s", err.name, err.message);
dbus.error_free(&err);
} else {
SDL_SetError("Error getting D-Bus machine ID");
}
return NULL;
}
/*
* Convert file drops with mime type "application/vnd.portal.filetransfer" to file paths
* Result must be freed with dbus->free_string_array().
* https://flatpak.github.io/xdg-desktop-portal/#gdbus-method-org-freedesktop-portal-FileTransfer.RetrieveFiles
*/
char **SDL_DBus_DocumentsPortalRetrieveFiles(const char *key, int *path_count)
{
DBusError err;
DBusMessageIter iter, iterDict;
char **paths = NULL;
DBusMessage *reply = NULL;
DBusMessage *msg = dbus.message_new_method_call("org.freedesktop.portal.Documents", // Node
"/org/freedesktop/portal/documents", // Path
"org.freedesktop.portal.FileTransfer", // Interface
"RetrieveFiles"); // Method
// Make sure we have a connection to the dbus session bus
if (!SDL_DBus_GetContext() || !dbus.session_conn) {
/* We either cannot connect to the session bus or were unable to
* load the D-Bus library at all. */
return NULL;
}
dbus.error_init(&err);
// First argument is a "application/vnd.portal.filetransfer" key from a DnD or clipboard event
if (!dbus.message_append_args(msg, DBUS_TYPE_STRING, &key, DBUS_TYPE_INVALID)) {
SDL_OutOfMemory();
dbus.message_unref(msg);
goto failed;
}
/* Second argument is a variant dictionary for options.
* The spec doesn't define any entries yet so it's empty. */
dbus.message_iter_init_append(msg, &iter);
if (!dbus.message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &iterDict) ||
!dbus.message_iter_close_container(&iter, &iterDict)) {
SDL_OutOfMemory();
dbus.message_unref(msg);
goto failed;
}
reply = dbus.connection_send_with_reply_and_block(dbus.session_conn, msg, DBUS_TIMEOUT_USE_DEFAULT, &err);
dbus.message_unref(msg);
if (reply) {
dbus.message_get_args(reply, &err, DBUS_TYPE_ARRAY, DBUS_TYPE_STRING, &paths, path_count, DBUS_TYPE_INVALID);
dbus.message_unref(reply);
}
if (paths) {
return paths;
}
failed:
if (dbus.error_is_set(&err)) {
SDL_SetError("%s: %s", err.name, err.message);
dbus.error_free(&err);
} else {
SDL_SetError("Error retrieving paths for documents portal \"%s\"", key);
}
return NULL;
}
#endif

114
thirdparty/sdl/core/linux/SDL_dbus.h vendored Normal file
View file

@ -0,0 +1,114 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#ifndef SDL_dbus_h_
#define SDL_dbus_h_
#ifdef HAVE_DBUS_DBUS_H
#define SDL_USE_LIBDBUS 1
#include <dbus/dbus.h>
#ifndef DBUS_TIMEOUT_USE_DEFAULT
#define DBUS_TIMEOUT_USE_DEFAULT -1
#endif
#ifndef DBUS_TIMEOUT_INFINITE
#define DBUS_TIMEOUT_INFINITE ((int) 0x7fffffff)
#endif
typedef struct SDL_DBusContext
{
DBusConnection *session_conn;
DBusConnection *system_conn;
DBusConnection *(*bus_get_private)(DBusBusType, DBusError *);
dbus_bool_t (*bus_register)(DBusConnection *, DBusError *);
void (*bus_add_match)(DBusConnection *, const char *, DBusError *);
DBusConnection *(*connection_open_private)(const char *, DBusError *);
void (*connection_set_exit_on_disconnect)(DBusConnection *, dbus_bool_t);
dbus_bool_t (*connection_get_is_connected)(DBusConnection *);
dbus_bool_t (*connection_add_filter)(DBusConnection *, DBusHandleMessageFunction, void *, DBusFreeFunction);
dbus_bool_t (*connection_remove_filter)(DBusConnection *, DBusHandleMessageFunction, void *);
dbus_bool_t (*connection_try_register_object_path)(DBusConnection *, const char *,
const DBusObjectPathVTable *, void *, DBusError *);
dbus_bool_t (*connection_send)(DBusConnection *, DBusMessage *, dbus_uint32_t *);
DBusMessage *(*connection_send_with_reply_and_block)(DBusConnection *, DBusMessage *, int, DBusError *);
void (*connection_close)(DBusConnection *);
void (*connection_ref)(DBusConnection *);
void (*connection_unref)(DBusConnection *);
void (*connection_flush)(DBusConnection *);
dbus_bool_t (*connection_read_write)(DBusConnection *, int);
DBusDispatchStatus (*connection_dispatch)(DBusConnection *);
dbus_bool_t (*message_is_signal)(DBusMessage *, const char *, const char *);
dbus_bool_t (*message_has_path)(DBusMessage *, const char *);
DBusMessage *(*message_new_method_call)(const char *, const char *, const char *, const char *);
dbus_bool_t (*message_append_args)(DBusMessage *, int, ...);
dbus_bool_t (*message_append_args_valist)(DBusMessage *, int, va_list);
void (*message_iter_init_append)(DBusMessage *, DBusMessageIter *);
dbus_bool_t (*message_iter_open_container)(DBusMessageIter *, int, const char *, DBusMessageIter *);
dbus_bool_t (*message_iter_append_basic)(DBusMessageIter *, int, const void *);
dbus_bool_t (*message_iter_close_container)(DBusMessageIter *, DBusMessageIter *);
dbus_bool_t (*message_get_args)(DBusMessage *, DBusError *, int, ...);
dbus_bool_t (*message_get_args_valist)(DBusMessage *, DBusError *, int, va_list);
dbus_bool_t (*message_iter_init)(DBusMessage *, DBusMessageIter *);
dbus_bool_t (*message_iter_next)(DBusMessageIter *);
void (*message_iter_get_basic)(DBusMessageIter *, void *);
int (*message_iter_get_arg_type)(DBusMessageIter *);
void (*message_iter_recurse)(DBusMessageIter *, DBusMessageIter *);
void (*message_unref)(DBusMessage *);
dbus_bool_t (*threads_init_default)(void);
void (*error_init)(DBusError *);
dbus_bool_t (*error_is_set)(const DBusError *);
void (*error_free)(DBusError *);
char *(*get_local_machine_id)(void);
char *(*try_get_local_machine_id)(DBusError *);
void (*free)(void *);
void (*free_string_array)(char **);
void (*shutdown)(void);
} SDL_DBusContext;
extern void SDL_DBus_Init(void);
extern void SDL_DBus_Quit(void);
extern SDL_DBusContext *SDL_DBus_GetContext(void);
// These use the built-in Session connection.
extern bool SDL_DBus_CallMethod(const char *node, const char *path, const char *interface, const char *method, ...);
extern bool SDL_DBus_CallVoidMethod(const char *node, const char *path, const char *interface, const char *method, ...);
extern bool SDL_DBus_QueryProperty(const char *node, const char *path, const char *interface, const char *property, int expectedtype, void *result);
// These use whatever connection you like.
extern bool SDL_DBus_CallMethodOnConnection(DBusConnection *conn, const char *node, const char *path, const char *interface, const char *method, ...);
extern bool SDL_DBus_CallVoidMethodOnConnection(DBusConnection *conn, const char *node, const char *path, const char *interface, const char *method, ...);
extern bool SDL_DBus_QueryPropertyOnConnection(DBusConnection *conn, const char *node, const char *path, const char *interface, const char *property, int expectedtype, void *result);
extern void SDL_DBus_ScreensaverTickle(void);
extern bool SDL_DBus_ScreensaverInhibit(bool inhibit);
extern void SDL_DBus_PumpEvents(void);
extern char *SDL_DBus_GetLocalMachineId(void);
extern char **SDL_DBus_DocumentsPortalRetrieveFiles(const char *key, int *files_count);
#endif // HAVE_DBUS_DBUS_H
#endif // SDL_dbus_h_

1037
thirdparty/sdl/core/linux/SDL_evdev.c vendored Normal file

File diff suppressed because it is too large Load diff

41
thirdparty/sdl/core/linux/SDL_evdev.h vendored Normal file
View file

@ -0,0 +1,41 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#ifndef SDL_evdev_h_
#define SDL_evdev_h_
#ifdef SDL_INPUT_LINUXEV
struct input_event;
extern bool SDL_EVDEV_Init(void);
extern void SDL_EVDEV_Quit(void);
extern void SDL_EVDEV_SetVTSwitchCallbacks(void (*release_callback)(void*), void *release_callback_data,
void (*acquire_callback)(void*), void *acquire_callback_data);
extern int SDL_EVDEV_GetDeviceCount(int device_class);
extern void SDL_EVDEV_Poll(void);
extern Uint64 SDL_EVDEV_GetEventTimestamp(struct input_event *event);
#endif // SDL_INPUT_LINUXEV
#endif // SDL_evdev_h_

View file

@ -0,0 +1,168 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 2020 Collabora Ltd.
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#include "SDL_evdev_capabilities.h"
#ifdef HAVE_LINUX_INPUT_H
// missing defines in older Linux kernel headers
#ifndef BTN_TRIGGER_HAPPY
#define BTN_TRIGGER_HAPPY 0x2c0
#endif
#ifndef BTN_DPAD_UP
#define BTN_DPAD_UP 0x220
#endif
#ifndef KEY_ALS_TOGGLE
#define KEY_ALS_TOGGLE 0x230
#endif
extern int
SDL_EVDEV_GuessDeviceClass(const unsigned long bitmask_props[NBITS(INPUT_PROP_MAX)],
const unsigned long bitmask_ev[NBITS(EV_MAX)],
const unsigned long bitmask_abs[NBITS(ABS_MAX)],
const unsigned long bitmask_key[NBITS(KEY_MAX)],
const unsigned long bitmask_rel[NBITS(REL_MAX)])
{
struct range
{
unsigned start;
unsigned end;
};
// key code ranges above BTN_MISC (start is inclusive, stop is exclusive)
static const struct range high_key_blocks[] = {
{ KEY_OK, BTN_DPAD_UP },
{ KEY_ALS_TOGGLE, BTN_TRIGGER_HAPPY }
};
int devclass = 0;
unsigned long keyboard_mask;
// If the kernel specifically says it's an accelerometer, believe it
if (test_bit(INPUT_PROP_ACCELEROMETER, bitmask_props)) {
return SDL_UDEV_DEVICE_ACCELEROMETER;
}
// We treat pointing sticks as indistinguishable from mice
if (test_bit(INPUT_PROP_POINTING_STICK, bitmask_props)) {
return SDL_UDEV_DEVICE_MOUSE;
}
// We treat buttonpads as equivalent to touchpads
if (test_bit(INPUT_PROP_TOPBUTTONPAD, bitmask_props) ||
test_bit(INPUT_PROP_BUTTONPAD, bitmask_props) ||
test_bit(INPUT_PROP_SEMI_MT, bitmask_props)) {
return SDL_UDEV_DEVICE_TOUCHPAD;
}
// X, Y, Z axes but no buttons probably means an accelerometer
if (test_bit(EV_ABS, bitmask_ev) &&
test_bit(ABS_X, bitmask_abs) &&
test_bit(ABS_Y, bitmask_abs) &&
test_bit(ABS_Z, bitmask_abs) &&
!test_bit(EV_KEY, bitmask_ev)) {
return SDL_UDEV_DEVICE_ACCELEROMETER;
}
/* RX, RY, RZ axes but no buttons probably means a gyro or
* accelerometer (we don't distinguish) */
if (test_bit(EV_ABS, bitmask_ev) &&
test_bit(ABS_RX, bitmask_abs) &&
test_bit(ABS_RY, bitmask_abs) &&
test_bit(ABS_RZ, bitmask_abs) &&
!test_bit(EV_KEY, bitmask_ev)) {
return SDL_UDEV_DEVICE_ACCELEROMETER;
}
if (test_bit(EV_ABS, bitmask_ev) &&
test_bit(ABS_X, bitmask_abs) && test_bit(ABS_Y, bitmask_abs)) {
if (test_bit(BTN_STYLUS, bitmask_key) || test_bit(BTN_TOOL_PEN, bitmask_key)) {
; // ID_INPUT_TABLET
} else if (test_bit(BTN_TOOL_FINGER, bitmask_key) && !test_bit(BTN_TOOL_PEN, bitmask_key)) {
devclass |= SDL_UDEV_DEVICE_TOUCHPAD; // ID_INPUT_TOUCHPAD
} else if (test_bit(BTN_MOUSE, bitmask_key)) {
devclass |= SDL_UDEV_DEVICE_MOUSE; // ID_INPUT_MOUSE
} else if (test_bit(BTN_TOUCH, bitmask_key)) {
/* TODO: better determining between touchscreen and multitouch touchpad,
see https://github.com/systemd/systemd/blob/master/src/udev/udev-builtin-input_id.c */
devclass |= SDL_UDEV_DEVICE_TOUCHSCREEN; // ID_INPUT_TOUCHSCREEN
}
if (test_bit(BTN_TRIGGER, bitmask_key) ||
test_bit(BTN_A, bitmask_key) ||
test_bit(BTN_1, bitmask_key) ||
test_bit(ABS_RX, bitmask_abs) ||
test_bit(ABS_RY, bitmask_abs) ||
test_bit(ABS_RZ, bitmask_abs) ||
test_bit(ABS_THROTTLE, bitmask_abs) ||
test_bit(ABS_RUDDER, bitmask_abs) ||
test_bit(ABS_WHEEL, bitmask_abs) ||
test_bit(ABS_GAS, bitmask_abs) ||
test_bit(ABS_BRAKE, bitmask_abs)) {
devclass |= SDL_UDEV_DEVICE_JOYSTICK; // ID_INPUT_JOYSTICK
}
}
if (test_bit(EV_REL, bitmask_ev) &&
test_bit(REL_X, bitmask_rel) && test_bit(REL_Y, bitmask_rel) &&
test_bit(BTN_MOUSE, bitmask_key)) {
devclass |= SDL_UDEV_DEVICE_MOUSE; // ID_INPUT_MOUSE
}
if (test_bit(EV_KEY, bitmask_ev)) {
unsigned i;
unsigned long found = 0;
for (i = 0; i < BTN_MISC / BITS_PER_LONG; ++i) {
found |= bitmask_key[i];
}
// If there are no keys in the lower block, check the higher blocks
if (!found) {
unsigned block;
for (block = 0; block < (sizeof(high_key_blocks) / sizeof(struct range)); ++block) {
for (i = high_key_blocks[block].start; i < high_key_blocks[block].end; ++i) {
if (test_bit(i, bitmask_key)) {
found = 1;
break;
}
}
}
}
if (found > 0) {
devclass |= SDL_UDEV_DEVICE_HAS_KEYS; // ID_INPUT_KEY
}
}
/* the first 32 bits are ESC, numbers, and Q to D, so if we have all of
* those, consider it to be a fully-featured keyboard;
* do not test KEY_RESERVED, though */
keyboard_mask = 0xFFFFFFFE;
if ((bitmask_key[0] & keyboard_mask) == keyboard_mask) {
devclass |= SDL_UDEV_DEVICE_KEYBOARD; // ID_INPUT_KEYBOARD
}
return devclass;
}
#endif // HAVE_LINUX_INPUT_H

View file

@ -0,0 +1,76 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
Copyright (C) 2020 Collabora Ltd.
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#ifndef SDL_evdev_capabilities_h_
#define SDL_evdev_capabilities_h_
#ifdef HAVE_LINUX_INPUT_H
#include <linux/input.h>
#ifndef INPUT_PROP_SEMI_MT
#define INPUT_PROP_SEMI_MT 0x03
#endif
#ifndef INPUT_PROP_TOPBUTTONPAD
#define INPUT_PROP_TOPBUTTONPAD 0x04
#endif
#ifndef INPUT_PROP_POINTING_STICK
#define INPUT_PROP_POINTING_STICK 0x05
#endif
#ifndef INPUT_PROP_ACCELEROMETER
#define INPUT_PROP_ACCELEROMETER 0x06
#endif
#ifndef INPUT_PROP_MAX
#define INPUT_PROP_MAX 0x1f
#endif
// A device can be any combination of these classes
typedef enum
{
SDL_UDEV_DEVICE_UNKNOWN = 0x0000,
SDL_UDEV_DEVICE_MOUSE = 0x0001,
SDL_UDEV_DEVICE_KEYBOARD = 0x0002,
SDL_UDEV_DEVICE_JOYSTICK = 0x0004,
SDL_UDEV_DEVICE_SOUND = 0x0008,
SDL_UDEV_DEVICE_TOUCHSCREEN = 0x0010,
SDL_UDEV_DEVICE_ACCELEROMETER = 0x0020,
SDL_UDEV_DEVICE_TOUCHPAD = 0x0040,
SDL_UDEV_DEVICE_HAS_KEYS = 0x0080,
SDL_UDEV_DEVICE_VIDEO_CAPTURE = 0x0100,
} SDL_UDEV_deviceclass;
#define BITS_PER_LONG (sizeof(unsigned long) * 8)
#define NBITS(x) ((((x)-1) / BITS_PER_LONG) + 1)
#define EVDEV_OFF(x) ((x) % BITS_PER_LONG)
#define EVDEV_LONG(x) ((x) / BITS_PER_LONG)
#define test_bit(bit, array) ((array[EVDEV_LONG(bit)] >> EVDEV_OFF(bit)) & 1)
extern int SDL_EVDEV_GuessDeviceClass(const unsigned long bitmask_props[NBITS(INPUT_PROP_MAX)],
const unsigned long bitmask_ev[NBITS(EV_MAX)],
const unsigned long bitmask_abs[NBITS(ABS_MAX)],
const unsigned long bitmask_key[NBITS(KEY_MAX)],
const unsigned long bitmask_rel[NBITS(REL_MAX)]);
#endif // HAVE_LINUX_INPUT_H
#endif // SDL_evdev_capabilities_h_

View file

@ -0,0 +1,997 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#include "SDL_evdev_kbd.h"
#ifdef SDL_INPUT_LINUXKD
// This logic is adapted from drivers/tty/vt/keyboard.c in the Linux kernel source
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/kd.h>
#include <linux/keyboard.h>
#include <linux/vt.h>
#include <linux/tiocl.h> // for TIOCL_GETSHIFTSTATE
#include <signal.h>
#include "../../events/SDL_events_c.h"
#include "SDL_evdev_kbd_default_accents.h"
#include "SDL_evdev_kbd_default_keymap.h"
// These are not defined in older Linux kernel headers
#ifndef K_UNICODE
#define K_UNICODE 0x03
#endif
#ifndef K_OFF
#define K_OFF 0x04
#endif
/*
* Handler Tables.
*/
#define K_HANDLERS \
k_self, k_fn, k_spec, k_pad, \
k_dead, k_cons, k_cur, k_shift, \
k_meta, k_ascii, k_lock, k_lowercase, \
k_slock, k_dead2, k_brl, k_ignore
typedef void(k_handler_fn)(SDL_EVDEV_keyboard_state *kbd, unsigned char value, char up_flag);
static k_handler_fn K_HANDLERS;
static k_handler_fn *k_handler[16] = { K_HANDLERS };
typedef void(fn_handler_fn)(SDL_EVDEV_keyboard_state *kbd);
static void fn_enter(SDL_EVDEV_keyboard_state *kbd);
static void fn_caps_toggle(SDL_EVDEV_keyboard_state *kbd);
static void fn_caps_on(SDL_EVDEV_keyboard_state *kbd);
static void fn_num(SDL_EVDEV_keyboard_state *kbd);
static void fn_compose(SDL_EVDEV_keyboard_state *kbd);
static fn_handler_fn *fn_handler[] = {
NULL, fn_enter, NULL, NULL,
NULL, NULL, NULL, fn_caps_toggle,
fn_num, NULL, NULL, NULL,
NULL, fn_caps_on, fn_compose, NULL,
NULL, NULL, NULL, fn_num
};
/*
* Keyboard State
*/
struct SDL_EVDEV_keyboard_state
{
int console_fd;
bool muted;
int old_kbd_mode;
unsigned short **key_maps;
unsigned char shift_down[NR_SHIFT]; // shift state counters..
bool dead_key_next;
int npadch; // -1 or number assembled on pad
struct kbdiacrs *accents;
unsigned int diacr;
bool rep; // flag telling character repeat
unsigned char lockstate;
unsigned char slockstate;
unsigned char ledflagstate;
char shift_state;
char text[128];
unsigned int text_len;
void (*vt_release_callback)(void *);
void *vt_release_callback_data;
void (*vt_acquire_callback)(void *);
void *vt_acquire_callback_data;
};
#ifdef DUMP_ACCENTS
static void SDL_EVDEV_dump_accents(SDL_EVDEV_keyboard_state *kbd)
{
unsigned int i;
printf("static struct kbdiacrs default_accents = {\n");
printf(" %d,\n", kbd->accents->kb_cnt);
printf(" {\n");
for (i = 0; i < kbd->accents->kb_cnt; ++i) {
struct kbdiacr *diacr = &kbd->accents->kbdiacr[i];
printf(" { 0x%.2x, 0x%.2x, 0x%.2x },\n",
diacr->diacr, diacr->base, diacr->result);
}
while (i < 256) {
printf(" { 0x00, 0x00, 0x00 },\n");
++i;
}
printf(" }\n");
printf("};\n");
}
#endif // DUMP_ACCENTS
#ifdef DUMP_KEYMAP
static void SDL_EVDEV_dump_keymap(SDL_EVDEV_keyboard_state *kbd)
{
int i, j;
for (i = 0; i < MAX_NR_KEYMAPS; ++i) {
if (kbd->key_maps[i]) {
printf("static unsigned short default_key_map_%d[NR_KEYS] = {", i);
for (j = 0; j < NR_KEYS; ++j) {
if ((j % 8) == 0) {
printf("\n ");
}
printf("0x%.4x, ", kbd->key_maps[i][j]);
}
printf("\n};\n");
}
}
printf("\n");
printf("static unsigned short *default_key_maps[MAX_NR_KEYMAPS] = {\n");
for (i = 0; i < MAX_NR_KEYMAPS; ++i) {
if (kbd->key_maps[i]) {
printf(" default_key_map_%d,\n", i);
} else {
printf(" NULL,\n");
}
}
printf("};\n");
}
#endif // DUMP_KEYMAP
static SDL_EVDEV_keyboard_state *kbd_cleanup_state = NULL;
static int kbd_cleanup_sigactions_installed = 0;
static int kbd_cleanup_atexit_installed = 0;
static struct sigaction old_sigaction[NSIG];
static int fatal_signals[] = {
// Handlers for SIGTERM and SIGINT are installed in SDL_InitQuit.
SIGHUP, SIGQUIT, SIGILL, SIGABRT,
SIGFPE, SIGSEGV, SIGPIPE, SIGBUS,
SIGSYS
};
static void kbd_cleanup(void)
{
SDL_EVDEV_keyboard_state *kbd = kbd_cleanup_state;
if (!kbd) {
return;
}
kbd_cleanup_state = NULL;
ioctl(kbd->console_fd, KDSKBMODE, kbd->old_kbd_mode);
}
static void SDL_EVDEV_kbd_reraise_signal(int sig)
{
(void)raise(sig);
}
static siginfo_t *SDL_EVDEV_kdb_cleanup_siginfo = NULL;
static void *SDL_EVDEV_kdb_cleanup_ucontext = NULL;
static void kbd_cleanup_signal_action(int signum, siginfo_t *info, void *ucontext)
{
struct sigaction *old_action_p = &(old_sigaction[signum]);
sigset_t sigset;
// Restore original signal handler before going any further.
sigaction(signum, old_action_p, NULL);
// Unmask current signal.
sigemptyset(&sigset);
sigaddset(&sigset, signum);
sigprocmask(SIG_UNBLOCK, &sigset, NULL);
// Save original signal info and context for archeologists.
SDL_EVDEV_kdb_cleanup_siginfo = info;
SDL_EVDEV_kdb_cleanup_ucontext = ucontext;
// Restore keyboard.
kbd_cleanup();
// Reraise signal.
SDL_EVDEV_kbd_reraise_signal(signum);
}
static void kbd_unregister_emerg_cleanup(void)
{
int tabidx;
kbd_cleanup_state = NULL;
if (!kbd_cleanup_sigactions_installed) {
return;
}
kbd_cleanup_sigactions_installed = 0;
for (tabidx = 0; tabidx < sizeof(fatal_signals) / sizeof(fatal_signals[0]); ++tabidx) {
struct sigaction *old_action_p;
struct sigaction cur_action;
int signum = fatal_signals[tabidx];
old_action_p = &(old_sigaction[signum]);
// Examine current signal action
if (sigaction(signum, NULL, &cur_action)) {
continue;
}
// Check if action installed and not modified
if (!(cur_action.sa_flags & SA_SIGINFO) || cur_action.sa_sigaction != &kbd_cleanup_signal_action) {
continue;
}
// Restore original action
sigaction(signum, old_action_p, NULL);
}
}
static void kbd_cleanup_atexit(void)
{
// Restore keyboard.
kbd_cleanup();
// Try to restore signal handlers in case shared library is being unloaded
kbd_unregister_emerg_cleanup();
}
static void kbd_register_emerg_cleanup(SDL_EVDEV_keyboard_state *kbd)
{
int tabidx;
if (kbd_cleanup_state) {
return;
}
kbd_cleanup_state = kbd;
if (!kbd_cleanup_atexit_installed) {
/* Since glibc 2.2.3, atexit() (and on_exit(3)) can be used within a shared library to establish
* functions that are called when the shared library is unloaded.
* -- man atexit(3)
*/
(void)atexit(kbd_cleanup_atexit);
kbd_cleanup_atexit_installed = 1;
}
if (kbd_cleanup_sigactions_installed) {
return;
}
kbd_cleanup_sigactions_installed = 1;
for (tabidx = 0; tabidx < sizeof(fatal_signals) / sizeof(fatal_signals[0]); ++tabidx) {
struct sigaction *old_action_p;
struct sigaction new_action;
int signum = fatal_signals[tabidx];
old_action_p = &(old_sigaction[signum]);
if (sigaction(signum, NULL, old_action_p)) {
continue;
}
/* Skip SIGHUP and SIGPIPE if handler is already installed
* - assume the handler will do the cleanup
*/
if ((signum == SIGHUP || signum == SIGPIPE) && (old_action_p->sa_handler != SIG_DFL || (void (*)(int))old_action_p->sa_sigaction != SIG_DFL)) {
continue;
}
new_action = *old_action_p;
new_action.sa_flags |= SA_SIGINFO;
new_action.sa_sigaction = &kbd_cleanup_signal_action;
sigaction(signum, &new_action, NULL);
}
}
enum {
VT_SIGNAL_NONE,
VT_SIGNAL_RELEASE,
VT_SIGNAL_ACQUIRE,
};
static int vt_release_signal;
static int vt_acquire_signal;
static SDL_AtomicInt vt_signal_pending;
typedef void (*signal_handler)(int signum);
static void kbd_vt_release_signal_action(int signum)
{
SDL_SetAtomicInt(&vt_signal_pending, VT_SIGNAL_RELEASE);
}
static void kbd_vt_acquire_signal_action(int signum)
{
SDL_SetAtomicInt(&vt_signal_pending, VT_SIGNAL_ACQUIRE);
}
static bool setup_vt_signal(int signum, signal_handler handler)
{
struct sigaction *old_action_p;
struct sigaction new_action;
old_action_p = &(old_sigaction[signum]);
SDL_zero(new_action);
new_action.sa_handler = handler;
new_action.sa_flags = SA_RESTART;
if (sigaction(signum, &new_action, old_action_p) < 0) {
return false;
}
if (old_action_p->sa_handler != SIG_DFL) {
// This signal is already in use
sigaction(signum, old_action_p, NULL);
return false;
}
return true;
}
static int find_free_signal(signal_handler handler)
{
#ifdef SIGRTMIN
int i;
for (i = SIGRTMIN + 2; i <= SIGRTMAX; ++i) {
if (setup_vt_signal(i, handler)) {
return i;
}
}
#endif
if (setup_vt_signal(SIGUSR1, handler)) {
return SIGUSR1;
}
if (setup_vt_signal(SIGUSR2, handler)) {
return SIGUSR2;
}
return 0;
}
static void kbd_vt_quit(int console_fd)
{
struct vt_mode mode;
if (vt_release_signal) {
sigaction(vt_release_signal, &old_sigaction[vt_release_signal], NULL);
vt_release_signal = 0;
}
if (vt_acquire_signal) {
sigaction(vt_acquire_signal, &old_sigaction[vt_acquire_signal], NULL);
vt_acquire_signal = 0;
}
SDL_zero(mode);
mode.mode = VT_AUTO;
ioctl(console_fd, VT_SETMODE, &mode);
}
static bool kbd_vt_init(int console_fd)
{
struct vt_mode mode;
vt_release_signal = find_free_signal(kbd_vt_release_signal_action);
vt_acquire_signal = find_free_signal(kbd_vt_acquire_signal_action);
if (!vt_release_signal || !vt_acquire_signal ) {
kbd_vt_quit(console_fd);
return false;
}
SDL_zero(mode);
mode.mode = VT_PROCESS;
mode.relsig = vt_release_signal;
mode.acqsig = vt_acquire_signal;
mode.frsig = SIGIO;
if (ioctl(console_fd, VT_SETMODE, &mode) < 0) {
kbd_vt_quit(console_fd);
return false;
}
return true;
}
static void kbd_vt_update(SDL_EVDEV_keyboard_state *state)
{
int signal_pending = SDL_GetAtomicInt(&vt_signal_pending);
if (signal_pending != VT_SIGNAL_NONE) {
if (signal_pending == VT_SIGNAL_RELEASE) {
if (state->vt_release_callback) {
state->vt_release_callback(state->vt_release_callback_data);
}
ioctl(state->console_fd, VT_RELDISP, 1);
} else {
if (state->vt_acquire_callback) {
state->vt_acquire_callback(state->vt_acquire_callback_data);
}
ioctl(state->console_fd, VT_RELDISP, VT_ACKACQ);
}
SDL_CompareAndSwapAtomicInt(&vt_signal_pending, signal_pending, VT_SIGNAL_NONE);
}
}
SDL_EVDEV_keyboard_state *SDL_EVDEV_kbd_init(void)
{
SDL_EVDEV_keyboard_state *kbd;
char flag_state;
char kbtype;
char shift_state[sizeof(long)] = { TIOCL_GETSHIFTSTATE, 0 };
kbd = (SDL_EVDEV_keyboard_state *)SDL_calloc(1, sizeof(*kbd));
if (!kbd) {
return NULL;
}
// This might fail if we're not connected to a tty (e.g. on the Steam Link)
kbd->console_fd = open("/dev/tty", O_RDONLY | O_CLOEXEC);
if (!((ioctl(kbd->console_fd, KDGKBTYPE, &kbtype) == 0) && ((kbtype == KB_101) || (kbtype == KB_84)))) {
close(kbd->console_fd);
kbd->console_fd = -1;
}
kbd->npadch = -1;
if (ioctl(kbd->console_fd, TIOCLINUX, shift_state) == 0) {
kbd->shift_state = *shift_state;
}
if (ioctl(kbd->console_fd, KDGKBLED, &flag_state) == 0) {
kbd->ledflagstate = flag_state;
}
kbd->accents = &default_accents;
kbd->key_maps = default_key_maps;
if (ioctl(kbd->console_fd, KDGKBMODE, &kbd->old_kbd_mode) == 0) {
// Set the keyboard in UNICODE mode and load the keymaps
ioctl(kbd->console_fd, KDSKBMODE, K_UNICODE);
}
kbd_vt_init(kbd->console_fd);
return kbd;
}
void SDL_EVDEV_kbd_set_muted(SDL_EVDEV_keyboard_state *state, bool muted)
{
if (!state) {
return;
}
if (muted == state->muted) {
return;
}
if (muted) {
if (SDL_GetHintBoolean(SDL_HINT_MUTE_CONSOLE_KEYBOARD, true)) {
/* Mute the keyboard so keystrokes only generate evdev events
* and do not leak through to the console
*/
ioctl(state->console_fd, KDSKBMODE, K_OFF);
/* Make sure to restore keyboard if application fails to call
* SDL_Quit before exit or fatal signal is raised.
*/
if (!SDL_GetHintBoolean(SDL_HINT_NO_SIGNAL_HANDLERS, false)) {
kbd_register_emerg_cleanup(state);
}
}
} else {
kbd_unregister_emerg_cleanup();
// Restore the original keyboard mode
ioctl(state->console_fd, KDSKBMODE, state->old_kbd_mode);
}
state->muted = muted;
}
void SDL_EVDEV_kbd_set_vt_switch_callbacks(SDL_EVDEV_keyboard_state *state, void (*release_callback)(void*), void *release_callback_data, void (*acquire_callback)(void*), void *acquire_callback_data)
{
if (state == NULL) {
return;
}
state->vt_release_callback = release_callback;
state->vt_release_callback_data = release_callback_data;
state->vt_acquire_callback = acquire_callback;
state->vt_acquire_callback_data = acquire_callback_data;
}
void SDL_EVDEV_kbd_update(SDL_EVDEV_keyboard_state *state)
{
if (!state) {
return;
}
kbd_vt_update(state);
}
void SDL_EVDEV_kbd_quit(SDL_EVDEV_keyboard_state *state)
{
if (state == NULL) {
return;
}
SDL_EVDEV_kbd_set_muted(state, false);
kbd_vt_quit(state->console_fd);
if (state->console_fd >= 0) {
close(state->console_fd);
state->console_fd = -1;
}
if (state->key_maps && state->key_maps != default_key_maps) {
int i;
for (i = 0; i < MAX_NR_KEYMAPS; ++i) {
if (state->key_maps[i]) {
SDL_free(state->key_maps[i]);
}
}
SDL_free(state->key_maps);
}
SDL_free(state);
}
/*
* Helper Functions.
*/
static void put_queue(SDL_EVDEV_keyboard_state *kbd, uint c)
{
// c is already part of a UTF-8 sequence and safe to add as a character
if (kbd->text_len < (sizeof(kbd->text) - 1)) {
kbd->text[kbd->text_len++] = (char)c;
}
}
static void put_utf8(SDL_EVDEV_keyboard_state *kbd, uint c)
{
if (c < 0x80) {
put_queue(kbd, c); /* 0******* */
} else if (c < 0x800) {
/* 110***** 10****** */
put_queue(kbd, 0xc0 | (c >> 6));
put_queue(kbd, 0x80 | (c & 0x3f));
} else if (c < 0x10000) {
if (c >= 0xD800 && c < 0xE000) {
return;
}
if (c == 0xFFFF) {
return;
}
/* 1110**** 10****** 10****** */
put_queue(kbd, 0xe0 | (c >> 12));
put_queue(kbd, 0x80 | ((c >> 6) & 0x3f));
put_queue(kbd, 0x80 | (c & 0x3f));
} else if (c < 0x110000) {
/* 11110*** 10****** 10****** 10****** */
put_queue(kbd, 0xf0 | (c >> 18));
put_queue(kbd, 0x80 | ((c >> 12) & 0x3f));
put_queue(kbd, 0x80 | ((c >> 6) & 0x3f));
put_queue(kbd, 0x80 | (c & 0x3f));
}
}
/*
* We have a combining character DIACR here, followed by the character CH.
* If the combination occurs in the table, return the corresponding value.
* Otherwise, if CH is a space or equals DIACR, return DIACR.
* Otherwise, conclude that DIACR was not combining after all,
* queue it and return CH.
*/
static unsigned int handle_diacr(SDL_EVDEV_keyboard_state *kbd, unsigned int ch)
{
unsigned int d = kbd->diacr;
unsigned int i;
kbd->diacr = 0;
if (kbd->console_fd >= 0)
if (ioctl(kbd->console_fd, KDGKBDIACR, kbd->accents) < 0) {
// No worries, we'll use the default accent table
}
for (i = 0; i < kbd->accents->kb_cnt; i++) {
if (kbd->accents->kbdiacr[i].diacr == d &&
kbd->accents->kbdiacr[i].base == ch) {
return kbd->accents->kbdiacr[i].result;
}
}
if (ch == ' ' || ch == d) {
return d;
}
put_utf8(kbd, d);
return ch;
}
static bool vc_kbd_led(SDL_EVDEV_keyboard_state *kbd, int flag)
{
return (kbd->ledflagstate & flag) != 0;
}
static void set_vc_kbd_led(SDL_EVDEV_keyboard_state *kbd, int flag)
{
kbd->ledflagstate |= flag;
ioctl(kbd->console_fd, KDSETLED, (unsigned long)(kbd->ledflagstate));
}
static void clr_vc_kbd_led(SDL_EVDEV_keyboard_state *kbd, int flag)
{
kbd->ledflagstate &= ~flag;
ioctl(kbd->console_fd, KDSETLED, (unsigned long)(kbd->ledflagstate));
}
static void chg_vc_kbd_lock(SDL_EVDEV_keyboard_state *kbd, int flag)
{
kbd->lockstate ^= 1 << flag;
}
static void chg_vc_kbd_slock(SDL_EVDEV_keyboard_state *kbd, int flag)
{
kbd->slockstate ^= 1 << flag;
}
static void chg_vc_kbd_led(SDL_EVDEV_keyboard_state *kbd, int flag)
{
kbd->ledflagstate ^= flag;
ioctl(kbd->console_fd, KDSETLED, (unsigned long)(kbd->ledflagstate));
}
/*
* Special function handlers
*/
static void fn_enter(SDL_EVDEV_keyboard_state *kbd)
{
if (kbd->diacr) {
put_utf8(kbd, kbd->diacr);
kbd->diacr = 0;
}
}
static void fn_caps_toggle(SDL_EVDEV_keyboard_state *kbd)
{
if (kbd->rep) {
return;
}
chg_vc_kbd_led(kbd, K_CAPSLOCK);
}
static void fn_caps_on(SDL_EVDEV_keyboard_state *kbd)
{
if (kbd->rep) {
return;
}
set_vc_kbd_led(kbd, K_CAPSLOCK);
}
static void fn_num(SDL_EVDEV_keyboard_state *kbd)
{
if (!kbd->rep) {
chg_vc_kbd_led(kbd, K_NUMLOCK);
}
}
static void fn_compose(SDL_EVDEV_keyboard_state *kbd)
{
kbd->dead_key_next = true;
}
/*
* Special key handlers
*/
static void k_ignore(SDL_EVDEV_keyboard_state *kbd, unsigned char value, char up_flag)
{
}
static void k_spec(SDL_EVDEV_keyboard_state *kbd, unsigned char value, char up_flag)
{
if (up_flag) {
return;
}
if (value >= SDL_arraysize(fn_handler)) {
return;
}
if (fn_handler[value]) {
fn_handler[value](kbd);
}
}
static void k_lowercase(SDL_EVDEV_keyboard_state *kbd, unsigned char value, char up_flag)
{
}
static void k_self(SDL_EVDEV_keyboard_state *kbd, unsigned char value, char up_flag)
{
if (up_flag) {
return; // no action, if this is a key release
}
if (kbd->diacr) {
value = handle_diacr(kbd, value);
}
if (kbd->dead_key_next) {
kbd->dead_key_next = false;
kbd->diacr = value;
return;
}
put_utf8(kbd, value);
}
static void k_deadunicode(SDL_EVDEV_keyboard_state *kbd, unsigned int value, char up_flag)
{
if (up_flag) {
return;
}
kbd->diacr = (kbd->diacr ? handle_diacr(kbd, value) : value);
}
static void k_dead(SDL_EVDEV_keyboard_state *kbd, unsigned char value, char up_flag)
{
const unsigned char ret_diacr[NR_DEAD] = { '`', '\'', '^', '~', '"', ',' };
k_deadunicode(kbd, ret_diacr[value], up_flag);
}
static void k_dead2(SDL_EVDEV_keyboard_state *kbd, unsigned char value, char up_flag)
{
k_deadunicode(kbd, value, up_flag);
}
static void k_cons(SDL_EVDEV_keyboard_state *kbd, unsigned char value, char up_flag)
{
}
static void k_fn(SDL_EVDEV_keyboard_state *kbd, unsigned char value, char up_flag)
{
}
static void k_cur(SDL_EVDEV_keyboard_state *kbd, unsigned char value, char up_flag)
{
}
static void k_pad(SDL_EVDEV_keyboard_state *kbd, unsigned char value, char up_flag)
{
static const char pad_chars[] = "0123456789+-*/\015,.?()#";
if (up_flag) {
return; // no action, if this is a key release
}
if (!vc_kbd_led(kbd, K_NUMLOCK)) {
// unprintable action
return;
}
put_queue(kbd, pad_chars[value]);
}
static void k_shift(SDL_EVDEV_keyboard_state *kbd, unsigned char value, char up_flag)
{
int old_state = kbd->shift_state;
if (kbd->rep) {
return;
}
/*
* Mimic typewriter:
* a CapsShift key acts like Shift but undoes CapsLock
*/
if (value == KVAL(K_CAPSSHIFT)) {
value = KVAL(K_SHIFT);
if (!up_flag) {
clr_vc_kbd_led(kbd, K_CAPSLOCK);
}
}
if (up_flag) {
/*
* handle the case that two shift or control
* keys are depressed simultaneously
*/
if (kbd->shift_down[value]) {
kbd->shift_down[value]--;
}
} else {
kbd->shift_down[value]++;
}
if (kbd->shift_down[value]) {
kbd->shift_state |= (1 << value);
} else {
kbd->shift_state &= ~(1 << value);
}
// kludge
if (up_flag && kbd->shift_state != old_state && kbd->npadch != -1) {
put_utf8(kbd, kbd->npadch);
kbd->npadch = -1;
}
}
static void k_meta(SDL_EVDEV_keyboard_state *kbd, unsigned char value, char up_flag)
{
}
static void k_ascii(SDL_EVDEV_keyboard_state *kbd, unsigned char value, char up_flag)
{
int base;
if (up_flag) {
return;
}
if (value < 10) {
// decimal input of code, while Alt depressed
base = 10;
} else {
// hexadecimal input of code, while AltGr depressed
value -= 10;
base = 16;
}
if (kbd->npadch == -1) {
kbd->npadch = value;
} else {
kbd->npadch = kbd->npadch * base + value;
}
}
static void k_lock(SDL_EVDEV_keyboard_state *kbd, unsigned char value, char up_flag)
{
if (up_flag || kbd->rep) {
return;
}
chg_vc_kbd_lock(kbd, value);
}
static void k_slock(SDL_EVDEV_keyboard_state *kbd, unsigned char value, char up_flag)
{
k_shift(kbd, value, up_flag);
if (up_flag || kbd->rep) {
return;
}
chg_vc_kbd_slock(kbd, value);
// try to make Alt, oops, AltGr and such work
if (!kbd->key_maps[kbd->lockstate ^ kbd->slockstate]) {
kbd->slockstate = 0;
chg_vc_kbd_slock(kbd, value);
}
}
static void k_brl(SDL_EVDEV_keyboard_state *kbd, unsigned char value, char up_flag)
{
}
void SDL_EVDEV_kbd_keycode(SDL_EVDEV_keyboard_state *state, unsigned int keycode, int down)
{
unsigned char shift_final;
unsigned char type;
unsigned short *key_map;
unsigned short keysym;
if (!state) {
return;
}
state->rep = (down == 2);
shift_final = (state->shift_state | state->slockstate) ^ state->lockstate;
key_map = state->key_maps[shift_final];
if (!key_map) {
// Unsupported shift state (e.g. ctrl = 4, alt = 8), just reset to the default state
state->shift_state = 0;
state->slockstate = 0;
state->lockstate = 0;
return;
}
if (keycode < NR_KEYS) {
if (state->console_fd < 0) {
keysym = key_map[keycode];
} else {
struct kbentry kbe;
kbe.kb_table = shift_final;
kbe.kb_index = keycode;
if (ioctl(state->console_fd, KDGKBENT, &kbe) == 0)
keysym = (kbe.kb_value ^ 0xf000);
else
return;
}
} else {
return;
}
type = KTYP(keysym);
if (type < 0xf0) {
if (down) {
put_utf8(state, keysym);
}
} else {
type -= 0xf0;
// if type is KT_LETTER then it can be affected by Caps Lock
if (type == KT_LETTER) {
type = KT_LATIN;
if (vc_kbd_led(state, K_CAPSLOCK)) {
shift_final = shift_final ^ (1 << KG_SHIFT);
key_map = state->key_maps[shift_final];
if (key_map) {
if (state->console_fd < 0) {
keysym = key_map[keycode];
} else {
struct kbentry kbe;
kbe.kb_table = shift_final;
kbe.kb_index = keycode;
if (ioctl(state->console_fd, KDGKBENT, &kbe) == 0)
keysym = (kbe.kb_value ^ 0xf000);
}
}
}
}
(*k_handler[type])(state, keysym & 0xff, !down);
if (type != KT_SLOCK) {
state->slockstate = 0;
}
}
if (state->text_len > 0) {
state->text[state->text_len] = '\0';
SDL_SendKeyboardText(state->text);
state->text_len = 0;
}
}
#elif !defined(SDL_INPUT_FBSDKBIO) // !SDL_INPUT_LINUXKD
SDL_EVDEV_keyboard_state *SDL_EVDEV_kbd_init(void)
{
return NULL;
}
void SDL_EVDEV_kbd_set_muted(SDL_EVDEV_keyboard_state *state, bool muted)
{
}
void SDL_EVDEV_kbd_set_vt_switch_callbacks(SDL_EVDEV_keyboard_state *state, void (*release_callback)(void*), void *release_callback_data, void (*acquire_callback)(void*), void *acquire_callback_data)
{
}
void SDL_EVDEV_kbd_update(SDL_EVDEV_keyboard_state *state)
{
}
void SDL_EVDEV_kbd_keycode(SDL_EVDEV_keyboard_state *state, unsigned int keycode, int down)
{
}
void SDL_EVDEV_kbd_quit(SDL_EVDEV_keyboard_state *state)
{
}
#endif // SDL_INPUT_LINUXKD

View file

@ -0,0 +1,35 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#ifndef SDL_evdev_kbd_h_
#define SDL_evdev_kbd_h_
struct SDL_EVDEV_keyboard_state;
typedef struct SDL_EVDEV_keyboard_state SDL_EVDEV_keyboard_state;
extern SDL_EVDEV_keyboard_state *SDL_EVDEV_kbd_init(void);
extern void SDL_EVDEV_kbd_set_muted(SDL_EVDEV_keyboard_state *state, bool muted);
extern void SDL_EVDEV_kbd_set_vt_switch_callbacks(SDL_EVDEV_keyboard_state *state, void (*release_callback)(void*), void *release_callback_data, void (*acquire_callback)(void*), void *acquire_callback_data);
extern void SDL_EVDEV_kbd_update(SDL_EVDEV_keyboard_state *state);
extern void SDL_EVDEV_kbd_keycode(SDL_EVDEV_keyboard_state *state, unsigned int keycode, int down);
extern void SDL_EVDEV_kbd_quit(SDL_EVDEV_keyboard_state *state);
#endif // SDL_evdev_kbd_h_

View file

@ -0,0 +1,282 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
static struct kbdiacrs default_accents = {
68,
{
{ 0x60, 0x41, 0xc0 },
{ 0x60, 0x61, 0xe0 },
{ 0x27, 0x41, 0xc1 },
{ 0x27, 0x61, 0xe1 },
{ 0x5e, 0x41, 0xc2 },
{ 0x5e, 0x61, 0xe2 },
{ 0x7e, 0x41, 0xc3 },
{ 0x7e, 0x61, 0xe3 },
{ 0x22, 0x41, 0xc4 },
{ 0x22, 0x61, 0xe4 },
{ 0x4f, 0x41, 0xc5 },
{ 0x6f, 0x61, 0xe5 },
{ 0x30, 0x41, 0xc5 },
{ 0x30, 0x61, 0xe5 },
{ 0x41, 0x41, 0xc5 },
{ 0x61, 0x61, 0xe5 },
{ 0x41, 0x45, 0xc6 },
{ 0x61, 0x65, 0xe6 },
{ 0x2c, 0x43, 0xc7 },
{ 0x2c, 0x63, 0xe7 },
{ 0x60, 0x45, 0xc8 },
{ 0x60, 0x65, 0xe8 },
{ 0x27, 0x45, 0xc9 },
{ 0x27, 0x65, 0xe9 },
{ 0x5e, 0x45, 0xca },
{ 0x5e, 0x65, 0xea },
{ 0x22, 0x45, 0xcb },
{ 0x22, 0x65, 0xeb },
{ 0x60, 0x49, 0xcc },
{ 0x60, 0x69, 0xec },
{ 0x27, 0x49, 0xcd },
{ 0x27, 0x69, 0xed },
{ 0x5e, 0x49, 0xce },
{ 0x5e, 0x69, 0xee },
{ 0x22, 0x49, 0xcf },
{ 0x22, 0x69, 0xef },
{ 0x2d, 0x44, 0xd0 },
{ 0x2d, 0x64, 0xf0 },
{ 0x7e, 0x4e, 0xd1 },
{ 0x7e, 0x6e, 0xf1 },
{ 0x60, 0x4f, 0xd2 },
{ 0x60, 0x6f, 0xf2 },
{ 0x27, 0x4f, 0xd3 },
{ 0x27, 0x6f, 0xf3 },
{ 0x5e, 0x4f, 0xd4 },
{ 0x5e, 0x6f, 0xf4 },
{ 0x7e, 0x4f, 0xd5 },
{ 0x7e, 0x6f, 0xf5 },
{ 0x22, 0x4f, 0xd6 },
{ 0x22, 0x6f, 0xf6 },
{ 0x2f, 0x4f, 0xd8 },
{ 0x2f, 0x6f, 0xf8 },
{ 0x60, 0x55, 0xd9 },
{ 0x60, 0x75, 0xf9 },
{ 0x27, 0x55, 0xda },
{ 0x27, 0x75, 0xfa },
{ 0x5e, 0x55, 0xdb },
{ 0x5e, 0x75, 0xfb },
{ 0x22, 0x55, 0xdc },
{ 0x22, 0x75, 0xfc },
{ 0x27, 0x59, 0xdd },
{ 0x27, 0x79, 0xfd },
{ 0x54, 0x48, 0xde },
{ 0x74, 0x68, 0xfe },
{ 0x73, 0x73, 0xdf },
{ 0x22, 0x79, 0xff },
{ 0x73, 0x7a, 0xdf },
{ 0x69, 0x6a, 0xff },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
}
};

File diff suppressed because it is too large Load diff

414
thirdparty/sdl/core/linux/SDL_fcitx.c vendored Normal file
View file

@ -0,0 +1,414 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#include <unistd.h>
#include "SDL_fcitx.h"
//#include "../../video/SDL_sysvideo.h"
#include "SDL_dbus.h"
#ifdef SDL_VIDEO_DRIVER_X11
#include "../../video/x11/SDL_x11video.h"
#endif
#define FCITX_DBUS_SERVICE "org.freedesktop.portal.Fcitx"
#define FCITX_IM_DBUS_PATH "/org/freedesktop/portal/inputmethod"
#define FCITX_IM_DBUS_INTERFACE "org.fcitx.Fcitx.InputMethod1"
#define FCITX_IC_DBUS_INTERFACE "org.fcitx.Fcitx.InputContext1"
#define DBUS_TIMEOUT 500
typedef struct FcitxClient
{
SDL_DBusContext *dbus;
char *ic_path;
int id;
SDL_Rect cursor_rect;
} FcitxClient;
static FcitxClient fcitx_client;
static char *GetAppName(void)
{
#if defined(SDL_PLATFORM_LINUX) || defined(SDL_PLATFORM_FREEBSD)
char *spot;
char procfile[1024];
char linkfile[1024];
int linksize;
#ifdef SDL_PLATFORM_LINUX
(void)SDL_snprintf(procfile, sizeof(procfile), "/proc/%d/exe", getpid());
#elif defined(SDL_PLATFORM_FREEBSD)
(void)SDL_snprintf(procfile, sizeof(procfile), "/proc/%d/file", getpid());
#endif
linksize = readlink(procfile, linkfile, sizeof(linkfile) - 1);
if (linksize > 0) {
linkfile[linksize] = '\0';
spot = SDL_strrchr(linkfile, '/');
if (spot) {
return SDL_strdup(spot + 1);
} else {
return SDL_strdup(linkfile);
}
}
#endif // SDL_PLATFORM_LINUX || SDL_PLATFORM_FREEBSD
return SDL_strdup("SDL_App");
}
static size_t Fcitx_GetPreeditString(SDL_DBusContext *dbus,
DBusMessage *msg,
char **ret,
Sint32 *start_pos,
Sint32 *end_pos)
{
char *text = NULL, *subtext;
size_t text_bytes = 0;
DBusMessageIter iter, array, sub;
Sint32 p_start_pos = -1;
Sint32 p_end_pos = -1;
dbus->message_iter_init(msg, &iter);
// Message type is a(si)i, we only need string part
if (dbus->message_iter_get_arg_type(&iter) == DBUS_TYPE_ARRAY) {
size_t pos = 0;
// First pass: calculate string length
dbus->message_iter_recurse(&iter, &array);
while (dbus->message_iter_get_arg_type(&array) == DBUS_TYPE_STRUCT) {
dbus->message_iter_recurse(&array, &sub);
subtext = NULL;
if (dbus->message_iter_get_arg_type(&sub) == DBUS_TYPE_STRING) {
dbus->message_iter_get_basic(&sub, &subtext);
if (subtext && *subtext) {
text_bytes += SDL_strlen(subtext);
}
}
dbus->message_iter_next(&sub);
if (dbus->message_iter_get_arg_type(&sub) == DBUS_TYPE_INT32 && p_end_pos == -1) {
// Type is a bit field defined as follows:
// bit 3: Underline, bit 4: HighLight, bit 5: DontCommit,
// bit 6: Bold, bit 7: Strike, bit 8: Italic
Sint32 type;
dbus->message_iter_get_basic(&sub, &type);
// We only consider highlight
if (type & (1 << 4)) {
if (p_start_pos == -1) {
p_start_pos = pos;
}
} else if (p_start_pos != -1 && p_end_pos == -1) {
p_end_pos = pos;
}
}
dbus->message_iter_next(&array);
if (subtext && *subtext) {
pos += SDL_utf8strlen(subtext);
}
}
if (p_start_pos != -1 && p_end_pos == -1) {
p_end_pos = pos;
}
if (text_bytes) {
text = SDL_malloc(text_bytes + 1);
}
if (text) {
char *pivot = text;
// Second pass: join all the sub string
dbus->message_iter_recurse(&iter, &array);
while (dbus->message_iter_get_arg_type(&array) == DBUS_TYPE_STRUCT) {
dbus->message_iter_recurse(&array, &sub);
if (dbus->message_iter_get_arg_type(&sub) == DBUS_TYPE_STRING) {
dbus->message_iter_get_basic(&sub, &subtext);
if (subtext && *subtext) {
size_t length = SDL_strlen(subtext);
SDL_strlcpy(pivot, subtext, length + 1);
pivot += length;
}
}
dbus->message_iter_next(&array);
}
} else {
text_bytes = 0;
}
}
*ret = text;
*start_pos = p_start_pos;
*end_pos = p_end_pos;
return text_bytes;
}
static Sint32 Fcitx_GetPreeditCursorByte(SDL_DBusContext *dbus, DBusMessage *msg)
{
Sint32 byte = -1;
DBusMessageIter iter;
dbus->message_iter_init(msg, &iter);
dbus->message_iter_next(&iter);
if (dbus->message_iter_get_arg_type(&iter) != DBUS_TYPE_INT32) {
return -1;
}
dbus->message_iter_get_basic(&iter, &byte);
return byte;
}
static DBusHandlerResult DBus_MessageFilter(DBusConnection *conn, DBusMessage *msg, void *data)
{
SDL_DBusContext *dbus = (SDL_DBusContext *)data;
if (dbus->message_is_signal(msg, FCITX_IC_DBUS_INTERFACE, "CommitString")) {
DBusMessageIter iter;
const char *text = NULL;
dbus->message_iter_init(msg, &iter);
dbus->message_iter_get_basic(&iter, &text);
//SDL_SendKeyboardText(text);
return DBUS_HANDLER_RESULT_HANDLED;
}
if (dbus->message_is_signal(msg, FCITX_IC_DBUS_INTERFACE, "UpdateFormattedPreedit")) {
char *text = NULL;
Sint32 start_pos, end_pos;
size_t text_bytes = Fcitx_GetPreeditString(dbus, msg, &text, &start_pos, &end_pos);
if (text_bytes) {
if (start_pos == -1) {
Sint32 byte_pos = Fcitx_GetPreeditCursorByte(dbus, msg);
start_pos = byte_pos >= 0 ? SDL_utf8strnlen(text, byte_pos) : -1;
}
//SDL_SendEditingText(text, start_pos, end_pos >= 0 ? end_pos - start_pos : -1);
SDL_free(text);
} else {
//SDL_SendEditingText("", 0, 0);
}
//SDL_Fcitx_UpdateTextInputArea(SDL_GetKeyboardFocus());
return DBUS_HANDLER_RESULT_HANDLED;
}
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}
static void FcitxClientICCallMethod(FcitxClient *client, const char *method)
{
if (!client->ic_path) {
return;
}
SDL_DBus_CallVoidMethod(FCITX_DBUS_SERVICE, client->ic_path, FCITX_IC_DBUS_INTERFACE, method, DBUS_TYPE_INVALID);
}
static void SDLCALL Fcitx_SetCapabilities(void *data,
const char *name,
const char *old_val,
const char *hint)
{
FcitxClient *client = (FcitxClient *)data;
Uint64 caps = 0;
if (!client->ic_path) {
return;
}
if (hint && SDL_strstr(hint, "composition")) {
caps |= (1 << 1); // Preedit Flag
caps |= (1 << 4); // Formatted Preedit Flag
}
if (hint && SDL_strstr(hint, "candidates")) {
// FIXME, turn off native candidate rendering
}
SDL_DBus_CallVoidMethod(FCITX_DBUS_SERVICE, client->ic_path, FCITX_IC_DBUS_INTERFACE, "SetCapability", DBUS_TYPE_UINT64, &caps, DBUS_TYPE_INVALID);
}
static bool FcitxCreateInputContext(SDL_DBusContext *dbus, const char *appname, char **ic_path)
{
const char *program = "program";
bool result = false;
if (dbus && dbus->session_conn) {
DBusMessage *msg = dbus->message_new_method_call(FCITX_DBUS_SERVICE, FCITX_IM_DBUS_PATH, FCITX_IM_DBUS_INTERFACE, "CreateInputContext");
if (msg) {
DBusMessage *reply = NULL;
DBusMessageIter args, array, sub;
dbus->message_iter_init_append(msg, &args);
dbus->message_iter_open_container(&args, DBUS_TYPE_ARRAY, "(ss)", &array);
dbus->message_iter_open_container(&array, DBUS_TYPE_STRUCT, 0, &sub);
dbus->message_iter_append_basic(&sub, DBUS_TYPE_STRING, &program);
dbus->message_iter_append_basic(&sub, DBUS_TYPE_STRING, &appname);
dbus->message_iter_close_container(&array, &sub);
dbus->message_iter_close_container(&args, &array);
reply = dbus->connection_send_with_reply_and_block(dbus->session_conn, msg, 300, NULL);
if (reply) {
if (dbus->message_get_args(reply, NULL, DBUS_TYPE_OBJECT_PATH, ic_path, DBUS_TYPE_INVALID)) {
result = true;
}
dbus->message_unref(reply);
}
dbus->message_unref(msg);
}
}
return result;
}
static bool FcitxClientCreateIC(FcitxClient *client)
{
char *appname = GetAppName();
char *ic_path = NULL;
SDL_DBusContext *dbus = client->dbus;
// SDL_DBus_CallMethod cannot handle a(ss) type, call dbus function directly
if (!FcitxCreateInputContext(dbus, appname, &ic_path)) {
ic_path = NULL; // just in case.
}
SDL_free(appname);
if (ic_path) {
SDL_free(client->ic_path);
client->ic_path = SDL_strdup(ic_path);
dbus->bus_add_match(dbus->session_conn,
"type='signal', interface='org.fcitx.Fcitx.InputContext1'",
NULL);
dbus->connection_add_filter(dbus->session_conn,
&DBus_MessageFilter, dbus,
NULL);
dbus->connection_flush(dbus->session_conn);
SDL_AddHintCallback(SDL_HINT_IME_IMPLEMENTED_UI, Fcitx_SetCapabilities, client);
return true;
}
return false;
}
static Uint32 Fcitx_ModState(void)
{
Uint32 fcitx_mods = 0;
SDL_Keymod sdl_mods = SDL_GetModState();
if (sdl_mods & SDL_KMOD_SHIFT) {
fcitx_mods |= (1 << 0);
}
if (sdl_mods & SDL_KMOD_CAPS) {
fcitx_mods |= (1 << 1);
}
if (sdl_mods & SDL_KMOD_CTRL) {
fcitx_mods |= (1 << 2);
}
if (sdl_mods & SDL_KMOD_ALT) {
fcitx_mods |= (1 << 3);
}
if (sdl_mods & SDL_KMOD_NUM) {
fcitx_mods |= (1 << 4);
}
if (sdl_mods & SDL_KMOD_MODE) {
fcitx_mods |= (1 << 7);
}
if (sdl_mods & SDL_KMOD_LGUI) {
fcitx_mods |= (1 << 6);
}
if (sdl_mods & SDL_KMOD_RGUI) {
fcitx_mods |= (1 << 28);
}
return fcitx_mods;
}
bool SDL_Fcitx_Init(void)
{
fcitx_client.dbus = SDL_DBus_GetContext();
fcitx_client.cursor_rect.x = -1;
fcitx_client.cursor_rect.y = -1;
fcitx_client.cursor_rect.w = 0;
fcitx_client.cursor_rect.h = 0;
return FcitxClientCreateIC(&fcitx_client);
}
void SDL_Fcitx_Quit(void)
{
FcitxClientICCallMethod(&fcitx_client, "DestroyIC");
if (fcitx_client.ic_path) {
SDL_free(fcitx_client.ic_path);
fcitx_client.ic_path = NULL;
}
}
void SDL_Fcitx_SetFocus(bool focused)
{
if (focused) {
FcitxClientICCallMethod(&fcitx_client, "FocusIn");
} else {
FcitxClientICCallMethod(&fcitx_client, "FocusOut");
}
}
void SDL_Fcitx_Reset(void)
{
FcitxClientICCallMethod(&fcitx_client, "Reset");
}
bool SDL_Fcitx_ProcessKeyEvent(Uint32 keysym, Uint32 keycode, bool down)
{
Uint32 mod_state = Fcitx_ModState();
Uint32 handled = false;
Uint32 is_release = !down;
Uint32 event_time = 0;
if (!fcitx_client.ic_path) {
return false;
}
if (SDL_DBus_CallMethod(FCITX_DBUS_SERVICE, fcitx_client.ic_path, FCITX_IC_DBUS_INTERFACE, "ProcessKeyEvent",
DBUS_TYPE_UINT32, &keysym, DBUS_TYPE_UINT32, &keycode, DBUS_TYPE_UINT32, &mod_state, DBUS_TYPE_BOOLEAN, &is_release, DBUS_TYPE_UINT32, &event_time, DBUS_TYPE_INVALID,
DBUS_TYPE_BOOLEAN, &handled, DBUS_TYPE_INVALID)) {
if (handled) {
//SDL_Fcitx_UpdateTextInputArea(SDL_GetKeyboardFocus());
return true;
}
}
return false;
}
void SDL_Fcitx_PumpEvents(void)
{
SDL_DBusContext *dbus = fcitx_client.dbus;
DBusConnection *conn = dbus->session_conn;
dbus->connection_read_write(conn, 0);
while (dbus->connection_dispatch(conn) == DBUS_DISPATCH_DATA_REMAINS) {
// Do nothing, actual work happens in DBus_MessageFilter
}
}

35
thirdparty/sdl/core/linux/SDL_fcitx.h vendored Normal file
View file

@ -0,0 +1,35 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#ifndef SDL_fcitx_h_
#define SDL_fcitx_h_
#include "SDL_internal.h"
extern bool SDL_Fcitx_Init(void);
extern void SDL_Fcitx_Quit(void);
extern void SDL_Fcitx_SetFocus(bool focused);
extern void SDL_Fcitx_Reset(void);
extern bool SDL_Fcitx_ProcessKeyEvent(Uint32 keysym, Uint32 keycode, bool down);
extern void SDL_Fcitx_UpdateTextInputArea(SDL_Window *window);
extern void SDL_Fcitx_PumpEvents(void);
#endif // SDL_fcitx_h_

694
thirdparty/sdl/core/linux/SDL_ibus.c vendored Normal file
View file

@ -0,0 +1,694 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#ifdef HAVE_IBUS_IBUS_H
#include "SDL_ibus.h"
#include "SDL_dbus.h"
#ifdef SDL_USE_LIBDBUS
//#include "../../video/SDL_sysvideo.h"
#include "../../events/SDL_keyboard_c.h"
#ifdef SDL_VIDEO_DRIVER_X11
#include "../../video/x11/SDL_x11video.h"
#endif
#include <sys/inotify.h>
#include <unistd.h>
#include <fcntl.h>
static const char IBUS_PATH[] = "/org/freedesktop/IBus";
static const char IBUS_SERVICE[] = "org.freedesktop.IBus";
static const char IBUS_INTERFACE[] = "org.freedesktop.IBus";
static const char IBUS_INPUT_INTERFACE[] = "org.freedesktop.IBus.InputContext";
static const char IBUS_PORTAL_SERVICE[] = "org.freedesktop.portal.IBus";
static const char IBUS_PORTAL_INTERFACE[] = "org.freedesktop.IBus.Portal";
static const char IBUS_PORTAL_INPUT_INTERFACE[] = "org.freedesktop.IBus.InputContext";
static const char *ibus_service = NULL;
static const char *ibus_interface = NULL;
static const char *ibus_input_interface = NULL;
static char *input_ctx_path = NULL;
static SDL_Rect ibus_cursor_rect = { 0, 0, 0, 0 };
static DBusConnection *ibus_conn = NULL;
static bool ibus_is_portal_interface = false;
static char *ibus_addr_file = NULL;
static int inotify_fd = -1, inotify_wd = -1;
static Uint32 IBus_ModState(void)
{
Uint32 ibus_mods = 0;
SDL_Keymod sdl_mods = SDL_GetModState();
// Not sure about MOD3, MOD4 and HYPER mappings
if (sdl_mods & SDL_KMOD_LSHIFT) {
ibus_mods |= IBUS_SHIFT_MASK;
}
if (sdl_mods & SDL_KMOD_CAPS) {
ibus_mods |= IBUS_LOCK_MASK;
}
if (sdl_mods & SDL_KMOD_LCTRL) {
ibus_mods |= IBUS_CONTROL_MASK;
}
if (sdl_mods & SDL_KMOD_LALT) {
ibus_mods |= IBUS_MOD1_MASK;
}
if (sdl_mods & SDL_KMOD_NUM) {
ibus_mods |= IBUS_MOD2_MASK;
}
if (sdl_mods & SDL_KMOD_MODE) {
ibus_mods |= IBUS_MOD5_MASK;
}
if (sdl_mods & SDL_KMOD_LGUI) {
ibus_mods |= IBUS_SUPER_MASK;
}
if (sdl_mods & SDL_KMOD_RGUI) {
ibus_mods |= IBUS_META_MASK;
}
return ibus_mods;
}
static bool IBus_EnterVariant(DBusConnection *conn, DBusMessageIter *iter, SDL_DBusContext *dbus,
DBusMessageIter *inside, const char *struct_id, size_t id_size)
{
DBusMessageIter sub;
if (dbus->message_iter_get_arg_type(iter) != DBUS_TYPE_VARIANT) {
return false;
}
dbus->message_iter_recurse(iter, &sub);
if (dbus->message_iter_get_arg_type(&sub) != DBUS_TYPE_STRUCT) {
return false;
}
dbus->message_iter_recurse(&sub, inside);
if (dbus->message_iter_get_arg_type(inside) != DBUS_TYPE_STRING) {
return false;
}
dbus->message_iter_get_basic(inside, &struct_id);
if (!struct_id || SDL_strncmp(struct_id, struct_id, id_size) != 0) {
return false;
}
return true;
}
static bool IBus_GetDecorationPosition(DBusConnection *conn, DBusMessageIter *iter, SDL_DBusContext *dbus,
Uint32 *start_pos, Uint32 *end_pos)
{
DBusMessageIter sub1, sub2, array;
if (!IBus_EnterVariant(conn, iter, dbus, &sub1, "IBusText", sizeof("IBusText"))) {
return false;
}
dbus->message_iter_next(&sub1);
dbus->message_iter_next(&sub1);
dbus->message_iter_next(&sub1);
if (!IBus_EnterVariant(conn, &sub1, dbus, &sub2, "IBusAttrList", sizeof("IBusAttrList"))) {
return false;
}
dbus->message_iter_next(&sub2);
dbus->message_iter_next(&sub2);
if (dbus->message_iter_get_arg_type(&sub2) != DBUS_TYPE_ARRAY) {
return false;
}
dbus->message_iter_recurse(&sub2, &array);
while (dbus->message_iter_get_arg_type(&array) == DBUS_TYPE_VARIANT) {
DBusMessageIter sub;
if (IBus_EnterVariant(conn, &array, dbus, &sub, "IBusAttribute", sizeof("IBusAttribute"))) {
Uint32 type;
dbus->message_iter_next(&sub);
dbus->message_iter_next(&sub);
// From here on, the structure looks like this:
// Uint32 type: 1=underline, 2=foreground, 3=background
// Uint32 value: for underline it's 0=NONE, 1=SINGLE, 2=DOUBLE,
// 3=LOW, 4=ERROR
// for foreground and background it's a color
// Uint32 start_index: starting position for the style (utf8-char)
// Uint32 end_index: end position for the style (utf8-char)
dbus->message_iter_get_basic(&sub, &type);
// We only use the background type to determine the selection
if (type == 3) {
Uint32 start = -1;
dbus->message_iter_next(&sub);
dbus->message_iter_next(&sub);
if (dbus->message_iter_get_arg_type(&sub) == DBUS_TYPE_UINT32) {
dbus->message_iter_get_basic(&sub, &start);
dbus->message_iter_next(&sub);
if (dbus->message_iter_get_arg_type(&sub) == DBUS_TYPE_UINT32) {
dbus->message_iter_get_basic(&sub, end_pos);
*start_pos = start;
return true;
}
}
}
}
dbus->message_iter_next(&array);
}
return false;
}
static const char *IBus_GetVariantText(DBusConnection *conn, DBusMessageIter *iter, SDL_DBusContext *dbus)
{
// The text we need is nested weirdly, use dbus-monitor to see the structure better
const char *text = NULL;
DBusMessageIter sub;
if (!IBus_EnterVariant(conn, iter, dbus, &sub, "IBusText", sizeof("IBusText"))) {
return NULL;
}
dbus->message_iter_next(&sub);
dbus->message_iter_next(&sub);
if (dbus->message_iter_get_arg_type(&sub) != DBUS_TYPE_STRING) {
return NULL;
}
dbus->message_iter_get_basic(&sub, &text);
return text;
}
static bool IBus_GetVariantCursorPos(DBusConnection *conn, DBusMessageIter *iter, SDL_DBusContext *dbus,
Uint32 *pos)
{
dbus->message_iter_next(iter);
if (dbus->message_iter_get_arg_type(iter) != DBUS_TYPE_UINT32) {
return false;
}
dbus->message_iter_get_basic(iter, pos);
return true;
}
static DBusHandlerResult IBus_MessageHandler(DBusConnection *conn, DBusMessage *msg, void *user_data)
{
SDL_DBusContext *dbus = (SDL_DBusContext *)user_data;
if (dbus->message_is_signal(msg, ibus_input_interface, "CommitText")) {
DBusMessageIter iter;
const char *text;
dbus->message_iter_init(msg, &iter);
text = IBus_GetVariantText(conn, &iter, dbus);
SDL_SendKeyboardText(text);
return DBUS_HANDLER_RESULT_HANDLED;
}
if (dbus->message_is_signal(msg, ibus_input_interface, "UpdatePreeditText")) {
DBusMessageIter iter;
const char *text;
dbus->message_iter_init(msg, &iter);
text = IBus_GetVariantText(conn, &iter, dbus);
if (text) {
Uint32 pos, start_pos, end_pos;
bool has_pos = false;
bool has_dec_pos = false;
dbus->message_iter_init(msg, &iter);
has_dec_pos = IBus_GetDecorationPosition(conn, &iter, dbus, &start_pos, &end_pos);
if (!has_dec_pos) {
dbus->message_iter_init(msg, &iter);
has_pos = IBus_GetVariantCursorPos(conn, &iter, dbus, &pos);
}
if (has_dec_pos) {
SDL_SendEditingText(text, start_pos, end_pos - start_pos);
} else if (has_pos) {
SDL_SendEditingText(text, pos, -1);
} else {
SDL_SendEditingText(text, -1, -1);
}
}
//SDL_IBus_UpdateTextInputArea(SDL_GetKeyboardFocus());
return DBUS_HANDLER_RESULT_HANDLED;
}
if (dbus->message_is_signal(msg, ibus_input_interface, "HidePreeditText")) {
SDL_SendEditingText("", 0, 0);
return DBUS_HANDLER_RESULT_HANDLED;
}
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}
static char *IBus_ReadAddressFromFile(const char *file_path)
{
char addr_buf[1024];
bool success = false;
FILE *addr_file;
addr_file = fopen(file_path, "r");
if (!addr_file) {
return NULL;
}
while (fgets(addr_buf, sizeof(addr_buf), addr_file)) {
if (SDL_strncmp(addr_buf, "IBUS_ADDRESS=", sizeof("IBUS_ADDRESS=") - 1) == 0) {
size_t sz = SDL_strlen(addr_buf);
if (addr_buf[sz - 1] == '\n') {
addr_buf[sz - 1] = 0;
}
if (addr_buf[sz - 2] == '\r') {
addr_buf[sz - 2] = 0;
}
success = true;
break;
}
}
(void)fclose(addr_file);
if (success) {
return SDL_strdup(addr_buf + (sizeof("IBUS_ADDRESS=") - 1));
} else {
return NULL;
}
}
static char *IBus_GetDBusAddressFilename(void)
{
SDL_DBusContext *dbus;
const char *disp_env;
char config_dir[PATH_MAX];
char *display = NULL;
const char *addr;
const char *conf_env;
char *key;
char file_path[PATH_MAX];
const char *host;
char *disp_num, *screen_num;
if (ibus_addr_file) {
return SDL_strdup(ibus_addr_file);
}
dbus = SDL_DBus_GetContext();
if (!dbus) {
return NULL;
}
// Use this environment variable if it exists.
addr = SDL_getenv("IBUS_ADDRESS");
if (addr && *addr) {
return SDL_strdup(addr);
}
/* Otherwise, we have to get the hostname, display, machine id, config dir
and look up the address from a filepath using all those bits, eek. */
disp_env = SDL_getenv("DISPLAY");
if (!disp_env || !*disp_env) {
display = SDL_strdup(":0.0");
} else {
display = SDL_strdup(disp_env);
}
host = display;
disp_num = SDL_strrchr(display, ':');
screen_num = SDL_strrchr(display, '.');
if (!disp_num) {
SDL_free(display);
return NULL;
}
*disp_num = 0;
disp_num++;
if (screen_num) {
*screen_num = 0;
}
if (!*host) {
const char *session = SDL_getenv("XDG_SESSION_TYPE");
if (session && SDL_strcmp(session, "wayland") == 0) {
host = "unix-wayland";
} else {
host = "unix";
}
}
SDL_memset(config_dir, 0, sizeof(config_dir));
conf_env = SDL_getenv("XDG_CONFIG_HOME");
if (conf_env && *conf_env) {
SDL_strlcpy(config_dir, conf_env, sizeof(config_dir));
} else {
const char *home_env = SDL_getenv("HOME");
if (!home_env || !*home_env) {
SDL_free(display);
return NULL;
}
(void)SDL_snprintf(config_dir, sizeof(config_dir), "%s/.config", home_env);
}
key = SDL_DBus_GetLocalMachineId();
if (!key) {
SDL_free(display);
return NULL;
}
SDL_memset(file_path, 0, sizeof(file_path));
(void)SDL_snprintf(file_path, sizeof(file_path), "%s/ibus/bus/%s-%s-%s",
config_dir, key, host, disp_num);
dbus->free(key);
SDL_free(display);
return SDL_strdup(file_path);
}
static bool IBus_CheckConnection(SDL_DBusContext *dbus);
static void SDLCALL IBus_SetCapabilities(void *data, const char *name, const char *old_val,
const char *hint)
{
SDL_DBusContext *dbus = SDL_DBus_GetContext();
if (IBus_CheckConnection(dbus)) {
Uint32 caps = IBUS_CAP_FOCUS;
if (hint && SDL_strstr(hint, "composition")) {
caps |= IBUS_CAP_PREEDIT_TEXT;
}
if (hint && SDL_strstr(hint, "candidates")) {
// FIXME, turn off native candidate rendering
}
SDL_DBus_CallVoidMethodOnConnection(ibus_conn, ibus_service, input_ctx_path, ibus_input_interface, "SetCapabilities",
DBUS_TYPE_UINT32, &caps, DBUS_TYPE_INVALID);
}
}
static bool IBus_SetupConnection(SDL_DBusContext *dbus, const char *addr)
{
const char *client_name = "SDL3_Application";
const char *path = NULL;
bool result = false;
DBusObjectPathVTable ibus_vtable;
SDL_zero(ibus_vtable);
ibus_vtable.message_function = &IBus_MessageHandler;
/* try the portal interface first. Modern systems have this in general,
and sandbox things like FlakPak and Snaps, etc, require it. */
ibus_is_portal_interface = true;
ibus_service = IBUS_PORTAL_SERVICE;
ibus_interface = IBUS_PORTAL_INTERFACE;
ibus_input_interface = IBUS_PORTAL_INPUT_INTERFACE;
ibus_conn = dbus->session_conn;
result = SDL_DBus_CallMethodOnConnection(ibus_conn, ibus_service, IBUS_PATH, ibus_interface, "CreateInputContext",
DBUS_TYPE_STRING, &client_name, DBUS_TYPE_INVALID,
DBUS_TYPE_OBJECT_PATH, &path, DBUS_TYPE_INVALID);
if (!result) {
ibus_is_portal_interface = false;
ibus_service = IBUS_SERVICE;
ibus_interface = IBUS_INTERFACE;
ibus_input_interface = IBUS_INPUT_INTERFACE;
ibus_conn = dbus->connection_open_private(addr, NULL);
if (!ibus_conn) {
return false; // oh well.
}
dbus->connection_flush(ibus_conn);
if (!dbus->bus_register(ibus_conn, NULL)) {
ibus_conn = NULL;
return false;
}
dbus->connection_flush(ibus_conn);
result = SDL_DBus_CallMethodOnConnection(ibus_conn, ibus_service, IBUS_PATH, ibus_interface, "CreateInputContext",
DBUS_TYPE_STRING, &client_name, DBUS_TYPE_INVALID,
DBUS_TYPE_OBJECT_PATH, &path, DBUS_TYPE_INVALID);
} else {
// re-using dbus->session_conn
dbus->connection_ref(ibus_conn);
}
if (result) {
char matchstr[128];
(void)SDL_snprintf(matchstr, sizeof(matchstr), "type='signal',interface='%s'", ibus_input_interface);
SDL_free(input_ctx_path);
input_ctx_path = SDL_strdup(path);
SDL_AddHintCallback(SDL_HINT_IME_IMPLEMENTED_UI, IBus_SetCapabilities, NULL);
dbus->bus_add_match(ibus_conn, matchstr, NULL);
dbus->connection_try_register_object_path(ibus_conn, input_ctx_path, &ibus_vtable, dbus, NULL);
dbus->connection_flush(ibus_conn);
}
return result;
}
static bool IBus_CheckConnection(SDL_DBusContext *dbus)
{
if (!dbus) {
return false;
}
if (ibus_conn && dbus->connection_get_is_connected(ibus_conn)) {
return true;
}
if (inotify_fd > 0 && inotify_wd > 0) {
char buf[1024];
ssize_t readsize = read(inotify_fd, buf, sizeof(buf));
if (readsize > 0) {
char *p;
bool file_updated = false;
for (p = buf; p < buf + readsize; /**/) {
struct inotify_event *event = (struct inotify_event *)p;
if (event->len > 0) {
char *addr_file_no_path = SDL_strrchr(ibus_addr_file, '/');
if (!addr_file_no_path) {
return false;
}
if (SDL_strcmp(addr_file_no_path + 1, event->name) == 0) {
file_updated = true;
break;
}
}
p += sizeof(struct inotify_event) + event->len;
}
if (file_updated) {
char *addr = IBus_ReadAddressFromFile(ibus_addr_file);
if (addr) {
bool result = IBus_SetupConnection(dbus, addr);
SDL_free(addr);
return result;
}
}
}
}
return false;
}
bool SDL_IBus_Init(void)
{
bool result = false;
SDL_DBusContext *dbus = SDL_DBus_GetContext();
if (dbus) {
char *addr_file = IBus_GetDBusAddressFilename();
char *addr;
char *addr_file_dir;
if (!addr_file) {
return false;
}
addr = IBus_ReadAddressFromFile(addr_file);
if (!addr) {
SDL_free(addr_file);
return false;
}
if (ibus_addr_file) {
SDL_free(ibus_addr_file);
}
ibus_addr_file = SDL_strdup(addr_file);
if (inotify_fd < 0) {
inotify_fd = inotify_init();
fcntl(inotify_fd, F_SETFL, O_NONBLOCK);
}
addr_file_dir = SDL_strrchr(addr_file, '/');
if (addr_file_dir) {
*addr_file_dir = 0;
}
inotify_wd = inotify_add_watch(inotify_fd, addr_file, IN_CREATE | IN_MODIFY);
SDL_free(addr_file);
result = IBus_SetupConnection(dbus, addr);
SDL_free(addr);
// don't use the addr_file if using the portal interface.
if (result && ibus_is_portal_interface) {
if (inotify_fd > 0) {
if (inotify_wd > 0) {
inotify_rm_watch(inotify_fd, inotify_wd);
inotify_wd = -1;
}
close(inotify_fd);
inotify_fd = -1;
}
}
}
return result;
}
void SDL_IBus_Quit(void)
{
SDL_DBusContext *dbus;
if (input_ctx_path) {
SDL_free(input_ctx_path);
input_ctx_path = NULL;
}
if (ibus_addr_file) {
SDL_free(ibus_addr_file);
ibus_addr_file = NULL;
}
dbus = SDL_DBus_GetContext();
// if using portal, ibus_conn == session_conn; don't release it here.
if (dbus && ibus_conn && !ibus_is_portal_interface) {
dbus->connection_close(ibus_conn);
dbus->connection_unref(ibus_conn);
}
ibus_conn = NULL;
ibus_service = NULL;
ibus_interface = NULL;
ibus_input_interface = NULL;
ibus_is_portal_interface = false;
if (inotify_fd > 0 && inotify_wd > 0) {
inotify_rm_watch(inotify_fd, inotify_wd);
inotify_wd = -1;
}
// !!! FIXME: should we close(inotify_fd) here?
SDL_RemoveHintCallback(SDL_HINT_IME_IMPLEMENTED_UI, IBus_SetCapabilities, NULL);
SDL_memset(&ibus_cursor_rect, 0, sizeof(ibus_cursor_rect));
}
static void IBus_SimpleMessage(const char *method)
{
SDL_DBusContext *dbus = SDL_DBus_GetContext();
if ((input_ctx_path) && (IBus_CheckConnection(dbus))) {
SDL_DBus_CallVoidMethodOnConnection(ibus_conn, ibus_service, input_ctx_path, ibus_input_interface, method, DBUS_TYPE_INVALID);
}
}
void SDL_IBus_SetFocus(bool focused)
{
const char *method = focused ? "FocusIn" : "FocusOut";
IBus_SimpleMessage(method);
}
void SDL_IBus_Reset(void)
{
IBus_SimpleMessage("Reset");
}
bool SDL_IBus_ProcessKeyEvent(Uint32 keysym, Uint32 keycode, bool down)
{
Uint32 result = 0;
SDL_DBusContext *dbus = SDL_DBus_GetContext();
if (IBus_CheckConnection(dbus)) {
Uint32 mods = IBus_ModState();
Uint32 ibus_keycode = keycode - 8;
if (!down) {
mods |= (1 << 30); // IBUS_RELEASE_MASK
}
if (!SDL_DBus_CallMethodOnConnection(ibus_conn, ibus_service, input_ctx_path, ibus_input_interface, "ProcessKeyEvent",
DBUS_TYPE_UINT32, &keysym, DBUS_TYPE_UINT32, &ibus_keycode, DBUS_TYPE_UINT32, &mods, DBUS_TYPE_INVALID,
DBUS_TYPE_BOOLEAN, &result, DBUS_TYPE_INVALID)) {
result = 0;
}
}
//SDL_IBus_UpdateTextInputArea(SDL_GetKeyboardFocus());
return (result != 0);
}
void SDL_IBus_PumpEvents(void)
{
SDL_DBusContext *dbus = SDL_DBus_GetContext();
if (IBus_CheckConnection(dbus)) {
dbus->connection_read_write(ibus_conn, 0);
while (dbus->connection_dispatch(ibus_conn) == DBUS_DISPATCH_DATA_REMAINS) {
// Do nothing, actual work happens in IBus_MessageHandler
}
}
}
#endif // SDL_USE_LIBDBUS
#endif

55
thirdparty/sdl/core/linux/SDL_ibus.h vendored Normal file
View file

@ -0,0 +1,55 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#ifndef SDL_ibus_h_
#define SDL_ibus_h_
#ifdef HAVE_IBUS_IBUS_H
#define SDL_USE_IBUS 1
#include <ibus.h>
extern bool SDL_IBus_Init(void);
extern void SDL_IBus_Quit(void);
// Lets the IBus server know about changes in window focus
extern void SDL_IBus_SetFocus(bool focused);
// Closes the candidate list and resets any text currently being edited
extern void SDL_IBus_Reset(void);
/* Sends a keypress event to IBus, returns true if IBus used this event to
update its candidate list or change input methods. PumpEvents should be
called some time after this, to receive the TextInput / TextEditing event back. */
extern bool SDL_IBus_ProcessKeyEvent(Uint32 keysym, Uint32 keycode, bool down);
/* Update the position of IBus' candidate list. If rect is NULL then this will
just reposition it relative to the focused window's new position. */
extern void SDL_IBus_UpdateTextInputArea(SDL_Window *window);
/* Checks DBus for new IBus events, and calls SDL_SendKeyboardText /
SDL_SendEditingText for each event it finds */
extern void SDL_IBus_PumpEvents(void);
#endif // HAVE_IBUS_IBUS_H
#endif // SDL_ibus_h_

150
thirdparty/sdl/core/linux/SDL_ime.c vendored Normal file
View file

@ -0,0 +1,150 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#include "SDL_ime.h"
#include "SDL_ibus.h"
#include "SDL_fcitx.h"
typedef bool (*SDL_IME_Init_t)(void);
typedef void (*SDL_IME_Quit_t)(void);
typedef void (*SDL_IME_SetFocus_t)(bool);
typedef void (*SDL_IME_Reset_t)(void);
typedef bool (*SDL_IME_ProcessKeyEvent_t)(Uint32, Uint32, bool down);
typedef void (*SDL_IME_UpdateTextInputArea_t)(SDL_Window *window);
typedef void (*SDL_IME_PumpEvents_t)(void);
static SDL_IME_Init_t SDL_IME_Init_Real = NULL;
static SDL_IME_Quit_t SDL_IME_Quit_Real = NULL;
static SDL_IME_SetFocus_t SDL_IME_SetFocus_Real = NULL;
static SDL_IME_Reset_t SDL_IME_Reset_Real = NULL;
static SDL_IME_ProcessKeyEvent_t SDL_IME_ProcessKeyEvent_Real = NULL;
static SDL_IME_UpdateTextInputArea_t SDL_IME_UpdateTextInputArea_Real = NULL;
static SDL_IME_PumpEvents_t SDL_IME_PumpEvents_Real = NULL;
static void InitIME(void)
{
static bool inited = false;
#ifdef HAVE_FCITX
const char *im_module = SDL_getenv("SDL_IM_MODULE");
const char *xmodifiers = SDL_getenv("XMODIFIERS");
#endif
if (inited == true) {
return;
}
inited = true;
// See if fcitx IME support is being requested
#ifdef HAVE_FCITX
if (!SDL_IME_Init_Real &&
((im_module && SDL_strcmp(im_module, "fcitx") == 0) ||
(!im_module && xmodifiers && SDL_strstr(xmodifiers, "@im=fcitx") != NULL))) {
SDL_IME_Init_Real = SDL_Fcitx_Init;
SDL_IME_Quit_Real = SDL_Fcitx_Quit;
SDL_IME_SetFocus_Real = SDL_Fcitx_SetFocus;
SDL_IME_Reset_Real = SDL_Fcitx_Reset;
SDL_IME_ProcessKeyEvent_Real = SDL_Fcitx_ProcessKeyEvent;
SDL_IME_UpdateTextInputArea_Real = SDL_Fcitx_UpdateTextInputArea;
SDL_IME_PumpEvents_Real = SDL_Fcitx_PumpEvents;
}
#endif // HAVE_FCITX
// default to IBus
#ifdef HAVE_IBUS_IBUS_H
if (!SDL_IME_Init_Real) {
SDL_IME_Init_Real = SDL_IBus_Init;
SDL_IME_Quit_Real = SDL_IBus_Quit;
SDL_IME_SetFocus_Real = SDL_IBus_SetFocus;
SDL_IME_Reset_Real = SDL_IBus_Reset;
SDL_IME_ProcessKeyEvent_Real = SDL_IBus_ProcessKeyEvent;
SDL_IME_UpdateTextInputArea_Real = SDL_IBus_UpdateTextInputArea;
SDL_IME_PumpEvents_Real = SDL_IBus_PumpEvents;
}
#endif // HAVE_IBUS_IBUS_H
}
bool SDL_IME_Init(void)
{
InitIME();
if (SDL_IME_Init_Real) {
if (SDL_IME_Init_Real()) {
return true;
}
// uhoh, the IME implementation's init failed! Disable IME support.
SDL_IME_Init_Real = NULL;
SDL_IME_Quit_Real = NULL;
SDL_IME_SetFocus_Real = NULL;
SDL_IME_Reset_Real = NULL;
SDL_IME_ProcessKeyEvent_Real = NULL;
SDL_IME_UpdateTextInputArea_Real = NULL;
SDL_IME_PumpEvents_Real = NULL;
}
return false;
}
void SDL_IME_Quit(void)
{
if (SDL_IME_Quit_Real) {
SDL_IME_Quit_Real();
}
}
void SDL_IME_SetFocus(bool focused)
{
if (SDL_IME_SetFocus_Real) {
SDL_IME_SetFocus_Real(focused);
}
}
void SDL_IME_Reset(void)
{
if (SDL_IME_Reset_Real) {
SDL_IME_Reset_Real();
}
}
bool SDL_IME_ProcessKeyEvent(Uint32 keysym, Uint32 keycode, bool down)
{
if (SDL_IME_ProcessKeyEvent_Real) {
return SDL_IME_ProcessKeyEvent_Real(keysym, keycode, down);
}
return false;
}
void SDL_IME_UpdateTextInputArea(SDL_Window *window)
{
if (SDL_IME_UpdateTextInputArea_Real) {
SDL_IME_UpdateTextInputArea_Real(window);
}
}
void SDL_IME_PumpEvents(void)
{
if (SDL_IME_PumpEvents_Real) {
SDL_IME_PumpEvents_Real();
}
}

35
thirdparty/sdl/core/linux/SDL_ime.h vendored Normal file
View file

@ -0,0 +1,35 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#ifndef SDL_ime_h_
#define SDL_ime_h_
#include "SDL_internal.h"
extern bool SDL_IME_Init(void);
extern void SDL_IME_Quit(void);
extern void SDL_IME_SetFocus(bool focused);
extern void SDL_IME_Reset(void);
extern bool SDL_IME_ProcessKeyEvent(Uint32 keysym, Uint32 keycode, bool down);
extern void SDL_IME_UpdateTextInputArea(SDL_Window *window);
extern void SDL_IME_PumpEvents(void);
#endif // SDL_ime_h_

View file

@ -0,0 +1,156 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#include "SDL_dbus.h"
#include "SDL_system_theme.h"
//#include "../../video/SDL_sysvideo.h"
#include <unistd.h>
#define PORTAL_DESTINATION "org.freedesktop.portal.Desktop"
#define PORTAL_PATH "/org/freedesktop/portal/desktop"
#define PORTAL_INTERFACE "org.freedesktop.portal.Settings"
#define PORTAL_METHOD "Read"
#define SIGNAL_INTERFACE "org.freedesktop.portal.Settings"
#define SIGNAL_NAMESPACE "org.freedesktop.appearance"
#define SIGNAL_NAME "SettingChanged"
#define SIGNAL_KEY "color-scheme"
typedef struct SystemThemeData
{
SDL_DBusContext *dbus;
SDL_SystemTheme theme;
} SystemThemeData;
static SystemThemeData system_theme_data;
static bool DBus_ExtractThemeVariant(DBusMessageIter *iter, SDL_SystemTheme *theme) {
SDL_DBusContext *dbus = system_theme_data.dbus;
Uint32 color_scheme;
DBusMessageIter variant_iter;
if (dbus->message_iter_get_arg_type(iter) != DBUS_TYPE_VARIANT)
return false;
dbus->message_iter_recurse(iter, &variant_iter);
if (dbus->message_iter_get_arg_type(&variant_iter) != DBUS_TYPE_UINT32)
return false;
dbus->message_iter_get_basic(&variant_iter, &color_scheme);
switch (color_scheme) {
case 0:
*theme = SDL_SYSTEM_THEME_UNKNOWN;
break;
case 1:
*theme = SDL_SYSTEM_THEME_DARK;
break;
case 2:
*theme = SDL_SYSTEM_THEME_LIGHT;
break;
}
return true;
}
static DBusHandlerResult DBus_MessageFilter(DBusConnection *conn, DBusMessage *msg, void *data) {
SDL_DBusContext *dbus = (SDL_DBusContext *)data;
if (dbus->message_is_signal(msg, SIGNAL_INTERFACE, SIGNAL_NAME)) {
DBusMessageIter signal_iter;
const char *namespace, *key;
dbus->message_iter_init(msg, &signal_iter);
// Check if the parameters are what we expect
if (dbus->message_iter_get_arg_type(&signal_iter) != DBUS_TYPE_STRING)
goto not_our_signal;
dbus->message_iter_get_basic(&signal_iter, &namespace);
if (SDL_strcmp(SIGNAL_NAMESPACE, namespace) != 0)
goto not_our_signal;
if (!dbus->message_iter_next(&signal_iter))
goto not_our_signal;
if (dbus->message_iter_get_arg_type(&signal_iter) != DBUS_TYPE_STRING)
goto not_our_signal;
dbus->message_iter_get_basic(&signal_iter, &key);
if (SDL_strcmp(SIGNAL_KEY, key) != 0)
goto not_our_signal;
if (!dbus->message_iter_next(&signal_iter))
goto not_our_signal;
if (!DBus_ExtractThemeVariant(&signal_iter, &system_theme_data.theme))
goto not_our_signal;
//SDL_SetSystemTheme(system_theme_data.theme);
return DBUS_HANDLER_RESULT_HANDLED;
}
not_our_signal:
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}
bool SDL_SystemTheme_Init(void)
{
SDL_DBusContext *dbus = SDL_DBus_GetContext();
DBusMessage *msg;
static const char *namespace = SIGNAL_NAMESPACE;
static const char *key = SIGNAL_KEY;
system_theme_data.theme = SDL_SYSTEM_THEME_UNKNOWN;
system_theme_data.dbus = dbus;
if (!dbus) {
return false;
}
msg = dbus->message_new_method_call(PORTAL_DESTINATION, PORTAL_PATH, PORTAL_INTERFACE, PORTAL_METHOD);
if (msg) {
if (dbus->message_append_args(msg, DBUS_TYPE_STRING, &namespace, DBUS_TYPE_STRING, &key, DBUS_TYPE_INVALID)) {
DBusMessage *reply = dbus->connection_send_with_reply_and_block(dbus->session_conn, msg, 300, NULL);
if (reply) {
DBusMessageIter reply_iter, variant_outer_iter;
dbus->message_iter_init(reply, &reply_iter);
// The response has signature <<u>>
if (dbus->message_iter_get_arg_type(&reply_iter) != DBUS_TYPE_VARIANT)
goto incorrect_type;
dbus->message_iter_recurse(&reply_iter, &variant_outer_iter);
if (!DBus_ExtractThemeVariant(&variant_outer_iter, &system_theme_data.theme))
goto incorrect_type;
incorrect_type:
dbus->message_unref(reply);
}
}
dbus->message_unref(msg);
}
dbus->bus_add_match(dbus->session_conn,
"type='signal', interface='"SIGNAL_INTERFACE"',"
"member='"SIGNAL_NAME"', arg0='"SIGNAL_NAMESPACE"',"
"arg1='"SIGNAL_KEY"'", NULL);
dbus->connection_add_filter(dbus->session_conn,
&DBus_MessageFilter, dbus, NULL);
dbus->connection_flush(dbus->session_conn);
return true;
}
SDL_SystemTheme SDL_SystemTheme_Get(void)
{
return system_theme_data.theme;
}

View file

@ -0,0 +1,30 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#ifndef SDL_system_theme_h_
#define SDL_system_theme_h_
#include "SDL_internal.h"
extern bool SDL_SystemTheme_Init(void);
extern SDL_SystemTheme SDL_SystemTheme_Get(void);
#endif // SDL_system_theme_h_

View file

@ -0,0 +1,345 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#ifdef SDL_PLATFORM_LINUX
#ifndef SDL_THREADS_DISABLED
#include <sys/time.h>
#include <sys/resource.h>
#include <pthread.h>
#include <sched.h>
#include <unistd.h>
// RLIMIT_RTTIME requires kernel >= 2.6.25 and is in glibc >= 2.14
#ifndef RLIMIT_RTTIME
#define RLIMIT_RTTIME 15
#endif
// SCHED_RESET_ON_FORK is in kernel >= 2.6.32.
#ifndef SCHED_RESET_ON_FORK
#define SCHED_RESET_ON_FORK 0x40000000
#endif
#include "SDL_dbus.h"
#ifdef SDL_USE_LIBDBUS
// d-bus queries to org.freedesktop.RealtimeKit1.
#define RTKIT_DBUS_NODE "org.freedesktop.RealtimeKit1"
#define RTKIT_DBUS_PATH "/org/freedesktop/RealtimeKit1"
#define RTKIT_DBUS_INTERFACE "org.freedesktop.RealtimeKit1"
// d-bus queries to the XDG portal interface to RealtimeKit1
#define XDG_PORTAL_DBUS_NODE "org.freedesktop.portal.Desktop"
#define XDG_PORTAL_DBUS_PATH "/org/freedesktop/portal/desktop"
#define XDG_PORTAL_DBUS_INTERFACE "org.freedesktop.portal.Realtime"
static bool rtkit_use_session_conn;
static const char *rtkit_dbus_node;
static const char *rtkit_dbus_path;
static const char *rtkit_dbus_interface;
static pthread_once_t rtkit_initialize_once = PTHREAD_ONCE_INIT;
static Sint32 rtkit_min_nice_level = -20;
static Sint32 rtkit_max_realtime_priority = 99;
static Sint64 rtkit_max_rttime_usec = 200000;
/*
* Checking that the RTTimeUSecMax property exists and is an int64 confirms that:
* - The desktop portal exists and supports the realtime interface.
* - The realtime interface is new enough to have the required bug fixes applied.
*/
static bool realtime_portal_supported(DBusConnection *conn)
{
Sint64 res;
return SDL_DBus_QueryPropertyOnConnection(conn, XDG_PORTAL_DBUS_NODE, XDG_PORTAL_DBUS_PATH, XDG_PORTAL_DBUS_INTERFACE,
"RTTimeUSecMax", DBUS_TYPE_INT64, &res);
}
static void set_rtkit_interface(void)
{
SDL_DBusContext *dbus = SDL_DBus_GetContext();
// xdg-desktop-portal works in all instances, so check for it first.
if (dbus && realtime_portal_supported(dbus->session_conn)) {
rtkit_use_session_conn = true;
rtkit_dbus_node = XDG_PORTAL_DBUS_NODE;
rtkit_dbus_path = XDG_PORTAL_DBUS_PATH;
rtkit_dbus_interface = XDG_PORTAL_DBUS_INTERFACE;
} else { // Fall back to the standard rtkit interface in all other cases.
rtkit_use_session_conn = false;
rtkit_dbus_node = RTKIT_DBUS_NODE;
rtkit_dbus_path = RTKIT_DBUS_PATH;
rtkit_dbus_interface = RTKIT_DBUS_INTERFACE;
}
}
static DBusConnection *get_rtkit_dbus_connection(void)
{
SDL_DBusContext *dbus = SDL_DBus_GetContext();
if (dbus) {
return rtkit_use_session_conn ? dbus->session_conn : dbus->system_conn;
}
return NULL;
}
static void rtkit_initialize(void)
{
DBusConnection *dbus_conn;
set_rtkit_interface();
dbus_conn = get_rtkit_dbus_connection();
// Try getting minimum nice level: this is often greater than PRIO_MIN (-20).
if (!dbus_conn || !SDL_DBus_QueryPropertyOnConnection(dbus_conn, rtkit_dbus_node, rtkit_dbus_path, rtkit_dbus_interface, "MinNiceLevel",
DBUS_TYPE_INT32, &rtkit_min_nice_level)) {
rtkit_min_nice_level = -20;
}
// Try getting maximum realtime priority: this can be less than the POSIX default (99).
if (!dbus_conn || !SDL_DBus_QueryPropertyOnConnection(dbus_conn, rtkit_dbus_node, rtkit_dbus_path, rtkit_dbus_interface, "MaxRealtimePriority",
DBUS_TYPE_INT32, &rtkit_max_realtime_priority)) {
rtkit_max_realtime_priority = 99;
}
// Try getting maximum rttime allowed by rtkit: exceeding this value will result in SIGKILL
if (!dbus_conn || !SDL_DBus_QueryPropertyOnConnection(dbus_conn, rtkit_dbus_node, rtkit_dbus_path, rtkit_dbus_interface, "RTTimeUSecMax",
DBUS_TYPE_INT64, &rtkit_max_rttime_usec)) {
rtkit_max_rttime_usec = 200000;
}
}
static bool rtkit_initialize_realtime_thread(void)
{
// Following is an excerpt from rtkit README that outlines the requirements
// a thread must meet before making rtkit requests:
//
// * Only clients with RLIMIT_RTTIME set will get RT scheduling
//
// * RT scheduling will only be handed out to processes with
// SCHED_RESET_ON_FORK set to guarantee that the scheduling
// settings cannot 'leak' to child processes, thus making sure
// that 'RT fork bombs' cannot be used to bypass RLIMIT_RTTIME
// and take the system down.
//
// * Limits are enforced on all user controllable resources, only
// a maximum number of users, processes, threads can request RT
// scheduling at the same time.
//
// * Only a limited number of threads may be made RT in a
// specific time frame.
//
// * Client authorization is verified with PolicyKit
int err;
struct rlimit rlimit;
int nLimit = RLIMIT_RTTIME;
pid_t nPid = 0; // self
int nSchedPolicy = sched_getscheduler(nPid) | SCHED_RESET_ON_FORK;
struct sched_param schedParam;
SDL_zero(schedParam);
// Requirement #1: Set RLIMIT_RTTIME
err = getrlimit(nLimit, &rlimit);
if (err) {
return false;
}
// Current rtkit allows a max of 200ms right now
rlimit.rlim_max = rtkit_max_rttime_usec;
rlimit.rlim_cur = rlimit.rlim_max / 2;
err = setrlimit(nLimit, &rlimit);
if (err) {
return false;
}
// Requirement #2: Add SCHED_RESET_ON_FORK to the scheduler policy
err = sched_getparam(nPid, &schedParam);
if (err) {
return false;
}
err = sched_setscheduler(nPid, nSchedPolicy, &schedParam);
if (err) {
return false;
}
return true;
}
static bool rtkit_setpriority_nice(pid_t thread, int nice_level)
{
DBusConnection *dbus_conn;
Uint64 pid = (Uint64)getpid();
Uint64 tid = (Uint64)thread;
Sint32 nice = (Sint32)nice_level;
pthread_once(&rtkit_initialize_once, rtkit_initialize);
dbus_conn = get_rtkit_dbus_connection();
if (nice < rtkit_min_nice_level) {
nice = rtkit_min_nice_level;
}
if (!dbus_conn || !SDL_DBus_CallMethodOnConnection(dbus_conn,
rtkit_dbus_node, rtkit_dbus_path, rtkit_dbus_interface, "MakeThreadHighPriorityWithPID",
DBUS_TYPE_UINT64, &pid, DBUS_TYPE_UINT64, &tid, DBUS_TYPE_INT32, &nice, DBUS_TYPE_INVALID,
DBUS_TYPE_INVALID)) {
return false;
}
return true;
}
static bool rtkit_setpriority_realtime(pid_t thread, int rt_priority)
{
DBusConnection *dbus_conn;
Uint64 pid = (Uint64)getpid();
Uint64 tid = (Uint64)thread;
Uint32 priority = (Uint32)rt_priority;
pthread_once(&rtkit_initialize_once, rtkit_initialize);
dbus_conn = get_rtkit_dbus_connection();
if (priority > rtkit_max_realtime_priority) {
priority = rtkit_max_realtime_priority;
}
// We always perform the thread state changes necessary for rtkit.
// This wastes some system calls if the state is already set but
// typically code sets a thread priority and leaves it so it's
// not expected that this wasted effort will be an issue.
// We also do not quit if this fails, we let the rtkit request
// go through to determine whether it really needs to fail or not.
rtkit_initialize_realtime_thread();
if (!dbus_conn || !SDL_DBus_CallMethodOnConnection(dbus_conn,
rtkit_dbus_node, rtkit_dbus_path, rtkit_dbus_interface, "MakeThreadRealtimeWithPID",
DBUS_TYPE_UINT64, &pid, DBUS_TYPE_UINT64, &tid, DBUS_TYPE_UINT32, &priority, DBUS_TYPE_INVALID,
DBUS_TYPE_INVALID)) {
return false;
}
return true;
}
#else
#define rtkit_max_realtime_priority 99
#endif // dbus
#endif // threads
// this is a public symbol, so it has to exist even if threads are disabled.
bool SDL_SetLinuxThreadPriority(Sint64 threadID, int priority)
{
#ifdef SDL_THREADS_DISABLED
return SDL_Unsupported();
#else
if (setpriority(PRIO_PROCESS, (id_t)threadID, priority) == 0) {
return true;
}
#ifdef SDL_USE_LIBDBUS
/* Note that this fails you most likely:
* Have your process's scheduler incorrectly configured.
See the requirements at:
http://git.0pointer.net/rtkit.git/tree/README#n16
* Encountered dbus/polkit security restrictions. Note
that the RealtimeKit1 dbus endpoint is inaccessible
over ssh connections for most common distro configs.
You might want to check your local config for details:
/usr/share/polkit-1/actions/org.freedesktop.RealtimeKit1.policy
README and sample code at: http://git.0pointer.net/rtkit.git
*/
if (rtkit_setpriority_nice((pid_t)threadID, priority)) {
return true;
}
#endif
return SDL_SetError("setpriority() failed");
#endif
}
// this is a public symbol, so it has to exist even if threads are disabled.
bool SDL_SetLinuxThreadPriorityAndPolicy(Sint64 threadID, int sdlPriority, int schedPolicy)
{
#ifdef SDL_THREADS_DISABLED
return SDL_Unsupported();
#else
int osPriority;
if (schedPolicy == SCHED_RR || schedPolicy == SCHED_FIFO) {
if (sdlPriority == SDL_THREAD_PRIORITY_LOW) {
osPriority = 1;
} else if (sdlPriority == SDL_THREAD_PRIORITY_HIGH) {
osPriority = rtkit_max_realtime_priority * 3 / 4;
} else if (sdlPriority == SDL_THREAD_PRIORITY_TIME_CRITICAL) {
osPriority = rtkit_max_realtime_priority;
} else {
osPriority = rtkit_max_realtime_priority / 2;
}
} else {
if (sdlPriority == SDL_THREAD_PRIORITY_LOW) {
osPriority = 19;
} else if (sdlPriority == SDL_THREAD_PRIORITY_HIGH) {
osPriority = -10;
} else if (sdlPriority == SDL_THREAD_PRIORITY_TIME_CRITICAL) {
osPriority = -20;
} else {
osPriority = 0;
}
if (setpriority(PRIO_PROCESS, (id_t)threadID, osPriority) == 0) {
return true;
}
}
#ifdef SDL_USE_LIBDBUS
/* Note that this fails you most likely:
* Have your process's scheduler incorrectly configured.
See the requirements at:
http://git.0pointer.net/rtkit.git/tree/README#n16
* Encountered dbus/polkit security restrictions. Note
that the RealtimeKit1 dbus endpoint is inaccessible
over ssh connections for most common distro configs.
You might want to check your local config for details:
/usr/share/polkit-1/actions/org.freedesktop.RealtimeKit1.policy
README and sample code at: http://git.0pointer.net/rtkit.git
*/
if (schedPolicy == SCHED_RR || schedPolicy == SCHED_FIFO) {
if (rtkit_setpriority_realtime((pid_t)threadID, osPriority)) {
return true;
}
} else {
if (rtkit_setpriority_nice((pid_t)threadID, osPriority)) {
return true;
}
}
#endif
return SDL_SetError("setpriority() failed");
#endif
}
#endif // SDL_PLATFORM_LINUX

596
thirdparty/sdl/core/linux/SDL_udev.c vendored Normal file
View file

@ -0,0 +1,596 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
/*
* To list the properties of a device, try something like:
* udevadm info -a -n snd/hwC0D0 (for a sound card)
* udevadm info --query=all -n input/event3 (for a keyboard, mouse, etc)
* udevadm info --query=property -n input/event2
*/
#include "SDL_udev.h"
#ifdef SDL_USE_LIBUDEV
#include <linux/input.h>
#include <sys/stat.h>
#include "SDL_evdev_capabilities.h"
#include "../unix/SDL_poll.h"
static const char *SDL_UDEV_LIBS[] = { "libudev.so.1", "libudev.so.0" };
static SDL_UDEV_PrivateData *_this = NULL;
static bool SDL_UDEV_load_sym(const char *fn, void **addr);
static bool SDL_UDEV_load_syms(void);
static bool SDL_UDEV_hotplug_update_available(void);
static void get_caps(struct udev_device *dev, struct udev_device *pdev, const char *attr, unsigned long *bitmask, size_t bitmask_len);
static int guess_device_class(struct udev_device *dev);
static int device_class(struct udev_device *dev);
static void device_event(SDL_UDEV_deviceevent type, struct udev_device *dev);
static bool SDL_UDEV_load_sym(const char *fn, void **addr)
{
*addr = SDL_LoadFunction(_this->udev_handle, fn);
if (!*addr) {
// Don't call SDL_SetError(): SDL_LoadFunction already did.
return false;
}
return true;
}
static bool SDL_UDEV_load_syms(void)
{
/* cast funcs to char* first, to please GCC's strict aliasing rules. */
#define SDL_UDEV_SYM(x) \
if (!SDL_UDEV_load_sym(#x, (void **)(char *)&_this->syms.x)) \
return false
SDL_UDEV_SYM(udev_device_get_action);
SDL_UDEV_SYM(udev_device_get_devnode);
SDL_UDEV_SYM(udev_device_get_syspath);
SDL_UDEV_SYM(udev_device_get_subsystem);
SDL_UDEV_SYM(udev_device_get_parent_with_subsystem_devtype);
SDL_UDEV_SYM(udev_device_get_property_value);
SDL_UDEV_SYM(udev_device_get_sysattr_value);
SDL_UDEV_SYM(udev_device_new_from_syspath);
SDL_UDEV_SYM(udev_device_unref);
SDL_UDEV_SYM(udev_enumerate_add_match_property);
SDL_UDEV_SYM(udev_enumerate_add_match_subsystem);
SDL_UDEV_SYM(udev_enumerate_get_list_entry);
SDL_UDEV_SYM(udev_enumerate_new);
SDL_UDEV_SYM(udev_enumerate_scan_devices);
SDL_UDEV_SYM(udev_enumerate_unref);
SDL_UDEV_SYM(udev_list_entry_get_name);
SDL_UDEV_SYM(udev_list_entry_get_next);
SDL_UDEV_SYM(udev_monitor_enable_receiving);
SDL_UDEV_SYM(udev_monitor_filter_add_match_subsystem_devtype);
SDL_UDEV_SYM(udev_monitor_get_fd);
SDL_UDEV_SYM(udev_monitor_new_from_netlink);
SDL_UDEV_SYM(udev_monitor_receive_device);
SDL_UDEV_SYM(udev_monitor_unref);
SDL_UDEV_SYM(udev_new);
SDL_UDEV_SYM(udev_unref);
SDL_UDEV_SYM(udev_device_new_from_devnum);
SDL_UDEV_SYM(udev_device_get_devnum);
#undef SDL_UDEV_SYM
return true;
}
static bool SDL_UDEV_hotplug_update_available(void)
{
if (_this->udev_mon) {
const int fd = _this->syms.udev_monitor_get_fd(_this->udev_mon);
if (SDL_IOReady(fd, SDL_IOR_READ, 0)) {
return true;
}
}
return false;
}
bool SDL_UDEV_Init(void)
{
if (!_this) {
_this = (SDL_UDEV_PrivateData *)SDL_calloc(1, sizeof(*_this));
if (!_this) {
return false;
}
if (!SDL_UDEV_LoadLibrary()) {
SDL_UDEV_Quit();
return false;
}
/* Set up udev monitoring
* Listen for input devices (mouse, keyboard, joystick, etc) and sound devices
*/
_this->udev = _this->syms.udev_new();
if (!_this->udev) {
SDL_UDEV_Quit();
return SDL_SetError("udev_new() failed");
}
_this->udev_mon = _this->syms.udev_monitor_new_from_netlink(_this->udev, "udev");
if (!_this->udev_mon) {
SDL_UDEV_Quit();
return SDL_SetError("udev_monitor_new_from_netlink() failed");
}
_this->syms.udev_monitor_filter_add_match_subsystem_devtype(_this->udev_mon, "input", NULL);
_this->syms.udev_monitor_filter_add_match_subsystem_devtype(_this->udev_mon, "sound", NULL);
_this->syms.udev_monitor_filter_add_match_subsystem_devtype(_this->udev_mon, "video4linux", NULL);
_this->syms.udev_monitor_enable_receiving(_this->udev_mon);
// Do an initial scan of existing devices
SDL_UDEV_Scan();
}
_this->ref_count += 1;
return true;
}
void SDL_UDEV_Quit(void)
{
if (!_this) {
return;
}
_this->ref_count -= 1;
if (_this->ref_count < 1) {
if (_this->udev_mon) {
_this->syms.udev_monitor_unref(_this->udev_mon);
_this->udev_mon = NULL;
}
if (_this->udev) {
_this->syms.udev_unref(_this->udev);
_this->udev = NULL;
}
// Remove existing devices
while (_this->first) {
SDL_UDEV_CallbackList *item = _this->first;
_this->first = _this->first->next;
SDL_free(item);
}
SDL_UDEV_UnloadLibrary();
SDL_free(_this);
_this = NULL;
}
}
bool SDL_UDEV_Scan(void)
{
struct udev_enumerate *enumerate = NULL;
struct udev_list_entry *devs = NULL;
struct udev_list_entry *item = NULL;
if (!_this) {
return true;
}
enumerate = _this->syms.udev_enumerate_new(_this->udev);
if (!enumerate) {
SDL_UDEV_Quit();
return SDL_SetError("udev_enumerate_new() failed");
}
_this->syms.udev_enumerate_add_match_subsystem(enumerate, "input");
_this->syms.udev_enumerate_add_match_subsystem(enumerate, "sound");
_this->syms.udev_enumerate_add_match_subsystem(enumerate, "video4linux");
_this->syms.udev_enumerate_scan_devices(enumerate);
devs = _this->syms.udev_enumerate_get_list_entry(enumerate);
for (item = devs; item; item = _this->syms.udev_list_entry_get_next(item)) {
const char *path = _this->syms.udev_list_entry_get_name(item);
struct udev_device *dev = _this->syms.udev_device_new_from_syspath(_this->udev, path);
if (dev) {
device_event(SDL_UDEV_DEVICEADDED, dev);
_this->syms.udev_device_unref(dev);
}
}
_this->syms.udev_enumerate_unref(enumerate);
return true;
}
bool SDL_UDEV_GetProductInfo(const char *device_path, Uint16 *vendor, Uint16 *product, Uint16 *version, int *class)
{
struct stat statbuf;
char type;
struct udev_device *dev;
const char* val;
int class_temp;
if (!_this) {
return false;
}
if (stat(device_path, &statbuf) == -1) {
return false;
}
if (S_ISBLK(statbuf.st_mode)) {
type = 'b';
}
else if (S_ISCHR(statbuf.st_mode)) {
type = 'c';
}
else {
return false;
}
dev = _this->syms.udev_device_new_from_devnum(_this->udev, type, statbuf.st_rdev);
if (!dev) {
return false;
}
val = _this->syms.udev_device_get_property_value(dev, "ID_VENDOR_ID");
if (val) {
*vendor = (Uint16)SDL_strtol(val, NULL, 16);
}
val = _this->syms.udev_device_get_property_value(dev, "ID_MODEL_ID");
if (val) {
*product = (Uint16)SDL_strtol(val, NULL, 16);
}
val = _this->syms.udev_device_get_property_value(dev, "ID_REVISION");
if (val) {
*version = (Uint16)SDL_strtol(val, NULL, 16);
}
class_temp = device_class(dev);
if (class_temp) {
*class = class_temp;
}
_this->syms.udev_device_unref(dev);
return true;
}
void SDL_UDEV_UnloadLibrary(void)
{
if (!_this) {
return;
}
if (_this->udev_handle) {
SDL_UnloadObject(_this->udev_handle);
_this->udev_handle = NULL;
}
}
bool SDL_UDEV_LoadLibrary(void)
{
bool result = true;
if (!_this) {
return SDL_SetError("UDEV not initialized");
}
// See if there is a udev library already loaded
if (SDL_UDEV_load_syms()) {
return true;
}
#ifdef SDL_UDEV_DYNAMIC
// Check for the build environment's libudev first
if (!_this->udev_handle) {
_this->udev_handle = SDL_LoadObject(SDL_UDEV_DYNAMIC);
if (_this->udev_handle) {
result = SDL_UDEV_load_syms();
if (!result) {
SDL_UDEV_UnloadLibrary();
}
}
}
#endif
if (!_this->udev_handle) {
for (int i = 0; i < SDL_arraysize(SDL_UDEV_LIBS); i++) {
_this->udev_handle = SDL_LoadObject(SDL_UDEV_LIBS[i]);
if (_this->udev_handle) {
result = SDL_UDEV_load_syms();
if (!result) {
SDL_UDEV_UnloadLibrary();
} else {
break;
}
}
}
if (!_this->udev_handle) {
result = false;
// Don't call SDL_SetError(): SDL_LoadObject already did.
}
}
return result;
}
static void get_caps(struct udev_device *dev, struct udev_device *pdev, const char *attr, unsigned long *bitmask, size_t bitmask_len)
{
const char *value;
char text[4096];
char *word;
int i;
unsigned long v;
SDL_memset(bitmask, 0, bitmask_len * sizeof(*bitmask));
value = _this->syms.udev_device_get_sysattr_value(pdev, attr);
if (!value) {
return;
}
SDL_strlcpy(text, value, sizeof(text));
i = 0;
while ((word = SDL_strrchr(text, ' ')) != NULL) {
v = SDL_strtoul(word + 1, NULL, 16);
if (i < bitmask_len) {
bitmask[i] = v;
}
++i;
*word = '\0';
}
v = SDL_strtoul(text, NULL, 16);
if (i < bitmask_len) {
bitmask[i] = v;
}
}
static int guess_device_class(struct udev_device *dev)
{
struct udev_device *pdev;
unsigned long bitmask_props[NBITS(INPUT_PROP_MAX)];
unsigned long bitmask_ev[NBITS(EV_MAX)];
unsigned long bitmask_abs[NBITS(ABS_MAX)];
unsigned long bitmask_key[NBITS(KEY_MAX)];
unsigned long bitmask_rel[NBITS(REL_MAX)];
/* walk up the parental chain until we find the real input device; the
* argument is very likely a subdevice of this, like eventN */
pdev = dev;
while (pdev && !_this->syms.udev_device_get_sysattr_value(pdev, "capabilities/ev")) {
pdev = _this->syms.udev_device_get_parent_with_subsystem_devtype(pdev, "input", NULL);
}
if (!pdev) {
return 0;
}
get_caps(dev, pdev, "properties", bitmask_props, SDL_arraysize(bitmask_props));
get_caps(dev, pdev, "capabilities/ev", bitmask_ev, SDL_arraysize(bitmask_ev));
get_caps(dev, pdev, "capabilities/abs", bitmask_abs, SDL_arraysize(bitmask_abs));
get_caps(dev, pdev, "capabilities/rel", bitmask_rel, SDL_arraysize(bitmask_rel));
get_caps(dev, pdev, "capabilities/key", bitmask_key, SDL_arraysize(bitmask_key));
return SDL_EVDEV_GuessDeviceClass(&bitmask_props[0],
&bitmask_ev[0],
&bitmask_abs[0],
&bitmask_key[0],
&bitmask_rel[0]);
}
static int device_class(struct udev_device *dev)
{
const char *subsystem;
const char *val = NULL;
int devclass = 0;
subsystem = _this->syms.udev_device_get_subsystem(dev);
if (!subsystem) {
return 0;
}
if (SDL_strcmp(subsystem, "sound") == 0) {
devclass = SDL_UDEV_DEVICE_SOUND;
} else if (SDL_strcmp(subsystem, "video4linux") == 0) {
val = _this->syms.udev_device_get_property_value(dev, "ID_V4L_CAPABILITIES");
if (val && SDL_strcasestr(val, "capture")) {
devclass = SDL_UDEV_DEVICE_VIDEO_CAPTURE;
}
} else if (SDL_strcmp(subsystem, "input") == 0) {
// udev rules reference: http://cgit.freedesktop.org/systemd/systemd/tree/src/udev/udev-builtin-input_id.c
val = _this->syms.udev_device_get_property_value(dev, "ID_INPUT_JOYSTICK");
if (val && SDL_strcmp(val, "1") == 0) {
devclass |= SDL_UDEV_DEVICE_JOYSTICK;
}
val = _this->syms.udev_device_get_property_value(dev, "ID_INPUT_ACCELEROMETER");
if (val && SDL_strcmp(val, "1") == 0) {
devclass |= SDL_UDEV_DEVICE_ACCELEROMETER;
}
val = _this->syms.udev_device_get_property_value(dev, "ID_INPUT_MOUSE");
if (val && SDL_strcmp(val, "1") == 0) {
devclass |= SDL_UDEV_DEVICE_MOUSE;
}
val = _this->syms.udev_device_get_property_value(dev, "ID_INPUT_TOUCHSCREEN");
if (val && SDL_strcmp(val, "1") == 0) {
devclass |= SDL_UDEV_DEVICE_TOUCHSCREEN;
}
/* The undocumented rule is:
- All devices with keys get ID_INPUT_KEY
- From this subset, if they have ESC, numbers, and Q to D, it also gets ID_INPUT_KEYBOARD
Ref: http://cgit.freedesktop.org/systemd/systemd/tree/src/udev/udev-builtin-input_id.c#n183
*/
val = _this->syms.udev_device_get_property_value(dev, "ID_INPUT_KEY");
if (val && SDL_strcmp(val, "1") == 0) {
devclass |= SDL_UDEV_DEVICE_HAS_KEYS;
}
val = _this->syms.udev_device_get_property_value(dev, "ID_INPUT_KEYBOARD");
if (val && SDL_strcmp(val, "1") == 0) {
devclass |= SDL_UDEV_DEVICE_KEYBOARD;
}
if (devclass == 0) {
// Fall back to old style input classes
val = _this->syms.udev_device_get_property_value(dev, "ID_CLASS");
if (val) {
if (SDL_strcmp(val, "joystick") == 0) {
devclass = SDL_UDEV_DEVICE_JOYSTICK;
} else if (SDL_strcmp(val, "mouse") == 0) {
devclass = SDL_UDEV_DEVICE_MOUSE;
} else if (SDL_strcmp(val, "kbd") == 0) {
devclass = SDL_UDEV_DEVICE_HAS_KEYS | SDL_UDEV_DEVICE_KEYBOARD;
}
} else {
// We could be linked with libudev on a system that doesn't have udev running
devclass = guess_device_class(dev);
}
}
}
return devclass;
}
static void device_event(SDL_UDEV_deviceevent type, struct udev_device *dev)
{
int devclass = 0;
const char *path;
SDL_UDEV_CallbackList *item;
path = _this->syms.udev_device_get_devnode(dev);
if (!path) {
return;
}
if (type == SDL_UDEV_DEVICEADDED) {
devclass = device_class(dev);
if (!devclass) {
return;
}
} else {
// The device has been removed, the class isn't available
}
// Process callbacks
for (item = _this->first; item; item = item->next) {
item->callback(type, devclass, path);
}
}
void SDL_UDEV_Poll(void)
{
struct udev_device *dev = NULL;
const char *action = NULL;
if (!_this) {
return;
}
while (SDL_UDEV_hotplug_update_available()) {
dev = _this->syms.udev_monitor_receive_device(_this->udev_mon);
if (!dev) {
break;
}
action = _this->syms.udev_device_get_action(dev);
if (action) {
if (SDL_strcmp(action, "add") == 0) {
device_event(SDL_UDEV_DEVICEADDED, dev);
} else if (SDL_strcmp(action, "remove") == 0) {
device_event(SDL_UDEV_DEVICEREMOVED, dev);
}
}
_this->syms.udev_device_unref(dev);
}
}
bool SDL_UDEV_AddCallback(SDL_UDEV_Callback cb)
{
SDL_UDEV_CallbackList *item;
item = (SDL_UDEV_CallbackList *)SDL_calloc(1, sizeof(SDL_UDEV_CallbackList));
if (!item) {
return false;
}
item->callback = cb;
if (!_this->last) {
_this->first = _this->last = item;
} else {
_this->last->next = item;
_this->last = item;
}
return true;
}
void SDL_UDEV_DelCallback(SDL_UDEV_Callback cb)
{
SDL_UDEV_CallbackList *item;
SDL_UDEV_CallbackList *prev = NULL;
if (!_this) {
return;
}
for (item = _this->first; item; item = item->next) {
// found it, remove it.
if (item->callback == cb) {
if (prev) {
prev->next = item->next;
} else {
SDL_assert(_this->first == item);
_this->first = item->next;
}
if (item == _this->last) {
_this->last = prev;
}
SDL_free(item);
return;
}
prev = item;
}
}
const SDL_UDEV_Symbols *SDL_UDEV_GetUdevSyms(void)
{
if (!SDL_UDEV_Init()) {
SDL_SetError("Could not initialize UDEV");
return NULL;
}
return &_this->syms;
}
void SDL_UDEV_ReleaseUdevSyms(void)
{
SDL_UDEV_Quit();
}
#endif // SDL_USE_LIBUDEV

113
thirdparty/sdl/core/linux/SDL_udev.h vendored Normal file
View file

@ -0,0 +1,113 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#ifndef SDL_udev_h_
#define SDL_udev_h_
#if defined(HAVE_LIBUDEV_H) && defined(HAVE_LINUX_INPUT_H)
#ifndef SDL_USE_LIBUDEV
#define SDL_USE_LIBUDEV 1
#endif
#include <libudev.h>
#include <sys/time.h>
#include <sys/types.h>
/**
* Device type
*/
typedef enum
{
SDL_UDEV_DEVICEADDED = 1,
SDL_UDEV_DEVICEREMOVED
} SDL_UDEV_deviceevent;
typedef void (*SDL_UDEV_Callback)(SDL_UDEV_deviceevent udev_type, int udev_class, const char *devpath);
typedef struct SDL_UDEV_CallbackList
{
SDL_UDEV_Callback callback;
struct SDL_UDEV_CallbackList *next;
} SDL_UDEV_CallbackList;
typedef struct SDL_UDEV_Symbols
{
const char *(*udev_device_get_action)(struct udev_device *);
const char *(*udev_device_get_devnode)(struct udev_device *);
const char *(*udev_device_get_syspath)(struct udev_device *);
const char *(*udev_device_get_subsystem)(struct udev_device *);
struct udev_device *(*udev_device_get_parent_with_subsystem_devtype)(struct udev_device *udev_device, const char *subsystem, const char *devtype);
const char *(*udev_device_get_property_value)(struct udev_device *, const char *);
const char *(*udev_device_get_sysattr_value)(struct udev_device *udev_device, const char *sysattr);
struct udev_device *(*udev_device_new_from_syspath)(struct udev *, const char *);
void (*udev_device_unref)(struct udev_device *);
int (*udev_enumerate_add_match_property)(struct udev_enumerate *, const char *, const char *);
int (*udev_enumerate_add_match_subsystem)(struct udev_enumerate *, const char *);
struct udev_list_entry *(*udev_enumerate_get_list_entry)(struct udev_enumerate *);
struct udev_enumerate *(*udev_enumerate_new)(struct udev *);
int (*udev_enumerate_scan_devices)(struct udev_enumerate *);
void (*udev_enumerate_unref)(struct udev_enumerate *);
const char *(*udev_list_entry_get_name)(struct udev_list_entry *);
struct udev_list_entry *(*udev_list_entry_get_next)(struct udev_list_entry *);
int (*udev_monitor_enable_receiving)(struct udev_monitor *);
int (*udev_monitor_filter_add_match_subsystem_devtype)(struct udev_monitor *, const char *, const char *);
int (*udev_monitor_get_fd)(struct udev_monitor *);
struct udev_monitor *(*udev_monitor_new_from_netlink)(struct udev *, const char *);
struct udev_device *(*udev_monitor_receive_device)(struct udev_monitor *);
void (*udev_monitor_unref)(struct udev_monitor *);
struct udev *(*udev_new)(void);
void (*udev_unref)(struct udev *);
struct udev_device *(*udev_device_new_from_devnum)(struct udev *udev, char type, dev_t devnum);
dev_t (*udev_device_get_devnum)(struct udev_device *udev_device);
} SDL_UDEV_Symbols;
typedef struct SDL_UDEV_PrivateData
{
const char *udev_library;
SDL_SharedObject *udev_handle;
struct udev *udev;
struct udev_monitor *udev_mon;
int ref_count;
SDL_UDEV_CallbackList *first, *last;
// Function pointers
SDL_UDEV_Symbols syms;
} SDL_UDEV_PrivateData;
extern bool SDL_UDEV_Init(void);
extern void SDL_UDEV_Quit(void);
extern void SDL_UDEV_UnloadLibrary(void);
extern bool SDL_UDEV_LoadLibrary(void);
extern void SDL_UDEV_Poll(void);
extern bool SDL_UDEV_Scan(void);
extern bool SDL_UDEV_GetProductInfo(const char *device_path, Uint16 *vendor, Uint16 *product, Uint16 *version, int *class);
extern bool SDL_UDEV_AddCallback(SDL_UDEV_Callback cb);
extern void SDL_UDEV_DelCallback(SDL_UDEV_Callback cb);
extern const SDL_UDEV_Symbols *SDL_UDEV_GetUdevSyms(void);
extern void SDL_UDEV_ReleaseUdevSyms(void);
#endif // HAVE_LIBUDEV_H && HAVE_LINUX_INPUT_H
#endif // SDL_udev_h_

75
thirdparty/sdl/core/unix/SDL_appid.c vendored Normal file
View file

@ -0,0 +1,75 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#include "SDL_appid.h"
#include <unistd.h>
const char *SDL_GetExeName(void)
{
static const char *proc_name = NULL;
// TODO: Use a fallback if BSD has no mounted procfs (OpenBSD has no procfs at all)
if (!proc_name) {
#if defined(SDL_PLATFORM_LINUX) || defined(SDL_PLATFORM_FREEBSD) || defined (SDL_PLATFORM_NETBSD)
static char linkfile[1024];
int linksize;
#if defined(SDL_PLATFORM_LINUX)
const char *proc_path = "/proc/self/exe";
#elif defined(SDL_PLATFORM_FREEBSD)
const char *proc_path = "/proc/curproc/file";
#elif defined(SDL_PLATFORM_NETBSD)
const char *proc_path = "/proc/curproc/exe";
#endif
linksize = readlink(proc_path, linkfile, sizeof(linkfile) - 1);
if (linksize > 0) {
linkfile[linksize] = '\0';
proc_name = SDL_strrchr(linkfile, '/');
if (proc_name) {
++proc_name;
} else {
proc_name = linkfile;
}
}
#endif
}
return proc_name;
}
const char *SDL_GetAppID(void)
{
const char *id_str = SDL_GetAppMetadataProperty(SDL_PROP_APP_METADATA_IDENTIFIER_STRING);
if (!id_str) {
// If the hint isn't set, try to use the application's executable name
id_str = SDL_GetExeName();
}
if (!id_str) {
// Finally, use the default we've used forever
id_str = "SDL_App";
}
return id_str;
}

30
thirdparty/sdl/core/unix/SDL_appid.h vendored Normal file
View file

@ -0,0 +1,30 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#ifndef SDL_appid_h_
#define SDL_appid_h_
extern const char *SDL_GetExeName(void);
extern const char *SDL_GetAppID(void);
#endif // SDL_appid_h_

95
thirdparty/sdl/core/unix/SDL_poll.c vendored Normal file
View file

@ -0,0 +1,95 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#include "SDL_poll.h"
#ifdef HAVE_POLL
#include <poll.h>
#else
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#endif
#include <errno.h>
int SDL_IOReady(int fd, int flags, Sint64 timeoutNS)
{
int result;
SDL_assert(flags & (SDL_IOR_READ | SDL_IOR_WRITE));
// Note: We don't bother to account for elapsed time if we get EINTR
do {
#ifdef HAVE_POLL
struct pollfd info;
int timeoutMS;
info.fd = fd;
info.events = 0;
if (flags & SDL_IOR_READ) {
info.events |= POLLIN | POLLPRI;
}
if (flags & SDL_IOR_WRITE) {
info.events |= POLLOUT;
}
// FIXME: Add support for ppoll() for nanosecond precision
if (timeoutNS > 0) {
timeoutMS = (int)SDL_NS_TO_MS(timeoutNS + (SDL_NS_PER_MS - 1));
} else if (timeoutNS == 0) {
timeoutMS = 0;
} else {
timeoutMS = -1;
}
result = poll(&info, 1, timeoutMS);
#else
fd_set rfdset, *rfdp = NULL;
fd_set wfdset, *wfdp = NULL;
struct timeval tv, *tvp = NULL;
// If this assert triggers we'll corrupt memory here
SDL_assert(fd >= 0 && fd < FD_SETSIZE);
if (flags & SDL_IOR_READ) {
FD_ZERO(&rfdset);
FD_SET(fd, &rfdset);
rfdp = &rfdset;
}
if (flags & SDL_IOR_WRITE) {
FD_ZERO(&wfdset);
FD_SET(fd, &wfdset);
wfdp = &wfdset;
}
if (timeoutNS >= 0) {
tv.tv_sec = (timeoutNS / SDL_NS_PER_SECOND);
tv.tv_usec = SDL_NS_TO_US((timeoutNS % SDL_NS_PER_SECOND) + (SDL_NS_PER_US - 1));
tvp = &tv;
}
result = select(fd + 1, rfdp, wfdp, NULL, tvp);
#endif // HAVE_POLL
} while (result < 0 && errno == EINTR && !(flags & SDL_IOR_NO_RETRY));
return result;
}

33
thirdparty/sdl/core/unix/SDL_poll.h vendored Normal file
View file

@ -0,0 +1,33 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#ifndef SDL_poll_h_
#define SDL_poll_h_
#define SDL_IOR_READ 0x1
#define SDL_IOR_WRITE 0x2
#define SDL_IOR_NO_RETRY 0x4
extern int SDL_IOReady(int fd, int flags, Sint64 timeoutNS);
#endif // SDL_poll_h_

View file

@ -0,0 +1,112 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#ifndef SDL_directx_h_
#define SDL_directx_h_
// Include all of the DirectX 8.0 headers and adds any necessary tweaks
#include "SDL_windows.h"
#include <mmsystem.h>
#ifndef WIN32
#define WIN32
#endif
#undef WINNT
// Far pointers don't exist in 32-bit code
#ifndef FAR
#define FAR
#endif
// Error codes not yet included in Win32 API header files
#ifndef MAKE_HRESULT
#define MAKE_HRESULT(sev, fac, code) \
((HRESULT)(((unsigned long)(sev) << 31) | ((unsigned long)(fac) << 16) | ((unsigned long)(code))))
#endif
#ifndef S_OK
#define S_OK (HRESULT)0x00000000L
#endif
#ifndef SUCCEEDED
#define SUCCEEDED(x) ((HRESULT)(x) >= 0)
#endif
#ifndef FAILED
#define FAILED(x) ((HRESULT)(x) < 0)
#endif
#ifndef E_FAIL
#define E_FAIL (HRESULT)0x80000008L
#endif
#ifndef E_NOINTERFACE
#define E_NOINTERFACE (HRESULT)0x80004002L
#endif
#ifndef E_OUTOFMEMORY
#define E_OUTOFMEMORY (HRESULT)0x8007000EL
#endif
#ifndef E_INVALIDARG
#define E_INVALIDARG (HRESULT)0x80070057L
#endif
#ifndef E_NOTIMPL
#define E_NOTIMPL (HRESULT)0x80004001L
#endif
#ifndef REGDB_E_CLASSNOTREG
#define REGDB_E_CLASSNOTREG (HRESULT)0x80040154L
#endif
// Severity codes
#ifndef SEVERITY_ERROR
#define SEVERITY_ERROR 1
#endif
// Error facility codes
#ifndef FACILITY_WIN32
#define FACILITY_WIN32 7
#endif
#ifndef FIELD_OFFSET
#define FIELD_OFFSET(type, field) ((LONG) & (((type *)0)->field))
#endif
/* DirectX headers (if it isn't included, I haven't tested it yet)
*/
// We need these defines to mark what version of DirectX API we use
#define DIRECTDRAW_VERSION 0x0700
#define DIRECTSOUND_VERSION 0x0800
#define DIRECTINPUT_VERSION 0x0800 // Need version 7 for force feedback. Need version 8 so IDirectInput8_EnumDevices doesn't leak like a sieve...
#ifdef HAVE_DDRAW_H
#include <ddraw.h>
#endif
#ifdef HAVE_DSOUND_H
#include <dsound.h>
#endif
#ifdef HAVE_DINPUT_H
#include <dinput.h>
#else
typedef struct
{
int unused;
} DIDEVICEINSTANCE;
#endif
#endif // SDL_directx_h_

View file

@ -0,0 +1,98 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#ifdef HAVE_GAMEINPUT_H
#include "SDL_windows.h"
#include "SDL_gameinput.h"
#ifdef SDL_PLATFORM_WIN32
#include <initguid.h>
// {11BE2A7E-4254-445A-9C09-FFC40F006918}
DEFINE_GUID(SDL_IID_GameInput, 0x11BE2A7E, 0x4254, 0x445A, 0x9C, 0x09, 0xFF, 0xC4, 0x0F, 0x00, 0x69, 0x18);
#endif
static SDL_SharedObject *g_hGameInputDLL;
static IGameInput *g_pGameInput;
static int g_nGameInputRefCount;
bool SDL_InitGameInput(IGameInput **ppGameInput)
{
if (g_nGameInputRefCount == 0) {
g_hGameInputDLL = SDL_LoadObject("gameinput.dll");
if (!g_hGameInputDLL) {
return false;
}
typedef HRESULT (WINAPI *GameInputCreate_t)(IGameInput * *gameInput);
GameInputCreate_t GameInputCreateFunc = (GameInputCreate_t)SDL_LoadFunction(g_hGameInputDLL, "GameInputCreate");
if (!GameInputCreateFunc) {
SDL_UnloadObject(g_hGameInputDLL);
return false;
}
IGameInput *pGameInput = NULL;
HRESULT hr = GameInputCreateFunc(&pGameInput);
if (FAILED(hr)) {
SDL_UnloadObject(g_hGameInputDLL);
return WIN_SetErrorFromHRESULT("GameInputCreate failed", hr);
}
#ifdef SDL_PLATFORM_WIN32
hr = IGameInput_QueryInterface(pGameInput, &SDL_IID_GameInput, (void **)&g_pGameInput);
IGameInput_Release(pGameInput);
if (FAILED(hr)) {
SDL_UnloadObject(g_hGameInputDLL);
return WIN_SetErrorFromHRESULT("GameInput QueryInterface failed", hr);
}
#else
// Assume that the version we get is compatible with the current SDK
// If that isn't the case, define the correct GUID for SDL_IID_GameInput above
g_pGameInput = pGameInput;
#endif
}
++g_nGameInputRefCount;
if (ppGameInput) {
*ppGameInput = g_pGameInput;
}
return true;
}
void SDL_QuitGameInput(void)
{
SDL_assert(g_nGameInputRefCount > 0);
--g_nGameInputRefCount;
if (g_nGameInputRefCount == 0) {
if (g_pGameInput) {
IGameInput_Release(g_pGameInput);
g_pGameInput = NULL;
}
if (g_hGameInputDLL) {
SDL_UnloadObject(g_hGameInputDLL);
g_hGameInputDLL = NULL;
}
}
}
#endif // HAVE_GAMEINPUT_H

View file

@ -0,0 +1,36 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#ifndef SDL_gameinput_h_
#define SDL_gameinput_h_
#ifdef HAVE_GAMEINPUT_H
#define COBJMACROS
#include <gameinput.h>
extern bool SDL_InitGameInput(IGameInput **ppGameInput);
extern void SDL_QuitGameInput(void);
#endif // HAVE_GAMEINPUT_H
#endif // SDL_gameinput_h_

254
thirdparty/sdl/core/windows/SDL_hid.c vendored Normal file
View file

@ -0,0 +1,254 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#include "SDL_hid.h"
HidD_GetAttributes_t SDL_HidD_GetAttributes;
HidD_GetString_t SDL_HidD_GetManufacturerString;
HidD_GetString_t SDL_HidD_GetProductString;
HidP_GetCaps_t SDL_HidP_GetCaps;
HidP_GetButtonCaps_t SDL_HidP_GetButtonCaps;
HidP_GetValueCaps_t SDL_HidP_GetValueCaps;
HidP_MaxDataListLength_t SDL_HidP_MaxDataListLength;
HidP_GetData_t SDL_HidP_GetData;
static HMODULE s_pHIDDLL = 0;
static int s_HIDDLLRefCount = 0;
bool WIN_LoadHIDDLL(void)
{
if (s_pHIDDLL) {
SDL_assert(s_HIDDLLRefCount > 0);
s_HIDDLLRefCount++;
return true; // already loaded
}
s_pHIDDLL = LoadLibrary(TEXT("hid.dll"));
if (!s_pHIDDLL) {
return false;
}
SDL_assert(s_HIDDLLRefCount == 0);
s_HIDDLLRefCount = 1;
SDL_HidD_GetAttributes = (HidD_GetAttributes_t)GetProcAddress(s_pHIDDLL, "HidD_GetAttributes");
SDL_HidD_GetManufacturerString = (HidD_GetString_t)GetProcAddress(s_pHIDDLL, "HidD_GetManufacturerString");
SDL_HidD_GetProductString = (HidD_GetString_t)GetProcAddress(s_pHIDDLL, "HidD_GetProductString");
SDL_HidP_GetCaps = (HidP_GetCaps_t)GetProcAddress(s_pHIDDLL, "HidP_GetCaps");
SDL_HidP_GetButtonCaps = (HidP_GetButtonCaps_t)GetProcAddress(s_pHIDDLL, "HidP_GetButtonCaps");
SDL_HidP_GetValueCaps = (HidP_GetValueCaps_t)GetProcAddress(s_pHIDDLL, "HidP_GetValueCaps");
SDL_HidP_MaxDataListLength = (HidP_MaxDataListLength_t)GetProcAddress(s_pHIDDLL, "HidP_MaxDataListLength");
SDL_HidP_GetData = (HidP_GetData_t)GetProcAddress(s_pHIDDLL, "HidP_GetData");
if (!SDL_HidD_GetManufacturerString || !SDL_HidD_GetProductString ||
!SDL_HidP_GetCaps || !SDL_HidP_GetButtonCaps ||
!SDL_HidP_GetValueCaps || !SDL_HidP_MaxDataListLength || !SDL_HidP_GetData) {
WIN_UnloadHIDDLL();
return false;
}
return true;
}
void WIN_UnloadHIDDLL(void)
{
if (s_pHIDDLL) {
SDL_assert(s_HIDDLLRefCount > 0);
if (--s_HIDDLLRefCount == 0) {
FreeLibrary(s_pHIDDLL);
s_pHIDDLL = NULL;
}
} else {
SDL_assert(s_HIDDLLRefCount == 0);
}
}
#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
// CM_Register_Notification definitions
#define CR_SUCCESS 0
DECLARE_HANDLE(HCMNOTIFICATION);
typedef HCMNOTIFICATION *PHCMNOTIFICATION;
typedef enum _CM_NOTIFY_FILTER_TYPE
{
CM_NOTIFY_FILTER_TYPE_DEVICEINTERFACE = 0,
CM_NOTIFY_FILTER_TYPE_DEVICEHANDLE,
CM_NOTIFY_FILTER_TYPE_DEVICEINSTANCE,
CM_NOTIFY_FILTER_TYPE_MAX
} CM_NOTIFY_FILTER_TYPE, *PCM_NOTIFY_FILTER_TYPE;
typedef struct _CM_NOTIFY_FILTER
{
DWORD cbSize;
DWORD Flags;
CM_NOTIFY_FILTER_TYPE FilterType;
DWORD Reserved;
union
{
struct
{
GUID ClassGuid;
} DeviceInterface;
struct
{
HANDLE hTarget;
} DeviceHandle;
struct
{
WCHAR InstanceId[200];
} DeviceInstance;
} u;
} CM_NOTIFY_FILTER, *PCM_NOTIFY_FILTER;
typedef enum _CM_NOTIFY_ACTION
{
CM_NOTIFY_ACTION_DEVICEINTERFACEARRIVAL = 0,
CM_NOTIFY_ACTION_DEVICEINTERFACEREMOVAL,
CM_NOTIFY_ACTION_DEVICEQUERYREMOVE,
CM_NOTIFY_ACTION_DEVICEQUERYREMOVEFAILED,
CM_NOTIFY_ACTION_DEVICEREMOVEPENDING,
CM_NOTIFY_ACTION_DEVICEREMOVECOMPLETE,
CM_NOTIFY_ACTION_DEVICECUSTOMEVENT,
CM_NOTIFY_ACTION_DEVICEINSTANCEENUMERATED,
CM_NOTIFY_ACTION_DEVICEINSTANCESTARTED,
CM_NOTIFY_ACTION_DEVICEINSTANCEREMOVED,
CM_NOTIFY_ACTION_MAX
} CM_NOTIFY_ACTION, *PCM_NOTIFY_ACTION;
typedef struct _CM_NOTIFY_EVENT_DATA
{
CM_NOTIFY_FILTER_TYPE FilterType;
DWORD Reserved;
union
{
struct
{
GUID ClassGuid;
WCHAR SymbolicLink[ANYSIZE_ARRAY];
} DeviceInterface;
struct
{
GUID EventGuid;
LONG NameOffset;
DWORD DataSize;
BYTE Data[ANYSIZE_ARRAY];
} DeviceHandle;
struct
{
WCHAR InstanceId[ANYSIZE_ARRAY];
} DeviceInstance;
} u;
} CM_NOTIFY_EVENT_DATA, *PCM_NOTIFY_EVENT_DATA;
typedef DWORD (CALLBACK *PCM_NOTIFY_CALLBACK)(HCMNOTIFICATION hNotify, PVOID Context, CM_NOTIFY_ACTION Action, PCM_NOTIFY_EVENT_DATA EventData, DWORD EventDataSize);
typedef DWORD (WINAPI *CM_Register_NotificationFunc)(PCM_NOTIFY_FILTER pFilter, PVOID pContext, PCM_NOTIFY_CALLBACK pCallback, PHCMNOTIFICATION pNotifyContext);
typedef DWORD (WINAPI *CM_Unregister_NotificationFunc)(HCMNOTIFICATION NotifyContext);
static GUID GUID_DEVINTERFACE_HID = { 0x4D1E55B2L, 0xF16F, 0x11CF, { 0x88, 0xCB, 0x00, 0x11, 0x11, 0x00, 0x00, 0x30 } };
static int s_DeviceNotificationsRequested;
static HMODULE cfgmgr32_lib_handle;
static CM_Register_NotificationFunc CM_Register_Notification;
static CM_Unregister_NotificationFunc CM_Unregister_Notification;
static HCMNOTIFICATION s_DeviceNotificationFuncHandle;
static Uint64 s_LastDeviceNotification = 1;
static DWORD CALLBACK SDL_DeviceNotificationFunc(HCMNOTIFICATION hNotify, PVOID context, CM_NOTIFY_ACTION action, PCM_NOTIFY_EVENT_DATA eventData, DWORD event_data_size)
{
if (action == CM_NOTIFY_ACTION_DEVICEINTERFACEARRIVAL ||
action == CM_NOTIFY_ACTION_DEVICEINTERFACEREMOVAL) {
s_LastDeviceNotification = SDL_GetTicksNS();
}
return ERROR_SUCCESS;
}
void WIN_InitDeviceNotification(void)
{
++s_DeviceNotificationsRequested;
if (s_DeviceNotificationsRequested > 1) {
return;
}
cfgmgr32_lib_handle = LoadLibraryA("cfgmgr32.dll");
if (cfgmgr32_lib_handle) {
CM_Register_Notification = (CM_Register_NotificationFunc)GetProcAddress(cfgmgr32_lib_handle, "CM_Register_Notification");
CM_Unregister_Notification = (CM_Unregister_NotificationFunc)GetProcAddress(cfgmgr32_lib_handle, "CM_Unregister_Notification");
if (CM_Register_Notification && CM_Unregister_Notification) {
CM_NOTIFY_FILTER notify_filter;
SDL_zero(notify_filter);
notify_filter.cbSize = sizeof(notify_filter);
notify_filter.FilterType = CM_NOTIFY_FILTER_TYPE_DEVICEINTERFACE;
notify_filter.u.DeviceInterface.ClassGuid = GUID_DEVINTERFACE_HID;
if (CM_Register_Notification(&notify_filter, NULL, SDL_DeviceNotificationFunc, &s_DeviceNotificationFuncHandle) == CR_SUCCESS) {
return;
}
}
}
// FIXME: Should we log errors?
}
Uint64 WIN_GetLastDeviceNotification(void)
{
return s_LastDeviceNotification;
}
void WIN_QuitDeviceNotification(void)
{
if (--s_DeviceNotificationsRequested > 0) {
return;
}
// Make sure we have balanced calls to init/quit
SDL_assert(s_DeviceNotificationsRequested == 0);
if (cfgmgr32_lib_handle) {
if (s_DeviceNotificationFuncHandle && CM_Unregister_Notification) {
CM_Unregister_Notification(s_DeviceNotificationFuncHandle);
s_DeviceNotificationFuncHandle = NULL;
}
FreeLibrary(cfgmgr32_lib_handle);
cfgmgr32_lib_handle = NULL;
}
}
#else
void WIN_InitDeviceNotification(void)
{
}
Uint64 WIN_GetLastDeviceNotification( void )
{
return 0;
}
void WIN_QuitDeviceNotification(void)
{
}
#endif // !SDL_PLATFORM_XBOXONE && !SDL_PLATFORM_XBOXSERIES

215
thirdparty/sdl/core/windows/SDL_hid.h vendored Normal file
View file

@ -0,0 +1,215 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#ifndef SDL_hid_h_
#define SDL_hid_h_
#include "SDL_windows.h"
typedef LONG NTSTATUS;
typedef USHORT USAGE;
typedef struct _HIDP_PREPARSED_DATA *PHIDP_PREPARSED_DATA;
typedef struct _HIDD_ATTRIBUTES
{
ULONG Size;
USHORT VendorID;
USHORT ProductID;
USHORT VersionNumber;
} HIDD_ATTRIBUTES, *PHIDD_ATTRIBUTES;
typedef enum
{
HidP_Input = 0,
HidP_Output = 1,
HidP_Feature = 2
} HIDP_REPORT_TYPE;
typedef struct
{
USAGE UsagePage;
UCHAR ReportID;
BOOLEAN IsAlias;
USHORT BitField;
USHORT LinkCollection;
USAGE LinkUsage;
USAGE LinkUsagePage;
BOOLEAN IsRange;
BOOLEAN IsStringRange;
BOOLEAN IsDesignatorRange;
BOOLEAN IsAbsolute;
ULONG Reserved[10];
union
{
struct
{
USAGE UsageMin;
USAGE UsageMax;
USHORT StringMin;
USHORT StringMax;
USHORT DesignatorMin;
USHORT DesignatorMax;
USHORT DataIndexMin;
USHORT DataIndexMax;
} Range;
struct
{
USAGE Usage;
USAGE Reserved1;
USHORT StringIndex;
USHORT Reserved2;
USHORT DesignatorIndex;
USHORT Reserved3;
USHORT DataIndex;
USHORT Reserved4;
} NotRange;
};
} HIDP_BUTTON_CAPS, *PHIDP_BUTTON_CAPS;
typedef struct
{
USAGE UsagePage;
UCHAR ReportID;
BOOLEAN IsAlias;
USHORT BitField;
USHORT LinkCollection;
USAGE LinkUsage;
USAGE LinkUsagePage;
BOOLEAN IsRange;
BOOLEAN IsStringRange;
BOOLEAN IsDesignatorRange;
BOOLEAN IsAbsolute;
BOOLEAN HasNull;
UCHAR Reserved;
USHORT BitSize;
USHORT ReportCount;
USHORT Reserved2[5];
ULONG UnitsExp;
ULONG Units;
LONG LogicalMin;
LONG LogicalMax;
LONG PhysicalMin;
LONG PhysicalMax;
union
{
struct
{
USAGE UsageMin;
USAGE UsageMax;
USHORT StringMin;
USHORT StringMax;
USHORT DesignatorMin;
USHORT DesignatorMax;
USHORT DataIndexMin;
USHORT DataIndexMax;
} Range;
struct
{
USAGE Usage;
USAGE Reserved1;
USHORT StringIndex;
USHORT Reserved2;
USHORT DesignatorIndex;
USHORT Reserved3;
USHORT DataIndex;
USHORT Reserved4;
} NotRange;
};
} HIDP_VALUE_CAPS, *PHIDP_VALUE_CAPS;
typedef struct
{
USAGE Usage;
USAGE UsagePage;
USHORT InputReportByteLength;
USHORT OutputReportByteLength;
USHORT FeatureReportByteLength;
USHORT Reserved[17];
USHORT NumberLinkCollectionNodes;
USHORT NumberInputButtonCaps;
USHORT NumberInputValueCaps;
USHORT NumberInputDataIndices;
USHORT NumberOutputButtonCaps;
USHORT NumberOutputValueCaps;
USHORT NumberOutputDataIndices;
USHORT NumberFeatureButtonCaps;
USHORT NumberFeatureValueCaps;
USHORT NumberFeatureDataIndices;
} HIDP_CAPS, *PHIDP_CAPS;
typedef struct
{
USHORT DataIndex;
USHORT Reserved;
union
{
ULONG RawValue;
BOOLEAN On;
};
} HIDP_DATA, *PHIDP_DATA;
#define HIDP_ERROR_CODES(p1, p2) ((NTSTATUS)(((p1) << 28) | (0x11 << 16) | (p2)))
#define HIDP_STATUS_SUCCESS HIDP_ERROR_CODES(0x0, 0x0000)
#define HIDP_STATUS_NULL HIDP_ERROR_CODES(0x8, 0x0001)
#define HIDP_STATUS_INVALID_PREPARSED_DATA HIDP_ERROR_CODES(0xC, 0x0001)
#define HIDP_STATUS_INVALID_REPORT_TYPE HIDP_ERROR_CODES(0xC, 0x0002)
#define HIDP_STATUS_INVALID_REPORT_LENGTH HIDP_ERROR_CODES(0xC, 0x0003)
#define HIDP_STATUS_USAGE_NOT_FOUND HIDP_ERROR_CODES(0xC, 0x0004)
#define HIDP_STATUS_VALUE_OUT_OF_RANGE HIDP_ERROR_CODES(0xC, 0x0005)
#define HIDP_STATUS_BAD_LOG_PHY_VALUES HIDP_ERROR_CODES(0xC, 0x0006)
#define HIDP_STATUS_BUFFER_TOO_SMALL HIDP_ERROR_CODES(0xC, 0x0007)
#define HIDP_STATUS_INTERNAL_ERROR HIDP_ERROR_CODES(0xC, 0x0008)
#define HIDP_STATUS_I8042_TRANS_UNKNOWN HIDP_ERROR_CODES(0xC, 0x0009)
#define HIDP_STATUS_INCOMPATIBLE_REPORT_ID HIDP_ERROR_CODES(0xC, 0x000A)
#define HIDP_STATUS_NOT_VALUE_ARRAY HIDP_ERROR_CODES(0xC, 0x000B)
#define HIDP_STATUS_IS_VALUE_ARRAY HIDP_ERROR_CODES(0xC, 0x000C)
#define HIDP_STATUS_DATA_INDEX_NOT_FOUND HIDP_ERROR_CODES(0xC, 0x000D)
#define HIDP_STATUS_DATA_INDEX_OUT_OF_RANGE HIDP_ERROR_CODES(0xC, 0x000E)
#define HIDP_STATUS_BUTTON_NOT_PRESSED HIDP_ERROR_CODES(0xC, 0x000F)
#define HIDP_STATUS_REPORT_DOES_NOT_EXIST HIDP_ERROR_CODES(0xC, 0x0010)
#define HIDP_STATUS_NOT_IMPLEMENTED HIDP_ERROR_CODES(0xC, 0x0020)
extern bool WIN_LoadHIDDLL(void);
extern void WIN_UnloadHIDDLL(void);
typedef BOOLEAN (WINAPI *HidD_GetAttributes_t)(HANDLE HidDeviceObject, PHIDD_ATTRIBUTES Attributes);
typedef BOOLEAN (WINAPI *HidD_GetString_t)(HANDLE HidDeviceObject, PVOID Buffer, ULONG BufferLength);
typedef NTSTATUS (WINAPI *HidP_GetCaps_t)(PHIDP_PREPARSED_DATA PreparsedData, PHIDP_CAPS Capabilities);
typedef NTSTATUS (WINAPI *HidP_GetButtonCaps_t)(HIDP_REPORT_TYPE ReportType, PHIDP_BUTTON_CAPS ButtonCaps, PUSHORT ButtonCapsLength, PHIDP_PREPARSED_DATA PreparsedData);
typedef NTSTATUS (WINAPI *HidP_GetValueCaps_t)(HIDP_REPORT_TYPE ReportType, PHIDP_VALUE_CAPS ValueCaps, PUSHORT ValueCapsLength, PHIDP_PREPARSED_DATA PreparsedData);
typedef ULONG (WINAPI *HidP_MaxDataListLength_t)(HIDP_REPORT_TYPE ReportType, PHIDP_PREPARSED_DATA PreparsedData);
typedef NTSTATUS (WINAPI *HidP_GetData_t)(HIDP_REPORT_TYPE ReportType, PHIDP_DATA DataList, PULONG DataLength, PHIDP_PREPARSED_DATA PreparsedData, PCHAR Report, ULONG ReportLength);
extern HidD_GetAttributes_t SDL_HidD_GetAttributes;
extern HidD_GetString_t SDL_HidD_GetManufacturerString;
extern HidD_GetString_t SDL_HidD_GetProductString;
extern HidP_GetCaps_t SDL_HidP_GetCaps;
extern HidP_GetButtonCaps_t SDL_HidP_GetButtonCaps;
extern HidP_GetValueCaps_t SDL_HidP_GetValueCaps;
extern HidP_MaxDataListLength_t SDL_HidP_MaxDataListLength;
extern HidP_GetData_t SDL_HidP_GetData;
void WIN_InitDeviceNotification(void);
Uint64 WIN_GetLastDeviceNotification(void);
void WIN_QuitDeviceNotification(void);
#endif // SDL_hid_h_

View file

@ -0,0 +1,434 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#if defined(SDL_PLATFORM_WINDOWS) && defined(HAVE_MMDEVICEAPI_H)
#include "SDL_windows.h"
#include "SDL_immdevice.h"
#include "../../audio/SDL_sysaudio.h"
#include <objbase.h> // For CLSIDFromString
typedef struct SDL_IMMDevice_HandleData
{
LPWSTR immdevice_id;
GUID directsound_guid;
} SDL_IMMDevice_HandleData;
static const ERole SDL_IMMDevice_role = eConsole; // !!! FIXME: should this be eMultimedia? Should be a hint?
// This is global to the WASAPI target, to handle hotplug and default device lookup.
static IMMDeviceEnumerator *enumerator = NULL;
static SDL_IMMDevice_callbacks immcallbacks;
// PropVariantInit() is an inline function/macro in PropIdl.h that calls the C runtime's memset() directly. Use ours instead, to avoid dependency.
#ifdef PropVariantInit
#undef PropVariantInit
#endif
#define PropVariantInit(p) SDL_zerop(p)
// Some GUIDs we need to know without linking to libraries that aren't available before Vista.
/* *INDENT-OFF* */ // clang-format off
static const CLSID SDL_CLSID_MMDeviceEnumerator = { 0xbcde0395, 0xe52f, 0x467c,{ 0x8e, 0x3d, 0xc4, 0x57, 0x92, 0x91, 0x69, 0x2e } };
static const IID SDL_IID_IMMDeviceEnumerator = { 0xa95664d2, 0x9614, 0x4f35,{ 0xa7, 0x46, 0xde, 0x8d, 0xb6, 0x36, 0x17, 0xe6 } };
static const IID SDL_IID_IMMNotificationClient = { 0x7991eec9, 0x7e89, 0x4d85,{ 0x83, 0x90, 0x6c, 0x70, 0x3c, 0xec, 0x60, 0xc0 } };
static const IID SDL_IID_IMMEndpoint = { 0x1be09788, 0x6894, 0x4089,{ 0x85, 0x86, 0x9a, 0x2a, 0x6c, 0x26, 0x5a, 0xc5 } };
static const PROPERTYKEY SDL_PKEY_Device_FriendlyName = { { 0xa45c254e, 0xdf1c, 0x4efd,{ 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, } }, 14 };
static const PROPERTYKEY SDL_PKEY_AudioEngine_DeviceFormat = { { 0xf19f064d, 0x82c, 0x4e27,{ 0xbc, 0x73, 0x68, 0x82, 0xa1, 0xbb, 0x8e, 0x4c, } }, 0 };
static const PROPERTYKEY SDL_PKEY_AudioEndpoint_GUID = { { 0x1da5d803, 0xd492, 0x4edd,{ 0x8c, 0x23, 0xe0, 0xc0, 0xff, 0xee, 0x7f, 0x0e, } }, 4 };
/* *INDENT-ON* */ // clang-format on
static bool FindByDevIDCallback(SDL_AudioDevice *device, void *userdata)
{
LPCWSTR devid = (LPCWSTR)userdata;
if (devid && device && device->handle) {
const SDL_IMMDevice_HandleData *handle = (const SDL_IMMDevice_HandleData *)device->handle;
if (handle->immdevice_id && SDL_wcscmp(handle->immdevice_id, devid) == 0) {
return true;
}
}
return false;
}
static SDL_AudioDevice *SDL_IMMDevice_FindByDevID(LPCWSTR devid)
{
return SDL_FindPhysicalAudioDeviceByCallback(FindByDevIDCallback, (void *) devid);
}
LPGUID SDL_IMMDevice_GetDirectSoundGUID(SDL_AudioDevice *device)
{
return (device && device->handle) ? &(((SDL_IMMDevice_HandleData *) device->handle)->directsound_guid) : NULL;
}
LPCWSTR SDL_IMMDevice_GetDevID(SDL_AudioDevice *device)
{
return (device && device->handle) ? ((const SDL_IMMDevice_HandleData *) device->handle)->immdevice_id : NULL;
}
static void GetMMDeviceInfo(IMMDevice *device, char **utf8dev, WAVEFORMATEXTENSIBLE *fmt, GUID *guid)
{
/* PKEY_Device_FriendlyName gives you "Speakers (SoundBlaster Pro)" which drives me nuts. I'd rather it be
"SoundBlaster Pro (Speakers)" but I guess that's developers vs users. Windows uses the FriendlyName in
its own UIs, like Volume Control, etc. */
IPropertyStore *props = NULL;
*utf8dev = NULL;
SDL_zerop(fmt);
if (SUCCEEDED(IMMDevice_OpenPropertyStore(device, STGM_READ, &props))) {
PROPVARIANT var;
PropVariantInit(&var);
if (SUCCEEDED(IPropertyStore_GetValue(props, &SDL_PKEY_Device_FriendlyName, &var))) {
*utf8dev = WIN_StringToUTF8W(var.pwszVal);
}
PropVariantClear(&var);
if (SUCCEEDED(IPropertyStore_GetValue(props, &SDL_PKEY_AudioEngine_DeviceFormat, &var))) {
SDL_memcpy(fmt, var.blob.pBlobData, SDL_min(var.blob.cbSize, sizeof(WAVEFORMATEXTENSIBLE)));
}
PropVariantClear(&var);
if (SUCCEEDED(IPropertyStore_GetValue(props, &SDL_PKEY_AudioEndpoint_GUID, &var))) {
(void)CLSIDFromString(var.pwszVal, guid);
}
PropVariantClear(&var);
IPropertyStore_Release(props);
}
}
void SDL_IMMDevice_FreeDeviceHandle(SDL_AudioDevice *device)
{
if (device && device->handle) {
SDL_IMMDevice_HandleData *handle = (SDL_IMMDevice_HandleData *) device->handle;
SDL_free(handle->immdevice_id);
SDL_free(handle);
device->handle = NULL;
}
}
static SDL_AudioDevice *SDL_IMMDevice_Add(const bool recording, const char *devname, WAVEFORMATEXTENSIBLE *fmt, LPCWSTR devid, GUID *dsoundguid)
{
/* You can have multiple endpoints on a device that are mutually exclusive ("Speakers" vs "Line Out" or whatever).
In a perfect world, things that are unplugged won't be in this collection. The only gotcha is probably for
phones and tablets, where you might have an internal speaker and a headphone jack and expect both to be
available and switch automatically. (!!! FIXME...?) */
if (!devname) {
return NULL;
}
// see if we already have this one first.
SDL_AudioDevice *device = SDL_IMMDevice_FindByDevID(devid);
if (device) {
if (SDL_GetAtomicInt(&device->zombie)) {
// whoa, it came back! This can happen if you unplug and replug USB headphones while we're still keeping the SDL object alive.
// Kill this device's IMMDevice id; the device will go away when the app closes it, or maybe a new default device is chosen
// (possibly this reconnected device), so we just want to make sure IMMDevice doesn't try to find the old device by the existing ID string.
SDL_IMMDevice_HandleData *handle = (SDL_IMMDevice_HandleData *) device->handle;
SDL_free(handle->immdevice_id);
handle->immdevice_id = NULL;
device = NULL; // add a new device, below.
}
}
if (!device) {
// handle is freed by SDL_IMMDevice_FreeDeviceHandle!
SDL_IMMDevice_HandleData *handle = (SDL_IMMDevice_HandleData *)SDL_malloc(sizeof(SDL_IMMDevice_HandleData));
if (!handle) {
return NULL;
}
handle->immdevice_id = SDL_wcsdup(devid);
if (!handle->immdevice_id) {
SDL_free(handle);
return NULL;
}
SDL_memcpy(&handle->directsound_guid, dsoundguid, sizeof(GUID));
SDL_AudioSpec spec;
SDL_zero(spec);
spec.channels = (Uint8)fmt->Format.nChannels;
spec.freq = fmt->Format.nSamplesPerSec;
spec.format = SDL_WaveFormatExToSDLFormat((WAVEFORMATEX *)fmt);
device = SDL_AddAudioDevice(recording, devname, &spec, handle);
if (!device) {
SDL_free(handle->immdevice_id);
SDL_free(handle);
}
}
return device;
}
/* We need a COM subclass of IMMNotificationClient for hotplug support, which is
easy in C++, but we have to tapdance more to make work in C.
Thanks to this page for coaching on how to make this work:
https://www.codeproject.com/Articles/13601/COM-in-plain-C */
typedef struct SDLMMNotificationClient
{
const IMMNotificationClientVtbl *lpVtbl;
SDL_AtomicInt refcount;
} SDLMMNotificationClient;
static HRESULT STDMETHODCALLTYPE SDLMMNotificationClient_QueryInterface(IMMNotificationClient *client, REFIID iid, void **ppv)
{
if ((WIN_IsEqualIID(iid, &IID_IUnknown)) || (WIN_IsEqualIID(iid, &SDL_IID_IMMNotificationClient))) {
*ppv = client;
client->lpVtbl->AddRef(client);
return S_OK;
}
*ppv = NULL;
return E_NOINTERFACE;
}
static ULONG STDMETHODCALLTYPE SDLMMNotificationClient_AddRef(IMMNotificationClient *iclient)
{
SDLMMNotificationClient *client = (SDLMMNotificationClient *)iclient;
return (ULONG)(SDL_AtomicIncRef(&client->refcount) + 1);
}
static ULONG STDMETHODCALLTYPE SDLMMNotificationClient_Release(IMMNotificationClient *iclient)
{
// client is a static object; we don't ever free it.
SDLMMNotificationClient *client = (SDLMMNotificationClient *)iclient;
const ULONG rc = SDL_AtomicDecRef(&client->refcount);
if (rc == 0) {
SDL_SetAtomicInt(&client->refcount, 0); // uhh...
return 0;
}
return rc - 1;
}
// These are the entry points called when WASAPI device endpoints change.
static HRESULT STDMETHODCALLTYPE SDLMMNotificationClient_OnDefaultDeviceChanged(IMMNotificationClient *iclient, EDataFlow flow, ERole role, LPCWSTR pwstrDeviceId)
{
if (role == SDL_IMMDevice_role) {
immcallbacks.default_audio_device_changed(SDL_IMMDevice_FindByDevID(pwstrDeviceId));
}
return S_OK;
}
static HRESULT STDMETHODCALLTYPE SDLMMNotificationClient_OnDeviceAdded(IMMNotificationClient *iclient, LPCWSTR pwstrDeviceId)
{
/* we ignore this; devices added here then progress to ACTIVE, if appropriate, in
OnDeviceStateChange, making that a better place to deal with device adds. More
importantly: the first time you plug in a USB audio device, this callback will
fire, but when you unplug it, it isn't removed (it's state changes to NOTPRESENT).
Plugging it back in won't fire this callback again. */
return S_OK;
}
static HRESULT STDMETHODCALLTYPE SDLMMNotificationClient_OnDeviceRemoved(IMMNotificationClient *iclient, LPCWSTR pwstrDeviceId)
{
return S_OK; // See notes in OnDeviceAdded handler about why we ignore this.
}
static HRESULT STDMETHODCALLTYPE SDLMMNotificationClient_OnDeviceStateChanged(IMMNotificationClient *iclient, LPCWSTR pwstrDeviceId, DWORD dwNewState)
{
IMMDevice *device = NULL;
if (SUCCEEDED(IMMDeviceEnumerator_GetDevice(enumerator, pwstrDeviceId, &device))) {
IMMEndpoint *endpoint = NULL;
if (SUCCEEDED(IMMDevice_QueryInterface(device, &SDL_IID_IMMEndpoint, (void **)&endpoint))) {
EDataFlow flow;
if (SUCCEEDED(IMMEndpoint_GetDataFlow(endpoint, &flow))) {
const bool recording = (flow == eCapture);
if (dwNewState == DEVICE_STATE_ACTIVE) {
char *utf8dev;
WAVEFORMATEXTENSIBLE fmt;
GUID dsoundguid;
GetMMDeviceInfo(device, &utf8dev, &fmt, &dsoundguid);
if (utf8dev) {
SDL_IMMDevice_Add(recording, utf8dev, &fmt, pwstrDeviceId, &dsoundguid);
SDL_free(utf8dev);
}
} else {
immcallbacks.audio_device_disconnected(SDL_IMMDevice_FindByDevID(pwstrDeviceId));
}
}
IMMEndpoint_Release(endpoint);
}
IMMDevice_Release(device);
}
return S_OK;
}
static HRESULT STDMETHODCALLTYPE SDLMMNotificationClient_OnPropertyValueChanged(IMMNotificationClient *client, LPCWSTR pwstrDeviceId, const PROPERTYKEY key)
{
return S_OK; // we don't care about these.
}
static const IMMNotificationClientVtbl notification_client_vtbl = {
SDLMMNotificationClient_QueryInterface,
SDLMMNotificationClient_AddRef,
SDLMMNotificationClient_Release,
SDLMMNotificationClient_OnDeviceStateChanged,
SDLMMNotificationClient_OnDeviceAdded,
SDLMMNotificationClient_OnDeviceRemoved,
SDLMMNotificationClient_OnDefaultDeviceChanged,
SDLMMNotificationClient_OnPropertyValueChanged
};
static SDLMMNotificationClient notification_client = { &notification_client_vtbl, { 1 } };
bool SDL_IMMDevice_Init(const SDL_IMMDevice_callbacks *callbacks)
{
HRESULT ret;
// just skip the discussion with COM here.
if (!WIN_IsWindowsVistaOrGreater()) {
return SDL_SetError("IMMDevice support requires Windows Vista or later");
}
if (FAILED(WIN_CoInitialize())) {
return SDL_SetError("IMMDevice: CoInitialize() failed");
}
ret = CoCreateInstance(&SDL_CLSID_MMDeviceEnumerator, NULL, CLSCTX_INPROC_SERVER, &SDL_IID_IMMDeviceEnumerator, (LPVOID *)&enumerator);
if (FAILED(ret)) {
WIN_CoUninitialize();
return WIN_SetErrorFromHRESULT("IMMDevice CoCreateInstance(MMDeviceEnumerator)", ret);
}
if (callbacks) {
SDL_copyp(&immcallbacks, callbacks);
} else {
SDL_zero(immcallbacks);
}
if (!immcallbacks.audio_device_disconnected) {
immcallbacks.audio_device_disconnected = SDL_AudioDeviceDisconnected;
}
if (!immcallbacks.default_audio_device_changed) {
immcallbacks.default_audio_device_changed = SDL_DefaultAudioDeviceChanged;
}
return true;
}
void SDL_IMMDevice_Quit(void)
{
if (enumerator) {
IMMDeviceEnumerator_UnregisterEndpointNotificationCallback(enumerator, (IMMNotificationClient *)&notification_client);
IMMDeviceEnumerator_Release(enumerator);
enumerator = NULL;
}
SDL_zero(immcallbacks);
WIN_CoUninitialize();
}
bool SDL_IMMDevice_Get(SDL_AudioDevice *device, IMMDevice **immdevice, bool recording)
{
const Uint64 timeout = SDL_GetTicks() + 8000; // intel's audio drivers can fail for up to EIGHT SECONDS after a device is connected or we wake from sleep.
SDL_assert(device != NULL);
SDL_assert(immdevice != NULL);
LPCWSTR devid = SDL_IMMDevice_GetDevID(device);
SDL_assert(devid != NULL);
HRESULT ret;
while ((ret = IMMDeviceEnumerator_GetDevice(enumerator, devid, immdevice)) == E_NOTFOUND) {
const Uint64 now = SDL_GetTicks();
if (timeout > now) {
const Uint64 ticksleft = timeout - now;
SDL_Delay((Uint32)SDL_min(ticksleft, 300)); // wait awhile and try again.
continue;
}
break;
}
if (!SUCCEEDED(ret)) {
return WIN_SetErrorFromHRESULT("WASAPI can't find requested audio endpoint", ret);
}
return true;
}
static void EnumerateEndpointsForFlow(const bool recording, SDL_AudioDevice **default_device)
{
/* Note that WASAPI separates "adapter devices" from "audio endpoint devices"
...one adapter device ("SoundBlaster Pro") might have multiple endpoint devices ("Speakers", "Line-Out"). */
IMMDeviceCollection *collection = NULL;
if (FAILED(IMMDeviceEnumerator_EnumAudioEndpoints(enumerator, recording ? eCapture : eRender, DEVICE_STATE_ACTIVE, &collection))) {
return;
}
UINT total = 0;
if (FAILED(IMMDeviceCollection_GetCount(collection, &total))) {
IMMDeviceCollection_Release(collection);
return;
}
LPWSTR default_devid = NULL;
if (default_device) {
IMMDevice *default_immdevice = NULL;
const EDataFlow dataflow = recording ? eCapture : eRender;
if (SUCCEEDED(IMMDeviceEnumerator_GetDefaultAudioEndpoint(enumerator, dataflow, SDL_IMMDevice_role, &default_immdevice))) {
LPWSTR devid = NULL;
if (SUCCEEDED(IMMDevice_GetId(default_immdevice, &devid))) {
default_devid = SDL_wcsdup(devid); // if this fails, oh well.
CoTaskMemFree(devid);
}
IMMDevice_Release(default_immdevice);
}
}
for (UINT i = 0; i < total; i++) {
IMMDevice *immdevice = NULL;
if (SUCCEEDED(IMMDeviceCollection_Item(collection, i, &immdevice))) {
LPWSTR devid = NULL;
if (SUCCEEDED(IMMDevice_GetId(immdevice, &devid))) {
char *devname = NULL;
WAVEFORMATEXTENSIBLE fmt;
GUID dsoundguid;
SDL_zero(fmt);
SDL_zero(dsoundguid);
GetMMDeviceInfo(immdevice, &devname, &fmt, &dsoundguid);
if (devname) {
SDL_AudioDevice *sdldevice = SDL_IMMDevice_Add(recording, devname, &fmt, devid, &dsoundguid);
if (default_device && default_devid && SDL_wcscmp(default_devid, devid) == 0) {
*default_device = sdldevice;
}
SDL_free(devname);
}
CoTaskMemFree(devid);
}
IMMDevice_Release(immdevice);
}
}
SDL_free(default_devid);
IMMDeviceCollection_Release(collection);
}
void SDL_IMMDevice_EnumerateEndpoints(SDL_AudioDevice **default_playback, SDL_AudioDevice **default_recording)
{
EnumerateEndpointsForFlow(false, default_playback);
EnumerateEndpointsForFlow(true, default_recording);
// if this fails, we just won't get hotplug events. Carry on anyhow.
IMMDeviceEnumerator_RegisterEndpointNotificationCallback(enumerator, (IMMNotificationClient *)&notification_client);
}
#endif // defined(SDL_PLATFORM_WINDOWS) && defined(HAVE_MMDEVICEAPI_H)

View file

@ -0,0 +1,45 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#ifndef SDL_IMMDEVICE_H
#define SDL_IMMDEVICE_H
#define COBJMACROS
#include <mmdeviceapi.h>
#include <mmreg.h>
struct SDL_AudioDevice; // defined in src/audio/SDL_sysaudio.h
typedef struct SDL_IMMDevice_callbacks
{
void (*audio_device_disconnected)(struct SDL_AudioDevice *device);
void (*default_audio_device_changed)(struct SDL_AudioDevice *new_default_device);
} SDL_IMMDevice_callbacks;
bool SDL_IMMDevice_Init(const SDL_IMMDevice_callbacks *callbacks);
void SDL_IMMDevice_Quit(void);
bool SDL_IMMDevice_Get(struct SDL_AudioDevice *device, IMMDevice **immdevice, bool recording);
void SDL_IMMDevice_EnumerateEndpoints(struct SDL_AudioDevice **default_playback, struct SDL_AudioDevice **default_recording);
LPGUID SDL_IMMDevice_GetDirectSoundGUID(struct SDL_AudioDevice *device);
LPCWSTR SDL_IMMDevice_GetDevID(struct SDL_AudioDevice *device);
void SDL_IMMDevice_FreeDeviceHandle(struct SDL_AudioDevice *device);
#endif // SDL_IMMDEVICE_H

View file

@ -0,0 +1,375 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#if defined(SDL_PLATFORM_WINDOWS)
#include "SDL_windows.h"
#include <objbase.h> // for CoInitialize/CoUninitialize (Win32 only)
#ifdef HAVE_ROAPI_H
#include <roapi.h> // For RoInitialize/RoUninitialize (Win32 only)
#else
typedef enum RO_INIT_TYPE
{
RO_INIT_SINGLETHREADED = 0,
RO_INIT_MULTITHREADED = 1
} RO_INIT_TYPE;
#endif
#ifndef _WIN32_WINNT_VISTA
#define _WIN32_WINNT_VISTA 0x0600
#endif
#ifndef _WIN32_WINNT_WIN7
#define _WIN32_WINNT_WIN7 0x0601
#endif
#ifndef _WIN32_WINNT_WIN8
#define _WIN32_WINNT_WIN8 0x0602
#endif
#ifndef LOAD_LIBRARY_SEARCH_SYSTEM32
#define LOAD_LIBRARY_SEARCH_SYSTEM32 0x00000800
#endif
#ifndef WC_ERR_INVALID_CHARS
#define WC_ERR_INVALID_CHARS 0x00000080
#endif
// Sets an error message based on an HRESULT
bool WIN_SetErrorFromHRESULT(const char *prefix, HRESULT hr)
{
TCHAR buffer[1024];
char *message;
TCHAR *p = buffer;
DWORD c = FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, hr, 0,
buffer, SDL_arraysize(buffer), NULL);
buffer[c] = 0;
// kill CR/LF that FormatMessage() sticks at the end
while (*p) {
if (*p == '\r') {
*p = 0;
break;
}
++p;
}
message = WIN_StringToUTF8(buffer);
SDL_SetError("%s%s%s", prefix ? prefix : "", prefix ? ": " : "", message);
SDL_free(message);
return false;
}
// Sets an error message based on GetLastError()
bool WIN_SetError(const char *prefix)
{
return WIN_SetErrorFromHRESULT(prefix, GetLastError());
}
HRESULT
WIN_CoInitialize(void)
{
/* SDL handles any threading model, so initialize with the default, which
is compatible with OLE and if that doesn't work, try multi-threaded mode.
If you need multi-threaded mode, call CoInitializeEx() before SDL_Init()
*/
#if defined(SDL_PLATFORM_XBOXONE) || defined(SDL_PLATFORM_XBOXSERIES)
// On Xbox, there's no need to call CoInitializeEx (and it's not implemented)
return S_OK;
#else
HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
if (hr == RPC_E_CHANGED_MODE) {
hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
}
// S_FALSE means success, but someone else already initialized.
// You still need to call CoUninitialize in this case!
if (hr == S_FALSE) {
return S_OK;
}
return hr;
#endif
}
void WIN_CoUninitialize(void)
{
CoUninitialize();
}
FARPROC WIN_LoadComBaseFunction(const char *name)
{
static bool s_bLoaded;
static HMODULE s_hComBase;
if (!s_bLoaded) {
s_hComBase = LoadLibraryEx(TEXT("combase.dll"), NULL, LOAD_LIBRARY_SEARCH_SYSTEM32);
s_bLoaded = true;
}
if (s_hComBase) {
return GetProcAddress(s_hComBase, name);
} else {
return NULL;
}
}
HRESULT
WIN_RoInitialize(void)
{
typedef HRESULT(WINAPI * RoInitialize_t)(RO_INIT_TYPE initType);
RoInitialize_t RoInitializeFunc = (RoInitialize_t)WIN_LoadComBaseFunction("RoInitialize");
if (RoInitializeFunc) {
// RO_INIT_SINGLETHREADED is equivalent to COINIT_APARTMENTTHREADED
HRESULT hr = RoInitializeFunc(RO_INIT_SINGLETHREADED);
if (hr == RPC_E_CHANGED_MODE) {
hr = RoInitializeFunc(RO_INIT_MULTITHREADED);
}
// S_FALSE means success, but someone else already initialized.
// You still need to call RoUninitialize in this case!
if (hr == S_FALSE) {
return S_OK;
}
return hr;
} else {
return E_NOINTERFACE;
}
}
void WIN_RoUninitialize(void)
{
typedef void(WINAPI * RoUninitialize_t)(void);
RoUninitialize_t RoUninitializeFunc = (RoUninitialize_t)WIN_LoadComBaseFunction("RoUninitialize");
if (RoUninitializeFunc) {
RoUninitializeFunc();
}
}
#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
static BOOL IsWindowsVersionOrGreater(WORD wMajorVersion, WORD wMinorVersion, WORD wServicePackMajor)
{
OSVERSIONINFOEXW osvi;
DWORDLONG const dwlConditionMask = VerSetConditionMask(
VerSetConditionMask(
VerSetConditionMask(
0, VER_MAJORVERSION, VER_GREATER_EQUAL),
VER_MINORVERSION, VER_GREATER_EQUAL),
VER_SERVICEPACKMAJOR, VER_GREATER_EQUAL);
SDL_zero(osvi);
osvi.dwOSVersionInfoSize = sizeof(osvi);
osvi.dwMajorVersion = wMajorVersion;
osvi.dwMinorVersion = wMinorVersion;
osvi.wServicePackMajor = wServicePackMajor;
return VerifyVersionInfoW(&osvi, VER_MAJORVERSION | VER_MINORVERSION | VER_SERVICEPACKMAJOR, dwlConditionMask) != FALSE;
}
#endif
// apply some static variables so we only call into the Win32 API once per process for each check.
#if defined(SDL_PLATFORM_XBOXONE) || defined(SDL_PLATFORM_XBOXSERIES)
#define CHECKWINVER(notdesktop_platform_result, test) return (notdesktop_platform_result);
#else
#define CHECKWINVER(notdesktop_platform_result, test) \
static bool checked = false; \
static BOOL result = FALSE; \
if (!checked) { \
result = (test); \
checked = true; \
} \
return result;
#endif
// this is the oldest thing we run on (and we may lose support for this in SDL3 at any time!),
// so there's no "OrGreater" as that would always be TRUE. The other functions are here to
// ask "can we support a specific feature?" but this function is here to ask "do we need to do
// something different for an OS version we probably should abandon?" :)
BOOL WIN_IsWindowsXP(void)
{
CHECKWINVER(FALSE, !WIN_IsWindowsVistaOrGreater() && IsWindowsVersionOrGreater(HIBYTE(_WIN32_WINNT_WINXP), LOBYTE(_WIN32_WINNT_WINXP), 0));
}
BOOL WIN_IsWindowsVistaOrGreater(void)
{
CHECKWINVER(TRUE, IsWindowsVersionOrGreater(HIBYTE(_WIN32_WINNT_VISTA), LOBYTE(_WIN32_WINNT_VISTA), 0));
}
BOOL WIN_IsWindows7OrGreater(void)
{
CHECKWINVER(TRUE, IsWindowsVersionOrGreater(HIBYTE(_WIN32_WINNT_WIN7), LOBYTE(_WIN32_WINNT_WIN7), 0));
}
BOOL WIN_IsWindows8OrGreater(void)
{
CHECKWINVER(TRUE, IsWindowsVersionOrGreater(HIBYTE(_WIN32_WINNT_WIN8), LOBYTE(_WIN32_WINNT_WIN8), 0));
}
#undef CHECKWINVER
/*
WAVExxxCAPS gives you 31 bytes for the device name, and just truncates if it's
longer. However, since WinXP, you can use the WAVExxxCAPS2 structure, which
will give you a name GUID. The full name is in the Windows Registry under
that GUID, located here: HKLM\System\CurrentControlSet\Control\MediaCategories
Note that drivers can report GUID_NULL for the name GUID, in which case,
Windows makes a best effort to fill in those 31 bytes in the usual place.
This info summarized from MSDN:
http://web.archive.org/web/20131027093034/http://msdn.microsoft.com/en-us/library/windows/hardware/ff536382(v=vs.85).aspx
Always look this up in the registry if possible, because the strings are
different! At least on Win10, I see "Yeti Stereo Microphone" in the
Registry, and a unhelpful "Microphone(Yeti Stereo Microph" in winmm. Sigh.
(Also, DirectSound shouldn't be limited to 32 chars, but its device enum
has the same problem.)
WASAPI doesn't need this. This is just for DirectSound/WinMM.
*/
char *WIN_LookupAudioDeviceName(const WCHAR *name, const GUID *guid)
{
#if defined(SDL_PLATFORM_XBOXONE) || defined(SDL_PLATFORM_XBOXSERIES)
return WIN_StringToUTF8W(name); // No registry access on Xbox, go with what we've got.
#else
static const GUID nullguid = { 0 };
const unsigned char *ptr;
char keystr[128];
WCHAR *strw = NULL;
bool rc;
HKEY hkey;
DWORD len = 0;
char *result = NULL;
if (WIN_IsEqualGUID(guid, &nullguid)) {
return WIN_StringToUTF8(name); // No GUID, go with what we've got.
}
ptr = (const unsigned char *)guid;
(void)SDL_snprintf(keystr, sizeof(keystr),
"System\\CurrentControlSet\\Control\\MediaCategories\\{%02X%02X%02X%02X-%02X%02X-%02X%02X-%02X%02X-%02X%02X%02X%02X%02X%02X}",
ptr[3], ptr[2], ptr[1], ptr[0], ptr[5], ptr[4], ptr[7], ptr[6],
ptr[8], ptr[9], ptr[10], ptr[11], ptr[12], ptr[13], ptr[14], ptr[15]);
strw = WIN_UTF8ToString(keystr);
rc = (RegOpenKeyExW(HKEY_LOCAL_MACHINE, strw, 0, KEY_QUERY_VALUE, &hkey) == ERROR_SUCCESS);
SDL_free(strw);
if (!rc) {
return WIN_StringToUTF8(name); // oh well.
}
rc = (RegQueryValueExW(hkey, L"Name", NULL, NULL, NULL, &len) == ERROR_SUCCESS);
if (!rc) {
RegCloseKey(hkey);
return WIN_StringToUTF8(name); // oh well.
}
strw = (WCHAR *)SDL_malloc(len + sizeof(WCHAR));
if (!strw) {
RegCloseKey(hkey);
return WIN_StringToUTF8(name); // oh well.
}
rc = (RegQueryValueExW(hkey, L"Name", NULL, NULL, (LPBYTE)strw, &len) == ERROR_SUCCESS);
RegCloseKey(hkey);
if (!rc) {
SDL_free(strw);
return WIN_StringToUTF8(name); // oh well.
}
strw[len / 2] = 0; // make sure it's null-terminated.
result = WIN_StringToUTF8(strw);
SDL_free(strw);
return result ? result : WIN_StringToUTF8(name);
#endif
}
BOOL WIN_IsEqualGUID(const GUID *a, const GUID *b)
{
return (SDL_memcmp(a, b, sizeof(*a)) == 0);
}
BOOL WIN_IsEqualIID(REFIID a, REFIID b)
{
return (SDL_memcmp(a, b, sizeof(*a)) == 0);
}
void WIN_RECTToRect(const RECT *winrect, SDL_Rect *sdlrect)
{
sdlrect->x = winrect->left;
sdlrect->w = (winrect->right - winrect->left) + 1;
sdlrect->y = winrect->top;
sdlrect->h = (winrect->bottom - winrect->top) + 1;
}
void WIN_RectToRECT(const SDL_Rect *sdlrect, RECT *winrect)
{
winrect->left = sdlrect->x;
winrect->right = sdlrect->x + sdlrect->w - 1;
winrect->top = sdlrect->y;
winrect->bottom = sdlrect->y + sdlrect->h - 1;
}
bool WIN_WindowRectValid(const RECT *rect)
{
// A window can be resized to zero height, but not zero width
return (rect->right > 0);
}
// Some GUIDs we need to know without linking to libraries that aren't available before Vista.
/* *INDENT-OFF* */ // clang-format off
static const GUID SDL_KSDATAFORMAT_SUBTYPE_PCM = { 0x00000001, 0x0000, 0x0010,{ 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 } };
static const GUID SDL_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT = { 0x00000003, 0x0000, 0x0010,{ 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 } };
/* *INDENT-ON* */ // clang-format on
SDL_AudioFormat SDL_WaveFormatExToSDLFormat(WAVEFORMATEX *waveformat)
{
if ((waveformat->wFormatTag == WAVE_FORMAT_IEEE_FLOAT) && (waveformat->wBitsPerSample == 32)) {
return SDL_AUDIO_F32;
} else if ((waveformat->wFormatTag == WAVE_FORMAT_PCM) && (waveformat->wBitsPerSample == 16)) {
return SDL_AUDIO_S16;
} else if ((waveformat->wFormatTag == WAVE_FORMAT_PCM) && (waveformat->wBitsPerSample == 32)) {
return SDL_AUDIO_S32;
} else if (waveformat->wFormatTag == WAVE_FORMAT_EXTENSIBLE) {
const WAVEFORMATEXTENSIBLE *ext = (const WAVEFORMATEXTENSIBLE *)waveformat;
if ((SDL_memcmp(&ext->SubFormat, &SDL_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, sizeof(GUID)) == 0) && (waveformat->wBitsPerSample == 32)) {
return SDL_AUDIO_F32;
} else if ((SDL_memcmp(&ext->SubFormat, &SDL_KSDATAFORMAT_SUBTYPE_PCM, sizeof(GUID)) == 0) && (waveformat->wBitsPerSample == 16)) {
return SDL_AUDIO_S16;
} else if ((SDL_memcmp(&ext->SubFormat, &SDL_KSDATAFORMAT_SUBTYPE_PCM, sizeof(GUID)) == 0) && (waveformat->wBitsPerSample == 32)) {
return SDL_AUDIO_S32;
}
}
return SDL_AUDIO_UNKNOWN;
}
int WIN_WideCharToMultiByte(UINT CodePage, DWORD dwFlags, LPCWCH lpWideCharStr, int cchWideChar, LPSTR lpMultiByteStr, int cbMultiByte, LPCCH lpDefaultChar, LPBOOL lpUsedDefaultChar)
{
if (WIN_IsWindowsXP()) {
dwFlags &= ~WC_ERR_INVALID_CHARS; // not supported before Vista. Without this flag, it will just replace bogus chars with U+FFFD. You're on your own, WinXP.
}
return WideCharToMultiByte(CodePage, dwFlags, lpWideCharStr, cchWideChar, lpMultiByteStr, cbMultiByte, lpDefaultChar, lpUsedDefaultChar);
}
#endif // defined(SDL_PLATFORM_WINDOWS)

View file

@ -0,0 +1,172 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
// This is an include file for windows.h with the SDL build settings
#ifndef _INCLUDED_WINDOWS_H
#define _INCLUDED_WINDOWS_H
#ifdef SDL_PLATFORM_WIN32
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN 1
#endif
#ifndef STRICT
#define STRICT 1
#endif
#ifndef UNICODE
#define UNICODE 1
#endif
#undef WINVER
#undef _WIN32_WINNT
#if defined(SDL_VIDEO_RENDER_D3D12) || defined(HAVE_DXGI1_6_H)
#define _WIN32_WINNT 0xA00 // For D3D12, 0xA00 is required
#elif defined(HAVE_SHELLSCALINGAPI_H)
#define _WIN32_WINNT 0x603 // For DPI support
#else
#define _WIN32_WINNT 0x501 // Need 0x410 for AlphaBlend() and 0x500 for EnumDisplayDevices(), 0x501 for raw input
#endif
#define WINVER _WIN32_WINNT
#elif defined(SDL_PLATFORM_WINGDK)
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN 1
#endif
#ifndef STRICT
#define STRICT 1
#endif
#ifndef UNICODE
#define UNICODE 1
#endif
#undef WINVER
#undef _WIN32_WINNT
#define _WIN32_WINNT 0xA00
#define WINVER _WIN32_WINNT
#elif defined(SDL_PLATFORM_XBOXONE) || defined(SDL_PLATFORM_XBOXSERIES)
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN 1
#endif
#ifndef STRICT
#define STRICT 1
#endif
#ifndef UNICODE
#define UNICODE 1
#endif
#undef WINVER
#undef _WIN32_WINNT
#define _WIN32_WINNT 0xA00
#define WINVER _WIN32_WINNT
#endif
// See https://github.com/libsdl-org/SDL/pull/7607
// force_align_arg_pointer attribute requires gcc >= 4.2.x.
#if defined(__clang__)
#define HAVE_FORCE_ALIGN_ARG_POINTER
#elif defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 2))
#define HAVE_FORCE_ALIGN_ARG_POINTER
#endif
#if defined(__GNUC__) && defined(__i386__) && defined(HAVE_FORCE_ALIGN_ARG_POINTER)
#define MINGW32_FORCEALIGN __attribute__((force_align_arg_pointer))
#else
#define MINGW32_FORCEALIGN
#endif
#include <windows.h>
#include <basetyps.h> // for REFIID with broken mingw.org headers
#include <mmreg.h>
// Routines to convert from UTF8 to native Windows text
#define WIN_StringToUTF8W(S) SDL_iconv_string("UTF-8", "UTF-16LE", (const char *)(S), (SDL_wcslen(S) + 1) * sizeof(WCHAR))
#define WIN_UTF8ToStringW(S) (WCHAR *)SDL_iconv_string("UTF-16LE", "UTF-8", (const char *)(S), SDL_strlen(S) + 1)
// !!! FIXME: UTF8ToString() can just be a SDL_strdup() here.
#define WIN_StringToUTF8A(S) SDL_iconv_string("UTF-8", "ASCII", (const char *)(S), (SDL_strlen(S) + 1))
#define WIN_UTF8ToStringA(S) SDL_iconv_string("ASCII", "UTF-8", (const char *)(S), SDL_strlen(S) + 1)
#if UNICODE
#define WIN_StringToUTF8 WIN_StringToUTF8W
#define WIN_UTF8ToString WIN_UTF8ToStringW
#define SDL_tcslen SDL_wcslen
#define SDL_tcsstr SDL_wcsstr
#else
#define WIN_StringToUTF8 WIN_StringToUTF8A
#define WIN_UTF8ToString WIN_UTF8ToStringA
#define SDL_tcslen SDL_strlen
#define SDL_tcsstr SDL_strstr
#endif
// Set up for C function definitions, even when using C++
#ifdef __cplusplus
extern "C" {
#endif
// Sets an error message based on a given HRESULT
extern bool WIN_SetErrorFromHRESULT(const char *prefix, HRESULT hr);
// Sets an error message based on GetLastError(). Always returns false.
extern bool WIN_SetError(const char *prefix);
// Load a function from combase.dll
FARPROC WIN_LoadComBaseFunction(const char *name);
// Wrap up the oddities of CoInitialize() into a common function.
extern HRESULT WIN_CoInitialize(void);
extern void WIN_CoUninitialize(void);
// Wrap up the oddities of RoInitialize() into a common function.
extern HRESULT WIN_RoInitialize(void);
extern void WIN_RoUninitialize(void);
// Returns true if we're running on Windows XP (any service pack). DOES NOT CHECK XP "OR GREATER"!
extern BOOL WIN_IsWindowsXP(void);
// Returns true if we're running on Windows Vista and newer
extern BOOL WIN_IsWindowsVistaOrGreater(void);
// Returns true if we're running on Windows 7 and newer
extern BOOL WIN_IsWindows7OrGreater(void);
// Returns true if we're running on Windows 8 and newer
extern BOOL WIN_IsWindows8OrGreater(void);
// You need to SDL_free() the result of this call.
extern char *WIN_LookupAudioDeviceName(const WCHAR *name, const GUID *guid);
// Checks to see if two GUID are the same.
extern BOOL WIN_IsEqualGUID(const GUID *a, const GUID *b);
extern BOOL WIN_IsEqualIID(REFIID a, REFIID b);
// Convert between SDL_rect and RECT
extern void WIN_RECTToRect(const RECT *winrect, SDL_Rect *sdlrect);
extern void WIN_RectToRECT(const SDL_Rect *sdlrect, RECT *winrect);
// Returns false if a window client rect is not valid
bool WIN_WindowRectValid(const RECT *rect);
extern SDL_AudioFormat SDL_WaveFormatExToSDLFormat(WAVEFORMATEX *waveformat);
// WideCharToMultiByte, but with some WinXP management.
extern int WIN_WideCharToMultiByte(UINT CodePage, DWORD dwFlags, LPCWCH lpWideCharStr, int cchWideChar, LPSTR lpMultiByteStr, int cbMultiByte, LPCCH lpDefaultChar, LPBOOL lpUsedDefaultChar);
// Ends C function definitions when using C++
#ifdef __cplusplus
}
#endif
#endif // _INCLUDED_WINDOWS_H

140
thirdparty/sdl/core/windows/SDL_xinput.c vendored Normal file
View file

@ -0,0 +1,140 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#include "SDL_xinput.h"
// Set up for C function definitions, even when using C++
#ifdef __cplusplus
extern "C" {
#endif
XInputGetState_t SDL_XInputGetState = NULL;
XInputSetState_t SDL_XInputSetState = NULL;
XInputGetCapabilities_t SDL_XInputGetCapabilities = NULL;
XInputGetCapabilitiesEx_t SDL_XInputGetCapabilitiesEx = NULL;
XInputGetBatteryInformation_t SDL_XInputGetBatteryInformation = NULL;
DWORD SDL_XInputVersion = 0;
static HMODULE s_pXInputDLL = NULL;
static int s_XInputDLLRefCount = 0;
#if defined(SDL_PLATFORM_XBOXONE) || defined(SDL_PLATFORM_XBOXSERIES)
bool WIN_LoadXInputDLL(void)
{
/* Getting handles to system dlls (via LoadLibrary and its variants) is not
* supported on Xbox, thus, pointers to XInput's functions can't be
* retrieved via GetProcAddress.
*
* When on Xbox, assume that XInput is already loaded, and directly map
* its XInput.h-declared functions to the SDL_XInput* set of function
* pointers.
*/
SDL_XInputGetState = (XInputGetState_t)XInputGetState;
SDL_XInputSetState = (XInputSetState_t)XInputSetState;
SDL_XInputGetCapabilities = (XInputGetCapabilities_t)XInputGetCapabilities;
SDL_XInputGetBatteryInformation = (XInputGetBatteryInformation_t)XInputGetBatteryInformation;
// XInput 1.4 ships with Windows 8 and 8.1:
SDL_XInputVersion = (1 << 16) | 4;
return true;
}
void WIN_UnloadXInputDLL(void)
{
}
#else // !(defined(SDL_PLATFORM_XBOXONE) || defined(SDL_PLATFORM_XBOXSERIES))
bool WIN_LoadXInputDLL(void)
{
DWORD version = 0;
if (s_pXInputDLL) {
SDL_assert(s_XInputDLLRefCount > 0);
s_XInputDLLRefCount++;
return true; // already loaded
}
/* NOTE: Don't load XinputUap.dll
* This is XInput emulation over Windows.Gaming.Input, and has all the
* limitations of that API (no devices at startup, no background input, etc.)
*/
version = (1 << 16) | 4;
s_pXInputDLL = LoadLibrary(TEXT("XInput1_4.dll")); // 1.4 Ships with Windows 8.
if (!s_pXInputDLL) {
version = (1 << 16) | 3;
s_pXInputDLL = LoadLibrary(TEXT("XInput1_3.dll")); // 1.3 can be installed as a redistributable component.
}
if (!s_pXInputDLL) {
s_pXInputDLL = LoadLibrary(TEXT("bin\\XInput1_3.dll"));
}
if (!s_pXInputDLL) {
// "9.1.0" Ships with Vista and Win7, and is more limited than 1.3+ (e.g. XInputGetStateEx is not available.)
s_pXInputDLL = LoadLibrary(TEXT("XInput9_1_0.dll"));
}
if (!s_pXInputDLL) {
return false;
}
SDL_assert(s_XInputDLLRefCount == 0);
SDL_XInputVersion = version;
s_XInputDLLRefCount = 1;
// 100 is the ordinal for _XInputGetStateEx, which returns the same struct as XinputGetState, but with extra data in wButtons for the guide button, we think...
SDL_XInputGetState = (XInputGetState_t)GetProcAddress(s_pXInputDLL, (LPCSTR)100);
if (!SDL_XInputGetState) {
SDL_XInputGetState = (XInputGetState_t)GetProcAddress(s_pXInputDLL, "XInputGetState");
}
SDL_XInputSetState = (XInputSetState_t)GetProcAddress(s_pXInputDLL, "XInputSetState");
SDL_XInputGetCapabilities = (XInputGetCapabilities_t)GetProcAddress(s_pXInputDLL, "XInputGetCapabilities");
// 108 is the ordinal for _XInputGetCapabilitiesEx, which additionally returns VID/PID of the controller.
SDL_XInputGetCapabilitiesEx = (XInputGetCapabilitiesEx_t)GetProcAddress(s_pXInputDLL, (LPCSTR)108);
SDL_XInputGetBatteryInformation = (XInputGetBatteryInformation_t)GetProcAddress(s_pXInputDLL, "XInputGetBatteryInformation");
if (!SDL_XInputGetState || !SDL_XInputSetState || !SDL_XInputGetCapabilities) {
WIN_UnloadXInputDLL();
return false;
}
return true;
}
void WIN_UnloadXInputDLL(void)
{
if (s_pXInputDLL) {
SDL_assert(s_XInputDLLRefCount > 0);
if (--s_XInputDLLRefCount == 0) {
FreeLibrary(s_pXInputDLL);
s_pXInputDLL = NULL;
}
} else {
SDL_assert(s_XInputDLLRefCount == 0);
}
}
#endif
// Ends C function definitions when using C++
#ifdef __cplusplus
}
#endif

276
thirdparty/sdl/core/windows/SDL_xinput.h vendored Normal file
View file

@ -0,0 +1,276 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#ifndef SDL_xinput_h_
#define SDL_xinput_h_
#include "SDL_windows.h"
#ifdef HAVE_XINPUT_H
#if defined(SDL_PLATFORM_XBOXONE) || defined(SDL_PLATFORM_XBOXSERIES)
// Xbox supports an XInput wrapper which is a C++-only header...
#include <math.h> // Required to compile with recent MSVC...
#include <XInputOnGameInput.h>
using namespace XInputOnGameInput;
#else
#include <xinput.h>
#endif
#endif // HAVE_XINPUT_H
#ifndef XUSER_MAX_COUNT
#define XUSER_MAX_COUNT 4
#endif
#ifndef XUSER_INDEX_ANY
#define XUSER_INDEX_ANY 0x000000FF
#endif
#ifndef XINPUT_CAPS_FFB_SUPPORTED
#define XINPUT_CAPS_FFB_SUPPORTED 0x0001
#endif
#ifndef XINPUT_CAPS_WIRELESS
#define XINPUT_CAPS_WIRELESS 0x0002
#endif
#ifndef XINPUT_DEVSUBTYPE_UNKNOWN
#define XINPUT_DEVSUBTYPE_UNKNOWN 0x00
#endif
#ifndef XINPUT_DEVSUBTYPE_GAMEPAD
#define XINPUT_DEVSUBTYPE_GAMEPAD 0x01
#endif
#ifndef XINPUT_DEVSUBTYPE_WHEEL
#define XINPUT_DEVSUBTYPE_WHEEL 0x02
#endif
#ifndef XINPUT_DEVSUBTYPE_ARCADE_STICK
#define XINPUT_DEVSUBTYPE_ARCADE_STICK 0x03
#endif
#ifndef XINPUT_DEVSUBTYPE_FLIGHT_STICK
#define XINPUT_DEVSUBTYPE_FLIGHT_STICK 0x04
#endif
#ifndef XINPUT_DEVSUBTYPE_DANCE_PAD
#define XINPUT_DEVSUBTYPE_DANCE_PAD 0x05
#endif
#ifndef XINPUT_DEVSUBTYPE_GUITAR
#define XINPUT_DEVSUBTYPE_GUITAR 0x06
#endif
#ifndef XINPUT_DEVSUBTYPE_GUITAR_ALTERNATE
#define XINPUT_DEVSUBTYPE_GUITAR_ALTERNATE 0x07
#endif
#ifndef XINPUT_DEVSUBTYPE_DRUM_KIT
#define XINPUT_DEVSUBTYPE_DRUM_KIT 0x08
#endif
#ifndef XINPUT_DEVSUBTYPE_GUITAR_BASS
#define XINPUT_DEVSUBTYPE_GUITAR_BASS 0x0B
#endif
#ifndef XINPUT_DEVSUBTYPE_ARCADE_PAD
#define XINPUT_DEVSUBTYPE_ARCADE_PAD 0x13
#endif
#ifndef XINPUT_FLAG_GAMEPAD
#define XINPUT_FLAG_GAMEPAD 0x01
#endif
#ifndef XINPUT_GAMEPAD_DPAD_UP
#define XINPUT_GAMEPAD_DPAD_UP 0x0001
#endif
#ifndef XINPUT_GAMEPAD_DPAD_DOWN
#define XINPUT_GAMEPAD_DPAD_DOWN 0x0002
#endif
#ifndef XINPUT_GAMEPAD_DPAD_LEFT
#define XINPUT_GAMEPAD_DPAD_LEFT 0x0004
#endif
#ifndef XINPUT_GAMEPAD_DPAD_RIGHT
#define XINPUT_GAMEPAD_DPAD_RIGHT 0x0008
#endif
#ifndef XINPUT_GAMEPAD_START
#define XINPUT_GAMEPAD_START 0x0010
#endif
#ifndef XINPUT_GAMEPAD_BACK
#define XINPUT_GAMEPAD_BACK 0x0020
#endif
#ifndef XINPUT_GAMEPAD_LEFT_THUMB
#define XINPUT_GAMEPAD_LEFT_THUMB 0x0040
#endif
#ifndef XINPUT_GAMEPAD_RIGHT_THUMB
#define XINPUT_GAMEPAD_RIGHT_THUMB 0x0080
#endif
#ifndef XINPUT_GAMEPAD_LEFT_SHOULDER
#define XINPUT_GAMEPAD_LEFT_SHOULDER 0x0100
#endif
#ifndef XINPUT_GAMEPAD_RIGHT_SHOULDER
#define XINPUT_GAMEPAD_RIGHT_SHOULDER 0x0200
#endif
#ifndef XINPUT_GAMEPAD_A
#define XINPUT_GAMEPAD_A 0x1000
#endif
#ifndef XINPUT_GAMEPAD_B
#define XINPUT_GAMEPAD_B 0x2000
#endif
#ifndef XINPUT_GAMEPAD_X
#define XINPUT_GAMEPAD_X 0x4000
#endif
#ifndef XINPUT_GAMEPAD_Y
#define XINPUT_GAMEPAD_Y 0x8000
#endif
#ifndef XINPUT_GAMEPAD_GUIDE
#define XINPUT_GAMEPAD_GUIDE 0x0400
#endif
#ifndef BATTERY_DEVTYPE_GAMEPAD
#define BATTERY_DEVTYPE_GAMEPAD 0x00
#endif
#ifndef BATTERY_TYPE_DISCONNECTED
#define BATTERY_TYPE_DISCONNECTED 0x00
#endif
#ifndef BATTERY_TYPE_WIRED
#define BATTERY_TYPE_WIRED 0x01
#endif
#ifndef BATTERY_TYPE_UNKNOWN
#define BATTERY_TYPE_UNKNOWN 0xFF
#endif
#ifndef BATTERY_LEVEL_EMPTY
#define BATTERY_LEVEL_EMPTY 0x00
#endif
#ifndef BATTERY_LEVEL_LOW
#define BATTERY_LEVEL_LOW 0x01
#endif
#ifndef BATTERY_LEVEL_MEDIUM
#define BATTERY_LEVEL_MEDIUM 0x02
#endif
#ifndef BATTERY_LEVEL_FULL
#define BATTERY_LEVEL_FULL 0x03
#endif
// Set up for C function definitions, even when using C++
#ifdef __cplusplus
extern "C" {
#endif
// typedef's for XInput structs we use
// This is the same as XINPUT_BATTERY_INFORMATION, but always defined instead of just if WIN32_WINNT >= _WIN32_WINNT_WIN8
typedef struct
{
BYTE BatteryType;
BYTE BatteryLevel;
} XINPUT_BATTERY_INFORMATION_EX;
#ifndef HAVE_XINPUT_H
typedef struct
{
WORD wButtons;
BYTE bLeftTrigger;
BYTE bRightTrigger;
SHORT sThumbLX;
SHORT sThumbLY;
SHORT sThumbRX;
SHORT sThumbRY;
} XINPUT_GAMEPAD;
typedef struct
{
DWORD dwPacketNumber;
XINPUT_GAMEPAD Gamepad;
} XINPUT_STATE;
typedef struct
{
WORD wLeftMotorSpeed;
WORD wRightMotorSpeed;
} XINPUT_VIBRATION;
typedef struct
{
BYTE Type;
BYTE SubType;
WORD Flags;
XINPUT_GAMEPAD Gamepad;
XINPUT_VIBRATION Vibration;
} XINPUT_CAPABILITIES;
#endif // HAVE_XINPUT_H
// This struct is not defined in XInput headers.
typedef struct
{
XINPUT_CAPABILITIES Capabilities;
WORD VendorId;
WORD ProductId;
WORD ProductVersion;
WORD unk1;
DWORD unk2;
} SDL_XINPUT_CAPABILITIES_EX;
// Forward decl's for XInput API's we load dynamically and use if available
typedef DWORD(WINAPI *XInputGetState_t)(
DWORD dwUserIndex, // [in] Index of the gamer associated with the device
XINPUT_STATE *pState // [out] Receives the current state
);
typedef DWORD(WINAPI *XInputSetState_t)(
DWORD dwUserIndex, // [in] Index of the gamer associated with the device
XINPUT_VIBRATION *pVibration // [in, out] The vibration information to send to the controller
);
typedef DWORD(WINAPI *XInputGetCapabilities_t)(
DWORD dwUserIndex, // [in] Index of the gamer associated with the device
DWORD dwFlags, // [in] Input flags that identify the device type
XINPUT_CAPABILITIES *pCapabilities // [out] Receives the capabilities
);
// Only available in XInput 1.4 that is shipped with Windows 8 and newer.
typedef DWORD(WINAPI *XInputGetCapabilitiesEx_t)(
DWORD dwReserved, // [in] Must be 1
DWORD dwUserIndex, // [in] Index of the gamer associated with the device
DWORD dwFlags, // [in] Input flags that identify the device type
SDL_XINPUT_CAPABILITIES_EX *pCapabilitiesEx // [out] Receives the capabilities
);
typedef DWORD(WINAPI *XInputGetBatteryInformation_t)(
DWORD dwUserIndex,
BYTE devType,
XINPUT_BATTERY_INFORMATION_EX *pBatteryInformation);
extern bool WIN_LoadXInputDLL(void);
extern void WIN_UnloadXInputDLL(void);
extern XInputGetState_t SDL_XInputGetState;
extern XInputSetState_t SDL_XInputSetState;
extern XInputGetCapabilities_t SDL_XInputGetCapabilities;
extern XInputGetCapabilitiesEx_t SDL_XInputGetCapabilitiesEx;
extern XInputGetBatteryInformation_t SDL_XInputGetBatteryInformation;
extern DWORD SDL_XInputVersion; // ((major << 16) & 0xFF00) | (minor & 0xFF)
// Ends C function definitions when using C++
#ifdef __cplusplus
}
#endif
#define XINPUTGETSTATE SDL_XInputGetState
#define XINPUTSETSTATE SDL_XInputSetState
#define XINPUTGETCAPABILITIES SDL_XInputGetCapabilities
#define XINPUTGETCAPABILITIESEX SDL_XInputGetCapabilitiesEx
#define XINPUTGETBATTERYINFORMATION SDL_XInputGetBatteryInformation
#endif // SDL_xinput_h_

21
thirdparty/sdl/core/windows/pch.c vendored Normal file
View file

@ -0,0 +1,21 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"

21
thirdparty/sdl/core/windows/pch_cpp.cpp vendored Normal file
View file

@ -0,0 +1,21 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"

1833
thirdparty/sdl/events/SDL_events.c vendored Normal file

File diff suppressed because it is too large Load diff

59
thirdparty/sdl/events/SDL_events_c.h vendored Normal file
View file

@ -0,0 +1,59 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#ifndef SDL_events_c_h_
#define SDL_events_c_h_
#include "SDL_internal.h"
// Useful functions and variables from SDL_events.c
//#include "../video/SDL_sysvideo.h"
#include "SDL_mouse_c.h"
// Start and stop the event processing loop
extern bool SDL_StartEventLoop(void);
extern void SDL_StopEventLoop(void);
extern void SDL_QuitInterrupt(void);
extern void SDL_SendAppEvent(SDL_EventType eventType);
extern void SDL_SendKeymapChangedEvent(void);
extern void SDL_SendLocaleChangedEvent(void);
extern void SDL_SendSystemThemeChangedEvent(void);
extern void *SDL_AllocateTemporaryMemory(size_t size);
extern const char *SDL_CreateTemporaryString(const char *string);
extern void *SDL_ClaimTemporaryMemory(const void *mem);
extern void SDL_FreeTemporaryMemory(void);
extern void SDL_PumpEventMaintenance(void);
extern void SDL_SendQuit(void);
extern bool SDL_InitEvents(void);
extern void SDL_QuitEvents(void);
extern void SDL_SendPendingSignalEvents(void);
extern bool SDL_InitQuit(void);
extern void SDL_QuitQuit(void);
#endif // SDL_events_c_h_

143
thirdparty/sdl/events/SDL_eventwatch.c vendored Normal file
View file

@ -0,0 +1,143 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#include "SDL_eventwatch_c.h"
bool SDL_InitEventWatchList(SDL_EventWatchList *list)
{
if (list->lock == NULL) {
list->lock = SDL_CreateMutex();
if (list->lock == NULL) {
return false;
}
}
return true;
}
void SDL_QuitEventWatchList(SDL_EventWatchList *list)
{
if (list->lock) {
SDL_DestroyMutex(list->lock);
list->lock = NULL;
}
if (list->watchers) {
SDL_free(list->watchers);
list->watchers = NULL;
list->count = 0;
}
SDL_zero(list->filter);
}
bool SDL_DispatchEventWatchList(SDL_EventWatchList *list, SDL_Event *event)
{
SDL_EventWatcher *filter = &list->filter;
if (!filter->callback && list->count == 0) {
return true;
}
SDL_LockMutex(list->lock);
{
// Make sure we only dispatch the current watcher list
int i, count = list->count;
if (filter->callback && !filter->callback(filter->userdata, event)) {
SDL_UnlockMutex(list->lock);
return false;
}
list->dispatching = true;
for (i = 0; i < count; ++i) {
if (!list->watchers[i].removed) {
list->watchers[i].callback(list->watchers[i].userdata, event);
}
}
list->dispatching = false;
if (list->removed) {
for (i = list->count; i--;) {
if (list->watchers[i].removed) {
--list->count;
if (i < list->count) {
SDL_memmove(&list->watchers[i], &list->watchers[i + 1], (list->count - i) * sizeof(list->watchers[i]));
}
}
}
list->removed = false;
}
}
SDL_UnlockMutex(list->lock);
return true;
}
bool SDL_AddEventWatchList(SDL_EventWatchList *list, SDL_EventFilter filter, void *userdata)
{
bool result = true;
SDL_LockMutex(list->lock);
{
SDL_EventWatcher *watchers;
watchers = (SDL_EventWatcher *)SDL_realloc(list->watchers, (list->count + 1) * sizeof(*watchers));
if (watchers) {
SDL_EventWatcher *watcher;
list->watchers = watchers;
watcher = &list->watchers[list->count];
watcher->callback = filter;
watcher->userdata = userdata;
watcher->removed = false;
++list->count;
} else {
result = false;
}
}
SDL_UnlockMutex(list->lock);
return result;
}
void SDL_RemoveEventWatchList(SDL_EventWatchList *list, SDL_EventFilter filter, void *userdata)
{
SDL_LockMutex(list->lock);
{
int i;
for (i = 0; i < list->count; ++i) {
if (list->watchers[i].callback == filter && list->watchers[i].userdata == userdata) {
if (list->dispatching) {
list->watchers[i].removed = true;
list->removed = true;
} else {
--list->count;
if (i < list->count) {
SDL_memmove(&list->watchers[i], &list->watchers[i + 1], (list->count - i) * sizeof(list->watchers[i]));
}
}
break;
}
}
}
SDL_UnlockMutex(list->lock);
}

View file

@ -0,0 +1,45 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
typedef struct SDL_EventWatcher
{
SDL_EventFilter callback;
void *userdata;
bool removed;
} SDL_EventWatcher;
typedef struct SDL_EventWatchList
{
SDL_Mutex *lock;
SDL_EventWatcher filter;
SDL_EventWatcher *watchers;
int count;
bool dispatching;
bool removed;
} SDL_EventWatchList;
extern bool SDL_InitEventWatchList(SDL_EventWatchList *list);
extern void SDL_QuitEventWatchList(SDL_EventWatchList *list);
extern bool SDL_DispatchEventWatchList(SDL_EventWatchList *list, SDL_Event *event);
extern bool SDL_AddEventWatchList(SDL_EventWatchList *list, SDL_EventFilter filter, void *userdata);
extern void SDL_RemoveEventWatchList(SDL_EventWatchList *list, SDL_EventFilter filter, void *userdata);

215
thirdparty/sdl/events/SDL_mouse_c.h vendored Normal file
View file

@ -0,0 +1,215 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#ifndef SDL_mouse_c_h_
#define SDL_mouse_c_h_
// Mouse events not associated with a specific input device
#define SDL_GLOBAL_MOUSE_ID 0
// The default mouse input device, for platforms that don't have multiple mice
#define SDL_DEFAULT_MOUSE_ID 1
typedef struct SDL_CursorData SDL_CursorData;
struct SDL_Cursor
{
struct SDL_Cursor *next;
SDL_CursorData *internal;
};
typedef struct
{
Uint64 last_timestamp;
double click_motion_x;
double click_motion_y;
Uint8 click_count;
} SDL_MouseClickState;
typedef struct
{
SDL_MouseID mouseID;
Uint32 buttonstate;
// Data for double-click tracking
int num_clickstates;
SDL_MouseClickState *clickstate;
} SDL_MouseInputSource;
typedef struct
{
// Create a cursor from a surface
SDL_Cursor *(*CreateCursor)(SDL_Surface *surface, int hot_x, int hot_y);
// Create a system cursor
SDL_Cursor *(*CreateSystemCursor)(SDL_SystemCursor id);
// Show the specified cursor, or hide if cursor is NULL
bool (*ShowCursor)(SDL_Cursor *cursor);
// This is called when a mouse motion event occurs
bool (*MoveCursor)(SDL_Cursor *cursor);
// Free a window manager cursor
void (*FreeCursor)(SDL_Cursor *cursor);
// Warp the mouse to (x,y) within a window
bool (*WarpMouse)(SDL_Window *window, float x, float y);
// Warp the mouse to (x,y) in screen space
bool (*WarpMouseGlobal)(float x, float y);
// Set relative mode
bool (*SetRelativeMouseMode)(bool enabled);
// Set mouse capture
bool (*CaptureMouse)(SDL_Window *window);
// Get absolute mouse coordinates. (x) and (y) are never NULL and set to zero before call.
SDL_MouseButtonFlags (*GetGlobalMouseState)(float *x, float *y);
// Platform-specific system mouse transform
void (*ApplySystemScale)(void *internal, Uint64 timestamp, SDL_Window *window, SDL_MouseID mouseID, float *x, float *y);
void *system_scale_data;
// integer mode data
Uint8 integer_mode_flags; // 1 to enable mouse quantization, 2 to enable wheel quantization
float integer_mode_residual_motion_x;
float integer_mode_residual_motion_y;
// Data common to all mice
SDL_Window *focus;
float x;
float y;
float x_accu;
float y_accu;
float last_x, last_y; // the last reported x and y coordinates
float residual_scroll_x;
float residual_scroll_y;
double click_motion_x;
double click_motion_y;
bool has_position;
bool relative_mode;
bool relative_mode_warp_motion;
bool relative_mode_cursor_visible;
bool relative_mode_center;
bool warp_emulation_hint;
bool warp_emulation_active;
bool warp_emulation_prohibited;
Uint64 last_center_warp_time_ns;
bool enable_normal_speed_scale;
float normal_speed_scale;
bool enable_relative_speed_scale;
float relative_speed_scale;
bool enable_relative_system_scale;
Uint32 double_click_time;
int double_click_radius;
bool touch_mouse_events;
bool mouse_touch_events;
bool pen_mouse_events;
bool pen_touch_events;
bool was_touch_mouse_events; // Was a touch-mouse event pending?
bool added_mouse_touch_device; // did we SDL_AddTouch() a virtual touch device for the mouse?
bool added_pen_touch_device; // did we SDL_AddTouch() a virtual touch device for pens?
#ifdef SDL_PLATFORM_VITA
Uint8 vita_touch_mouse_device;
#endif
bool auto_capture;
bool capture_desired;
SDL_Window *capture_window;
// Data for input source state
int num_sources;
SDL_MouseInputSource *sources;
SDL_Cursor *cursors;
SDL_Cursor *def_cursor;
SDL_Cursor *cur_cursor;
bool cursor_shown;
// Driver-dependent data.
void *internal;
} SDL_Mouse;
// Initialize the mouse subsystem, called before the main video driver is initialized
extern bool SDL_PreInitMouse(void);
// Finish initializing the mouse subsystem, called after the main video driver was initialized
extern void SDL_PostInitMouse(void);
// Return whether a device is actually a mouse
extern bool SDL_IsMouse(Uint16 vendor, Uint16 product);
// A mouse has been added to the system
extern void SDL_AddMouse(SDL_MouseID mouseID, const char *name, bool send_event);
// A mouse has been removed from the system
extern void SDL_RemoveMouse(SDL_MouseID mouseID, bool send_event);
// Get the mouse state structure
extern SDL_Mouse *SDL_GetMouse(void);
// Set the default mouse cursor
extern void SDL_SetDefaultCursor(SDL_Cursor *cursor);
// Get the preferred default system cursor
extern SDL_SystemCursor SDL_GetDefaultSystemCursor(void);
// Set the mouse focus window
extern void SDL_SetMouseFocus(SDL_Window *window);
// Update the mouse capture window
extern bool SDL_UpdateMouseCapture(bool force_release);
// Send a mouse motion event
extern void SDL_SendMouseMotion(Uint64 timestamp, SDL_Window *window, SDL_MouseID mouseID, bool relative, float x, float y);
// Send a mouse button event
extern void SDL_SendMouseButton(Uint64 timestamp, SDL_Window *window, SDL_MouseID mouseID, Uint8 button, bool down);
// Send a mouse button event with a click count
extern void SDL_SendMouseButtonClicks(Uint64 timestamp, SDL_Window *window, SDL_MouseID mouseID, Uint8 button, bool down, int clicks);
// Send a mouse wheel event
extern void SDL_SendMouseWheel(Uint64 timestamp, SDL_Window *window, SDL_MouseID mouseID, float x, float y, SDL_MouseWheelDirection direction);
// Warp the mouse within the window, potentially overriding relative mode
extern void SDL_PerformWarpMouseInWindow(SDL_Window *window, float x, float y, bool ignore_relative_mode);
// Relative mouse mode
extern bool SDL_SetRelativeMouseMode(bool enabled);
extern bool SDL_GetRelativeMouseMode(void);
extern void SDL_UpdateRelativeMouseMode(void);
extern void SDL_DisableMouseWarpEmulation(void);
// TODO RECONNECT: Set mouse state to "zero"
#if 0
extern void SDL_ResetMouse(void);
#endif // 0
// Check if mouse position is within window or captured by window
extern bool SDL_MousePositionInWindow(SDL_Window *window, float x, float y);
// Shutdown the mouse subsystem
extern void SDL_QuitMouse(void);
#endif // SDL_mouse_c_h_

788
thirdparty/sdl/haptic/SDL_haptic.c vendored Normal file
View file

@ -0,0 +1,788 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#include "SDL_syshaptic.h"
#include "SDL_haptic_c.h"
#include "../joystick/SDL_joystick_c.h" // For SDL_IsJoystickValid
#include "../SDL_hints_c.h"
typedef struct SDL_Haptic_VIDPID_Naxes {
Uint16 vid;
Uint16 pid;
Uint16 naxes;
} SDL_Haptic_VIDPID_Naxes;
static void SDL_Haptic_Load_Axes_List(SDL_Haptic_VIDPID_Naxes **entries, int *num_entries)
{
SDL_Haptic_VIDPID_Naxes entry;
const char *spot;
int length = 0;
spot = SDL_GetHint(SDL_HINT_JOYSTICK_HAPTIC_AXES);
if (!spot)
return;
while (SDL_sscanf(spot, "0x%hx/0x%hx/%hu%n", &entry.vid, &entry.pid, &entry.naxes, &length) == 3) {
SDL_assert(length > 0);
spot += length;
length = 0;
if ((*num_entries % 8) == 0) {
int new_max = *num_entries + 8;
SDL_Haptic_VIDPID_Naxes *new_entries =
(SDL_Haptic_VIDPID_Naxes *)SDL_realloc(*entries, new_max * sizeof(**entries));
// Out of memory, go with what we have already
if (!new_entries)
break;
*entries = new_entries;
}
(*entries)[(*num_entries)++] = entry;
if (spot[0] == ',')
spot++;
}
}
// /* Return -1 if not found */
static int SDL_Haptic_Naxes_List_Index(struct SDL_Haptic_VIDPID_Naxes *entries, int num_entries, Uint16 vid, Uint16 pid)
{
if (!entries)
return -1;
int i;
for (i = 0; i < num_entries; ++i) {
if (entries[i].vid == vid && entries[i].pid == pid)
return i;
}
return -1;
}
// Check if device needs a custom number of naxes
static int SDL_Haptic_Get_Naxes(Uint16 vid, Uint16 pid)
{
int num_entries = 0, index = 0, naxes = -1;
SDL_Haptic_VIDPID_Naxes *naxes_list = NULL;
SDL_Haptic_Load_Axes_List(&naxes_list, &num_entries);
if (!num_entries || !naxes_list)
return -1;
// Perform "wildcard" pass
index = SDL_Haptic_Naxes_List_Index(naxes_list, num_entries, 0xffff, 0xffff);
if (index >= 0)
naxes = naxes_list[index].naxes;
index = SDL_Haptic_Naxes_List_Index(naxes_list, num_entries, vid, pid);
if (index >= 0)
naxes = naxes_list[index].naxes;
SDL_free(naxes_list);
return naxes;
}
static SDL_Haptic *SDL_haptics = NULL;
#define CHECK_HAPTIC_MAGIC(haptic, result) \
if (!SDL_ObjectValid(haptic, SDL_OBJECT_TYPE_HAPTIC)) { \
SDL_InvalidParamError("haptic"); \
return result; \
}
bool SDL_InitHaptics(void)
{
return SDL_SYS_HapticInit();
}
static bool SDL_GetHapticIndex(SDL_HapticID instance_id, int *driver_index)
{
int num_haptics, device_index;
if (instance_id > 0) {
num_haptics = SDL_SYS_NumHaptics();
for (device_index = 0; device_index < num_haptics; ++device_index) {
SDL_HapticID haptic_id = SDL_SYS_HapticInstanceID(device_index);
if (haptic_id == instance_id) {
*driver_index = device_index;
return true;
}
}
}
SDL_SetError("Haptic device %" SDL_PRIu32 " not found", instance_id);
return false;
}
SDL_HapticID *SDL_GetHaptics(int *count)
{
int device_index;
int haptic_index = 0, num_haptics = 0;
SDL_HapticID *haptics;
num_haptics = SDL_SYS_NumHaptics();
haptics = (SDL_HapticID *)SDL_malloc((num_haptics + 1) * sizeof(*haptics));
if (haptics) {
if (count) {
*count = num_haptics;
}
for (device_index = 0; device_index < num_haptics; ++device_index) {
haptics[haptic_index] = SDL_SYS_HapticInstanceID(device_index);
SDL_assert(haptics[haptic_index] > 0);
++haptic_index;
}
haptics[haptic_index] = 0;
} else {
if (count) {
*count = 0;
}
}
return haptics;
}
const char *SDL_GetHapticNameForID(SDL_HapticID instance_id)
{
int device_index;
const char *name = NULL;
if (SDL_GetHapticIndex(instance_id, &device_index)) {
name = SDL_GetPersistentString(SDL_SYS_HapticName(device_index));
}
return name;
}
SDL_Haptic *SDL_OpenHaptic(SDL_HapticID instance_id)
{
SDL_Haptic *haptic;
SDL_Haptic *hapticlist;
const char *name;
int device_index = 0;
if (!SDL_GetHapticIndex(instance_id, &device_index)) {
return NULL;
}
hapticlist = SDL_haptics;
/* If the haptic device is already open, return it
* it is important that we have a single haptic device for each instance id
*/
while (hapticlist) {
if (instance_id == hapticlist->instance_id) {
haptic = hapticlist;
++haptic->ref_count;
return haptic;
}
hapticlist = hapticlist->next;
}
// Create the haptic device
haptic = (SDL_Haptic *)SDL_calloc(1, sizeof(*haptic));
if (!haptic) {
return NULL;
}
// Initialize the haptic device
SDL_SetObjectValid(haptic, SDL_OBJECT_TYPE_HAPTIC, true);
haptic->instance_id = instance_id;
haptic->rumble_id = -1;
if (!SDL_SYS_HapticOpen(haptic)) {
SDL_SetObjectValid(haptic, SDL_OBJECT_TYPE_HAPTIC, false);
SDL_free(haptic);
return NULL;
}
if (!haptic->name) {
name = SDL_SYS_HapticName(device_index);
if (name) {
haptic->name = SDL_strdup(name);
}
}
// Add haptic to list
++haptic->ref_count;
// Link the haptic in the list
haptic->next = SDL_haptics;
SDL_haptics = haptic;
// Disable autocenter and set gain to max.
if (haptic->supported & SDL_HAPTIC_GAIN) {
SDL_SetHapticGain(haptic, 100);
}
if (haptic->supported & SDL_HAPTIC_AUTOCENTER) {
SDL_SetHapticAutocenter(haptic, 0);
}
return haptic;
}
SDL_Haptic *SDL_GetHapticFromID(SDL_HapticID instance_id)
{
SDL_Haptic *haptic;
for (haptic = SDL_haptics; haptic; haptic = haptic->next) {
if (instance_id == haptic->instance_id) {
break;
}
}
return haptic;
}
SDL_HapticID SDL_GetHapticID(SDL_Haptic *haptic)
{
CHECK_HAPTIC_MAGIC(haptic, 0);
return haptic->instance_id;
}
const char *SDL_GetHapticName(SDL_Haptic *haptic)
{
CHECK_HAPTIC_MAGIC(haptic, NULL);
return SDL_GetPersistentString(haptic->name);
}
bool SDL_IsMouseHaptic(void)
{
if (SDL_SYS_HapticMouse() < 0) {
return false;
}
return true;
}
SDL_Haptic *SDL_OpenHapticFromMouse(void)
{
int device_index;
device_index = SDL_SYS_HapticMouse();
if (device_index < 0) {
SDL_SetError("Haptic: Mouse isn't a haptic device.");
return NULL;
}
return SDL_OpenHaptic(device_index);
}
bool SDL_IsJoystickHaptic(SDL_Joystick *joystick)
{
bool result = false;
SDL_LockJoysticks();
{
// Must be a valid joystick
if (SDL_IsJoystickValid(joystick) &&
!SDL_IsGamepad(SDL_GetJoystickID(joystick))) {
result = SDL_SYS_JoystickIsHaptic(joystick);
}
}
SDL_UnlockJoysticks();
return result;
}
SDL_Haptic *SDL_OpenHapticFromJoystick(SDL_Joystick *joystick)
{
SDL_Haptic *haptic;
SDL_Haptic *hapticlist;
SDL_LockJoysticks();
{
// Must be a valid joystick
if (!SDL_IsJoystickValid(joystick)) {
SDL_SetError("Haptic: Joystick isn't valid.");
SDL_UnlockJoysticks();
return NULL;
}
// Joystick must be haptic
if (SDL_IsGamepad(SDL_GetJoystickID(joystick)) ||
!SDL_SYS_JoystickIsHaptic(joystick)) {
SDL_SetError("Haptic: Joystick isn't a haptic device.");
SDL_UnlockJoysticks();
return NULL;
}
hapticlist = SDL_haptics;
// Check to see if joystick's haptic is already open
while (hapticlist) {
if (SDL_SYS_JoystickSameHaptic(hapticlist, joystick)) {
haptic = hapticlist;
++haptic->ref_count;
SDL_UnlockJoysticks();
return haptic;
}
hapticlist = hapticlist->next;
}
// Create the haptic device
haptic = (SDL_Haptic *)SDL_calloc(1, sizeof(*haptic));
if (!haptic) {
SDL_UnlockJoysticks();
return NULL;
}
/* Initialize the haptic device
* This function should fill in the instance ID and name.
*/
SDL_SetObjectValid(haptic, SDL_OBJECT_TYPE_HAPTIC, true);
haptic->rumble_id = -1;
if (!SDL_SYS_HapticOpenFromJoystick(haptic, joystick)) {
SDL_SetError("Haptic: SDL_SYS_HapticOpenFromJoystick failed.");
SDL_SetObjectValid(haptic, SDL_OBJECT_TYPE_HAPTIC, false);
SDL_free(haptic);
SDL_UnlockJoysticks();
return NULL;
}
SDL_assert(haptic->instance_id != 0);
}
SDL_UnlockJoysticks();
// Check if custom number of haptic axes was defined
Uint16 vid = SDL_GetJoystickVendor(joystick);
Uint16 pid = SDL_GetJoystickProduct(joystick);
int general_axes = SDL_GetNumJoystickAxes(joystick);
int naxes = SDL_Haptic_Get_Naxes(vid, pid);
if (naxes > 0)
haptic->naxes = naxes;
// Limit to the actual number of axes found on the device
if (general_axes >= 0 && naxes > general_axes)
haptic->naxes = general_axes;
// Add haptic to list
++haptic->ref_count;
// Link the haptic in the list
haptic->next = SDL_haptics;
SDL_haptics = haptic;
return haptic;
}
void SDL_CloseHaptic(SDL_Haptic *haptic)
{
int i;
SDL_Haptic *hapticlist;
SDL_Haptic *hapticlistprev;
CHECK_HAPTIC_MAGIC(haptic,);
// Check if it's still in use
if (--haptic->ref_count > 0) {
return;
}
// Close it, properly removing effects if needed
for (i = 0; i < haptic->neffects; i++) {
if (haptic->effects[i].hweffect != NULL) {
SDL_DestroyHapticEffect(haptic, i);
}
}
SDL_SYS_HapticClose(haptic);
SDL_SetObjectValid(haptic, SDL_OBJECT_TYPE_HAPTIC, false);
// Remove from the list
hapticlist = SDL_haptics;
hapticlistprev = NULL;
while (hapticlist) {
if (haptic == hapticlist) {
if (hapticlistprev) {
// unlink this entry
hapticlistprev->next = hapticlist->next;
} else {
SDL_haptics = haptic->next;
}
break;
}
hapticlistprev = hapticlist;
hapticlist = hapticlist->next;
}
// Free the data associated with this device
SDL_free(haptic->name);
SDL_free(haptic);
}
void SDL_QuitHaptics(void)
{
while (SDL_haptics) {
SDL_CloseHaptic(SDL_haptics);
}
SDL_SYS_HapticQuit();
}
int SDL_GetMaxHapticEffects(SDL_Haptic *haptic)
{
CHECK_HAPTIC_MAGIC(haptic, -1);
return haptic->neffects;
}
int SDL_GetMaxHapticEffectsPlaying(SDL_Haptic *haptic)
{
CHECK_HAPTIC_MAGIC(haptic, -1);
return haptic->nplaying;
}
Uint32 SDL_GetHapticFeatures(SDL_Haptic *haptic)
{
CHECK_HAPTIC_MAGIC(haptic, 0);
return haptic->supported;
}
int SDL_GetNumHapticAxes(SDL_Haptic *haptic)
{
CHECK_HAPTIC_MAGIC(haptic, -1);
return haptic->naxes;
}
bool SDL_HapticEffectSupported(SDL_Haptic *haptic, const SDL_HapticEffect *effect)
{
CHECK_HAPTIC_MAGIC(haptic, false);
if (!effect) {
return false;
}
if ((haptic->supported & effect->type) != 0) {
return true;
}
return false;
}
int SDL_CreateHapticEffect(SDL_Haptic *haptic, const SDL_HapticEffect *effect)
{
int i;
CHECK_HAPTIC_MAGIC(haptic, -1);
if (!effect) {
SDL_InvalidParamError("effect");
return -1;
}
// Check to see if effect is supported
if (SDL_HapticEffectSupported(haptic, effect) == false) {
SDL_SetError("Haptic: Effect not supported by haptic device.");
return -1;
}
// See if there's a free slot
for (i = 0; i < haptic->neffects; i++) {
if (haptic->effects[i].hweffect == NULL) {
// Now let the backend create the real effect
if (!SDL_SYS_HapticNewEffect(haptic, &haptic->effects[i], effect)) {
return -1; // Backend failed to create effect
}
SDL_memcpy(&haptic->effects[i].effect, effect,
sizeof(SDL_HapticEffect));
return i;
}
}
SDL_SetError("Haptic: Device has no free space left.");
return -1;
}
static bool ValidEffect(SDL_Haptic *haptic, int effect)
{
if ((effect < 0) || (effect >= haptic->neffects)) {
SDL_SetError("Haptic: Invalid effect identifier.");
return false;
}
return true;
}
bool SDL_UpdateHapticEffect(SDL_Haptic *haptic, int effect, const SDL_HapticEffect *data)
{
CHECK_HAPTIC_MAGIC(haptic, false);
if (!ValidEffect(haptic, effect)) {
return false;
}
if (!data) {
return SDL_InvalidParamError("data");
}
// Can't change type dynamically.
if (data->type != haptic->effects[effect].effect.type) {
return SDL_SetError("Haptic: Updating effect type is illegal.");
}
// Updates the effect
if (!SDL_SYS_HapticUpdateEffect(haptic, &haptic->effects[effect], data)) {
return false;
}
SDL_memcpy(&haptic->effects[effect].effect, data,
sizeof(SDL_HapticEffect));
return true;
}
bool SDL_RunHapticEffect(SDL_Haptic *haptic, int effect, Uint32 iterations)
{
CHECK_HAPTIC_MAGIC(haptic, false);
if (!ValidEffect(haptic, effect)) {
return false;
}
// Run the effect
if (!SDL_SYS_HapticRunEffect(haptic, &haptic->effects[effect], iterations)) {
return false;
}
return true;
}
bool SDL_StopHapticEffect(SDL_Haptic *haptic, int effect)
{
CHECK_HAPTIC_MAGIC(haptic, false);
if (!ValidEffect(haptic, effect)) {
return false;
}
// Stop the effect
if (!SDL_SYS_HapticStopEffect(haptic, &haptic->effects[effect])) {
return false;
}
return true;
}
void SDL_DestroyHapticEffect(SDL_Haptic *haptic, int effect)
{
CHECK_HAPTIC_MAGIC(haptic,);
if (!ValidEffect(haptic, effect)) {
return;
}
// Not allocated
if (haptic->effects[effect].hweffect == NULL) {
return;
}
SDL_SYS_HapticDestroyEffect(haptic, &haptic->effects[effect]);
}
bool SDL_GetHapticEffectStatus(SDL_Haptic *haptic, int effect)
{
CHECK_HAPTIC_MAGIC(haptic, false);
if (!ValidEffect(haptic, effect)) {
return false;
}
if (!(haptic->supported & SDL_HAPTIC_STATUS)) {
return SDL_SetError("Haptic: Device does not support status queries.");
}
SDL_ClearError();
return (SDL_SYS_HapticGetEffectStatus(haptic, &haptic->effects[effect]) > 0);
}
bool SDL_SetHapticGain(SDL_Haptic *haptic, int gain)
{
const char *env;
int real_gain, max_gain;
CHECK_HAPTIC_MAGIC(haptic, false);
if (!(haptic->supported & SDL_HAPTIC_GAIN)) {
return SDL_SetError("Haptic: Device does not support setting gain.");
}
if ((gain < 0) || (gain > 100)) {
return SDL_SetError("Haptic: Gain must be between 0 and 100.");
}
// The user can use an environment variable to override the max gain.
env = SDL_getenv("SDL_HAPTIC_GAIN_MAX");
if (env) {
max_gain = SDL_atoi(env);
// Check for sanity.
if (max_gain < 0) {
max_gain = 0;
} else if (max_gain > 100) {
max_gain = 100;
}
// We'll scale it linearly with SDL_HAPTIC_GAIN_MAX
real_gain = (gain * max_gain) / 100;
} else {
real_gain = gain;
}
return SDL_SYS_HapticSetGain(haptic, real_gain);
}
bool SDL_SetHapticAutocenter(SDL_Haptic *haptic, int autocenter)
{
CHECK_HAPTIC_MAGIC(haptic, false);
if (!(haptic->supported & SDL_HAPTIC_AUTOCENTER)) {
return SDL_SetError("Haptic: Device does not support setting autocenter.");
}
if ((autocenter < 0) || (autocenter > 100)) {
return SDL_SetError("Haptic: Autocenter must be between 0 and 100.");
}
return SDL_SYS_HapticSetAutocenter(haptic, autocenter);
}
bool SDL_PauseHaptic(SDL_Haptic *haptic)
{
CHECK_HAPTIC_MAGIC(haptic, false);
if (!(haptic->supported & SDL_HAPTIC_PAUSE)) {
return SDL_SetError("Haptic: Device does not support setting pausing.");
}
return SDL_SYS_HapticPause(haptic);
}
bool SDL_ResumeHaptic(SDL_Haptic *haptic)
{
CHECK_HAPTIC_MAGIC(haptic, false);
if (!(haptic->supported & SDL_HAPTIC_PAUSE)) {
return true; // Not going to be paused, so we pretend it's unpaused.
}
return SDL_SYS_HapticResume(haptic);
}
bool SDL_StopHapticEffects(SDL_Haptic *haptic)
{
CHECK_HAPTIC_MAGIC(haptic, false);
return SDL_SYS_HapticStopAll(haptic);
}
bool SDL_HapticRumbleSupported(SDL_Haptic *haptic)
{
CHECK_HAPTIC_MAGIC(haptic, false);
// Most things can use SINE, but XInput only has LEFTRIGHT.
return (haptic->supported & (SDL_HAPTIC_SINE | SDL_HAPTIC_LEFTRIGHT)) != 0;
}
bool SDL_InitHapticRumble(SDL_Haptic *haptic)
{
SDL_HapticEffect *efx = &haptic->rumble_effect;
CHECK_HAPTIC_MAGIC(haptic, false);
// Already allocated.
if (haptic->rumble_id >= 0) {
return true;
}
SDL_zerop(efx);
if (haptic->supported & SDL_HAPTIC_SINE) {
efx->type = SDL_HAPTIC_SINE;
efx->periodic.direction.type = SDL_HAPTIC_CARTESIAN;
efx->periodic.period = 1000;
efx->periodic.magnitude = 0x4000;
efx->periodic.length = 5000;
efx->periodic.attack_length = 0;
efx->periodic.fade_length = 0;
} else if (haptic->supported & SDL_HAPTIC_LEFTRIGHT) { // XInput?
efx->type = SDL_HAPTIC_LEFTRIGHT;
efx->leftright.length = 5000;
efx->leftright.large_magnitude = 0x4000;
efx->leftright.small_magnitude = 0x4000;
} else {
return SDL_SetError("Device doesn't support rumble");
}
haptic->rumble_id = SDL_CreateHapticEffect(haptic, &haptic->rumble_effect);
if (haptic->rumble_id >= 0) {
return true;
}
return false;
}
bool SDL_PlayHapticRumble(SDL_Haptic *haptic, float strength, Uint32 length)
{
SDL_HapticEffect *efx;
Sint16 magnitude;
CHECK_HAPTIC_MAGIC(haptic, false);
if (haptic->rumble_id < 0) {
return SDL_SetError("Haptic: Rumble effect not initialized on haptic device");
}
// Clamp strength.
if (strength > 1.0f) {
strength = 1.0f;
} else if (strength < 0.0f) {
strength = 0.0f;
}
magnitude = (Sint16)(32767.0f * strength);
efx = &haptic->rumble_effect;
if (efx->type == SDL_HAPTIC_SINE) {
efx->periodic.magnitude = magnitude;
efx->periodic.length = length;
} else if (efx->type == SDL_HAPTIC_LEFTRIGHT) {
efx->leftright.small_magnitude = efx->leftright.large_magnitude = magnitude;
efx->leftright.length = length;
} else {
SDL_assert(!"This should have been caught elsewhere");
}
if (!SDL_UpdateHapticEffect(haptic, haptic->rumble_id, &haptic->rumble_effect)) {
return false;
}
return SDL_RunHapticEffect(haptic, haptic->rumble_id, 1);
}
bool SDL_StopHapticRumble(SDL_Haptic *haptic)
{
CHECK_HAPTIC_MAGIC(haptic, false);
if (haptic->rumble_id < 0) {
return SDL_SetError("Haptic: Rumble effect not initialized on haptic device");
}
return SDL_StopHapticEffect(haptic, haptic->rumble_id);
}

28
thirdparty/sdl/haptic/SDL_haptic_c.h vendored Normal file
View file

@ -0,0 +1,28 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#ifndef SDL_haptic_c_h_
#define SDL_haptic_c_h_
extern bool SDL_InitHaptics(void);
extern void SDL_QuitHaptics(void);
#endif // SDL_haptic_c_h_

194
thirdparty/sdl/haptic/SDL_syshaptic.h vendored Normal file
View file

@ -0,0 +1,194 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#ifndef SDL_syshaptic_h_
#define SDL_syshaptic_h_
// Set up for C function definitions, even when using C++
#ifdef __cplusplus
extern "C" {
#endif
struct haptic_effect
{
SDL_HapticEffect effect; // The current event
struct haptic_hweffect *hweffect; // The hardware behind the event
};
/*
* The real SDL_Haptic struct.
*/
struct SDL_Haptic
{
SDL_HapticID instance_id; // Device instance, monotonically increasing from 0
char *name; // Device name - system dependent
struct haptic_effect *effects; // Allocated effects
int neffects; // Maximum amount of effects
int nplaying; // Maximum amount of effects to play at the same time
Uint32 supported; // Supported effects and features
int naxes; // Number of axes on the device.
struct haptic_hwdata *hwdata; // Driver dependent
int ref_count; // Count for multiple opens
int rumble_id; // ID of rumble effect for simple rumble API.
SDL_HapticEffect rumble_effect; // Rumble effect.
struct SDL_Haptic *next; // pointer to next haptic we have allocated
};
/*
* Scans the system for haptic devices.
*
* Returns number of devices on success, -1 on error.
*/
extern bool SDL_SYS_HapticInit(void);
// Function to return the number of haptic devices plugged in right now
extern int SDL_SYS_NumHaptics(void);
/*
* Gets the instance ID of the haptic device
*/
extern SDL_HapticID SDL_SYS_HapticInstanceID(int index);
/*
* Gets the device dependent name of the haptic device
*/
extern const char *SDL_SYS_HapticName(int index);
/*
* Opens the haptic device for usage. The haptic device should have
* the index value set previously.
*/
extern bool SDL_SYS_HapticOpen(SDL_Haptic *haptic);
/*
* Returns the index of the haptic core pointer or -1 if none is found.
*/
extern int SDL_SYS_HapticMouse(void);
/*
* Checks to see if the joystick has haptic capabilities.
*/
extern bool SDL_SYS_JoystickIsHaptic(SDL_Joystick *joystick);
/*
* Opens the haptic device for usage using the same device as
* the joystick.
*/
extern bool SDL_SYS_HapticOpenFromJoystick(SDL_Haptic *haptic,
SDL_Joystick *joystick);
/*
* Checks to see if haptic device and joystick device are the same.
*
* Returns true if they are the same, false if they aren't.
*/
extern bool SDL_SYS_JoystickSameHaptic(SDL_Haptic *haptic,
SDL_Joystick *joystick);
/*
* Closes a haptic device after usage.
*/
extern void SDL_SYS_HapticClose(SDL_Haptic *haptic);
/*
* Performs a cleanup on the haptic subsystem.
*/
extern void SDL_SYS_HapticQuit(void);
/*
* Creates a new haptic effect on the haptic device using base
* as a template for the effect.
*/
extern bool SDL_SYS_HapticNewEffect(SDL_Haptic *haptic,
struct haptic_effect *effect,
const SDL_HapticEffect *base);
/*
* Updates the haptic effect on the haptic device using data
* as a template.
*/
extern bool SDL_SYS_HapticUpdateEffect(SDL_Haptic *haptic,
struct haptic_effect *effect,
const SDL_HapticEffect *data);
/*
* Runs the effect on the haptic device.
*/
extern bool SDL_SYS_HapticRunEffect(SDL_Haptic *haptic,
struct haptic_effect *effect,
Uint32 iterations);
/*
* Stops the effect on the haptic device.
*/
extern bool SDL_SYS_HapticStopEffect(SDL_Haptic *haptic,
struct haptic_effect *effect);
/*
* Cleanups up the effect on the haptic device.
*/
extern void SDL_SYS_HapticDestroyEffect(SDL_Haptic *haptic,
struct haptic_effect *effect);
/*
* Queries the device for the status of effect.
*
* Returns 0 if device is stopped, >0 if device is playing and
* -1 on error.
*/
extern int SDL_SYS_HapticGetEffectStatus(SDL_Haptic *haptic,
struct haptic_effect *effect);
/*
* Sets the global gain of the haptic device.
*/
extern bool SDL_SYS_HapticSetGain(SDL_Haptic *haptic, int gain);
/*
* Sets the autocenter feature of the haptic device.
*/
extern bool SDL_SYS_HapticSetAutocenter(SDL_Haptic *haptic, int autocenter);
/*
* Pauses the haptic device.
*/
extern bool SDL_SYS_HapticPause(SDL_Haptic *haptic);
/*
* Unpauses the haptic device.
*/
extern bool SDL_SYS_HapticResume(SDL_Haptic *haptic);
/*
* Stops all the currently playing haptic effects on the device.
*/
extern bool SDL_SYS_HapticStopAll(SDL_Haptic *haptic);
// Ends C function definitions when using C++
#ifdef __cplusplus
}
#endif
#endif // SDL_syshaptic_h_

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,29 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
// Things named "Master" were renamed to "Main" in macOS 12.0's SDK.
#include <AvailabilityMacros.h>
#if MAC_OS_X_VERSION_MIN_REQUIRED < 120000
#define kIOMainPortDefault kIOMasterPortDefault
#endif
extern bool MacHaptic_MaybeAddDevice(io_object_t device);
extern bool MacHaptic_MaybeRemoveDevice(io_object_t device);

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,53 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#include "SDL_windowshaptic_c.h"
// Set up for C function definitions, even when using C++
#ifdef __cplusplus
extern "C" {
#endif
extern bool SDL_DINPUT_HapticInit(void);
extern bool SDL_DINPUT_HapticMaybeAddDevice(const DIDEVICEINSTANCE *pdidInstance);
extern bool SDL_DINPUT_HapticMaybeRemoveDevice(const DIDEVICEINSTANCE *pdidInstance);
extern bool SDL_DINPUT_HapticOpen(SDL_Haptic *haptic, SDL_hapticlist_item *item);
extern bool SDL_DINPUT_JoystickSameHaptic(SDL_Haptic *haptic, SDL_Joystick *joystick);
extern bool SDL_DINPUT_HapticOpenFromJoystick(SDL_Haptic *haptic, SDL_Joystick *joystick);
extern void SDL_DINPUT_HapticClose(SDL_Haptic *haptic);
extern void SDL_DINPUT_HapticQuit(void);
extern bool SDL_DINPUT_HapticNewEffect(SDL_Haptic *haptic, struct haptic_effect *effect, const SDL_HapticEffect *base);
extern bool SDL_DINPUT_HapticUpdateEffect(SDL_Haptic *haptic, struct haptic_effect *effect, const SDL_HapticEffect *data);
extern bool SDL_DINPUT_HapticRunEffect(SDL_Haptic *haptic, struct haptic_effect *effect, Uint32 iterations);
extern bool SDL_DINPUT_HapticStopEffect(SDL_Haptic *haptic, struct haptic_effect *effect);
extern void SDL_DINPUT_HapticDestroyEffect(SDL_Haptic *haptic, struct haptic_effect *effect);
extern int SDL_DINPUT_HapticGetEffectStatus(SDL_Haptic *haptic, struct haptic_effect *effect);
extern bool SDL_DINPUT_HapticSetGain(SDL_Haptic *haptic, int gain);
extern bool SDL_DINPUT_HapticSetAutocenter(SDL_Haptic *haptic, int autocenter);
extern bool SDL_DINPUT_HapticPause(SDL_Haptic *haptic);
extern bool SDL_DINPUT_HapticResume(SDL_Haptic *haptic);
extern bool SDL_DINPUT_HapticStopAll(SDL_Haptic *haptic);
// Ends C function definitions when using C++
#ifdef __cplusplus
}
#endif

View file

@ -0,0 +1,369 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#ifdef SDL_HAPTIC_DINPUT
#include "../SDL_syshaptic.h"
#include "../../joystick/SDL_sysjoystick.h" // For the real SDL_Joystick
#include "../../joystick/windows/SDL_windowsjoystick_c.h" // For joystick hwdata
#include "../../joystick/windows/SDL_xinputjoystick_c.h" // For xinput rumble
#include "SDL_windowshaptic_c.h"
#include "SDL_dinputhaptic_c.h"
// Set up for C function definitions, even when using C++
#ifdef __cplusplus
extern "C" {
#endif
/*
* Internal stuff.
*/
SDL_hapticlist_item *SDL_hapticlist = NULL;
static SDL_hapticlist_item *SDL_hapticlist_tail = NULL;
static int numhaptics = 0;
/*
* Initializes the haptic subsystem.
*/
bool SDL_SYS_HapticInit(void)
{
JoyStick_DeviceData *device;
if (!SDL_DINPUT_HapticInit()) {
return false;
}
/* The joystick subsystem will usually be initialized before haptics,
* so the initial HapticMaybeAddDevice() calls from the joystick
* subsystem will arrive too early to create haptic devices. We will
* invoke those callbacks again here to pick up any joysticks that
* were added prior to haptics initialization. */
for (device = SYS_Joystick; device; device = device->pNext) {
SDL_DINPUT_HapticMaybeAddDevice(&device->dxdevice);
}
return true;
}
bool SDL_SYS_AddHapticDevice(SDL_hapticlist_item *item)
{
if (!SDL_hapticlist_tail) {
SDL_hapticlist = SDL_hapticlist_tail = item;
} else {
SDL_hapticlist_tail->next = item;
SDL_hapticlist_tail = item;
}
// Device has been added.
++numhaptics;
return true;
}
bool SDL_SYS_RemoveHapticDevice(SDL_hapticlist_item *prev, SDL_hapticlist_item *item)
{
const bool result = item->haptic ? true : false;
if (prev) {
prev->next = item->next;
} else {
SDL_assert(SDL_hapticlist == item);
SDL_hapticlist = item->next;
}
if (item == SDL_hapticlist_tail) {
SDL_hapticlist_tail = prev;
}
--numhaptics;
// !!! TODO: Send a haptic remove event?
SDL_free(item);
return result;
}
int SDL_SYS_NumHaptics(void)
{
return numhaptics;
}
static SDL_hapticlist_item *HapticByDevIndex(int device_index)
{
SDL_hapticlist_item *item = SDL_hapticlist;
if ((device_index < 0) || (device_index >= numhaptics)) {
return NULL;
}
while (device_index > 0) {
SDL_assert(item != NULL);
--device_index;
item = item->next;
}
return item;
}
static SDL_hapticlist_item *HapticByInstanceID(SDL_HapticID instance_id)
{
SDL_hapticlist_item *item;
for (item = SDL_hapticlist; item; item = item->next) {
if (instance_id == item->instance_id) {
return item;
}
}
return NULL;
}
SDL_HapticID SDL_SYS_HapticInstanceID(int index)
{
SDL_hapticlist_item *item = HapticByDevIndex(index);
if (item) {
return item->instance_id;
}
return 0;
}
/*
* Return the name of a haptic device, does not need to be opened.
*/
const char *SDL_SYS_HapticName(int index)
{
SDL_hapticlist_item *item = HapticByDevIndex(index);
return item->name;
}
/*
* Opens a haptic device for usage.
*/
bool SDL_SYS_HapticOpen(SDL_Haptic *haptic)
{
SDL_hapticlist_item *item = HapticByInstanceID(haptic->instance_id);
return SDL_DINPUT_HapticOpen(haptic, item);
}
/*
* Opens a haptic device from first mouse it finds for usage.
*/
int SDL_SYS_HapticMouse(void)
{
#ifdef SDL_HAPTIC_DINPUT
SDL_hapticlist_item *item;
int index = 0;
// Grab the first mouse haptic device we find.
for (item = SDL_hapticlist; item; item = item->next) {
if (item->capabilities.dwDevType == DI8DEVCLASS_POINTER) {
return index;
}
++index;
}
#endif // SDL_HAPTIC_DINPUT
return -1;
}
/*
* Checks to see if a joystick has haptic features.
*/
bool SDL_SYS_JoystickIsHaptic(SDL_Joystick *joystick)
{
if (joystick->driver != &SDL_WINDOWS_JoystickDriver) {
return false;
}
if (joystick->hwdata->Capabilities.dwFlags & DIDC_FORCEFEEDBACK) {
return true;
}
return false;
}
/*
* Checks to see if the haptic device and joystick are in reality the same.
*/
bool SDL_SYS_JoystickSameHaptic(SDL_Haptic *haptic, SDL_Joystick *joystick)
{
if (joystick->driver != &SDL_WINDOWS_JoystickDriver) {
return false;
}
return SDL_DINPUT_JoystickSameHaptic(haptic, joystick);
}
/*
* Opens a SDL_Haptic from a SDL_Joystick.
*/
bool SDL_SYS_HapticOpenFromJoystick(SDL_Haptic *haptic, SDL_Joystick *joystick)
{
SDL_assert(joystick->driver == &SDL_WINDOWS_JoystickDriver);
return SDL_DINPUT_HapticOpenFromJoystick(haptic, joystick);
}
/*
* Closes the haptic device.
*/
void SDL_SYS_HapticClose(SDL_Haptic *haptic)
{
if (haptic->hwdata) {
// Free effects.
SDL_free(haptic->effects);
haptic->effects = NULL;
haptic->neffects = 0;
// Clean up
SDL_DINPUT_HapticClose(haptic);
// Free
SDL_free(haptic->hwdata);
haptic->hwdata = NULL;
}
}
/*
* Clean up after system specific haptic stuff
*/
void SDL_SYS_HapticQuit(void)
{
SDL_hapticlist_item *item;
SDL_hapticlist_item *next = NULL;
for (item = SDL_hapticlist; item; item = next) {
/* Opened and not closed haptics are leaked, this is on purpose.
* Close your haptic devices after usage. */
// !!! FIXME: (...is leaking on purpose a good idea?) - No, of course not.
next = item->next;
SDL_free(item->name);
SDL_free(item);
}
SDL_DINPUT_HapticQuit();
numhaptics = 0;
SDL_hapticlist = NULL;
SDL_hapticlist_tail = NULL;
}
/*
* Creates a new haptic effect.
*/
bool SDL_SYS_HapticNewEffect(SDL_Haptic *haptic, struct haptic_effect *effect,
const SDL_HapticEffect *base)
{
bool result;
// Alloc the effect.
effect->hweffect = (struct haptic_hweffect *) SDL_calloc(1, sizeof(struct haptic_hweffect));
if (!effect->hweffect) {
return false;
}
result = SDL_DINPUT_HapticNewEffect(haptic, effect, base);
if (!result) {
SDL_free(effect->hweffect);
effect->hweffect = NULL;
}
return result;
}
/*
* Updates an effect.
*/
bool SDL_SYS_HapticUpdateEffect(SDL_Haptic *haptic, struct haptic_effect *effect, const SDL_HapticEffect *data)
{
return SDL_DINPUT_HapticUpdateEffect(haptic, effect, data);
}
/*
* Runs an effect.
*/
bool SDL_SYS_HapticRunEffect(SDL_Haptic *haptic, struct haptic_effect *effect, Uint32 iterations)
{
return SDL_DINPUT_HapticRunEffect(haptic, effect, iterations);
}
/*
* Stops an effect.
*/
bool SDL_SYS_HapticStopEffect(SDL_Haptic *haptic, struct haptic_effect *effect)
{
return SDL_DINPUT_HapticStopEffect(haptic, effect);
}
/*
* Frees the effect.
*/
void SDL_SYS_HapticDestroyEffect(SDL_Haptic *haptic, struct haptic_effect *effect)
{
SDL_DINPUT_HapticDestroyEffect(haptic, effect);
SDL_free(effect->hweffect);
effect->hweffect = NULL;
}
/*
* Gets the status of a haptic effect.
*/
int SDL_SYS_HapticGetEffectStatus(SDL_Haptic *haptic, struct haptic_effect *effect)
{
return SDL_DINPUT_HapticGetEffectStatus(haptic, effect);
}
/*
* Sets the gain.
*/
bool SDL_SYS_HapticSetGain(SDL_Haptic *haptic, int gain)
{
return SDL_DINPUT_HapticSetGain(haptic, gain);
}
/*
* Sets the autocentering.
*/
bool SDL_SYS_HapticSetAutocenter(SDL_Haptic *haptic, int autocenter)
{
return SDL_DINPUT_HapticSetAutocenter(haptic, autocenter);
}
/*
* Pauses the device.
*/
bool SDL_SYS_HapticPause(SDL_Haptic *haptic)
{
return SDL_DINPUT_HapticPause(haptic);
}
/*
* Pauses the device.
*/
bool SDL_SYS_HapticResume(SDL_Haptic *haptic)
{
return SDL_DINPUT_HapticResume(haptic);
}
/*
* Stops all the playing effects on the device.
*/
bool SDL_SYS_HapticStopAll(SDL_Haptic *haptic)
{
return SDL_DINPUT_HapticStopAll(haptic);
}
// Ends C function definitions when using C++
#ifdef __cplusplus
}
#endif
#endif // SDL_HAPTIC_DINPUT

View file

@ -0,0 +1,87 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#ifndef SDL_windowshaptic_c_h_
#define SDL_windowshaptic_c_h_
#include "../SDL_syshaptic.h"
#include "../../core/windows/SDL_directx.h"
#include "../../core/windows/SDL_xinput.h"
// Set up for C function definitions, even when using C++
#ifdef __cplusplus
extern "C" {
#endif
/*
* Haptic system hardware data.
*/
struct haptic_hwdata
{
#ifdef SDL_HAPTIC_DINPUT
LPDIRECTINPUTDEVICE8 device;
#endif
DWORD axes[3]; // Axes to use.
bool is_joystick; // Device is loaded as joystick.
SDL_Thread *thread;
SDL_Mutex *mutex;
Uint64 stopTicks;
SDL_AtomicInt stopThread;
};
/*
* Haptic system effect data.
*/
#ifdef SDL_HAPTIC_DINPUT
struct haptic_hweffect
{
DIEFFECT effect;
LPDIRECTINPUTEFFECT ref;
};
#endif
/*
* List of available haptic devices.
*/
typedef struct SDL_hapticlist_item
{
SDL_HapticID instance_id;
char *name;
SDL_Haptic *haptic;
#ifdef SDL_HAPTIC_DINPUT
DIDEVICEINSTANCE instance;
DIDEVCAPS capabilities;
#endif
struct SDL_hapticlist_item *next;
} SDL_hapticlist_item;
extern SDL_hapticlist_item *SDL_hapticlist;
extern bool SDL_SYS_AddHapticDevice(SDL_hapticlist_item *item);
extern bool SDL_SYS_RemoveHapticDevice(SDL_hapticlist_item *prev, SDL_hapticlist_item *item);
// Ends C function definitions when using C++
#ifdef __cplusplus
}
#endif
#endif // SDL_windowshaptic_c_h_

18
thirdparty/sdl/hidapi/AUTHORS.txt vendored Normal file
View file

@ -0,0 +1,18 @@
HIDAPI Authors:
Alan Ott <alan@signal11.us>:
Original Author and Maintainer
Linux, Windows, and Mac implementations
Ludovic Rousseau <rousseau@debian.org>:
Formatting for Doxygen documentation
Bug fixes
Correctness fixes
libusb/hidapi Team:
Development/maintenance since June 4th 2019
For a comprehensive list of contributions, see the commit list at github:
https://github.com/libusb/hidapi/graphs/contributors

26
thirdparty/sdl/hidapi/LICENSE-bsd.txt vendored Normal file
View file

@ -0,0 +1,26 @@
Copyright (c) 2010, Alan Ott, Signal 11 Software
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of Signal 11 Software nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.

13
thirdparty/sdl/hidapi/LICENSE.txt vendored Normal file
View file

@ -0,0 +1,13 @@
HIDAPI can be used under one of three licenses.
1. The GNU General Public License, version 3.0, in LICENSE-gpl3.txt
2. A BSD-Style License, in LICENSE-bsd.txt.
3. The more liberal original HIDAPI license. LICENSE-orig.txt
The license chosen is at the discretion of the user of HIDAPI. For example:
1. An author of GPL software would likely use HIDAPI under the terms of the
GPL.
2. An author of commercial closed-source software would likely use HIDAPI
under the terms of the BSD-style license or the original HIDAPI license.

1752
thirdparty/sdl/hidapi/SDL_hidapi.c vendored Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,26 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
/* The implementation for Android is in a separate .cpp file */
#undef HIDAPI_H__
#include "hidapi/hidapi.h"
#define HAVE_PLATFORM_BACKEND 1
#define udev_ctx 1

35
thirdparty/sdl/hidapi/SDL_hidapi_c.h vendored Normal file
View file

@ -0,0 +1,35 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
/* Return true if the HIDAPI should ignore a device during enumeration */
extern bool SDL_HIDAPI_ShouldIgnoreDevice(int bus_type, Uint16 vendor_id, Uint16 product_id, Uint16 usage_page, Uint16 usage);
#ifdef SDL_JOYSTICK_HIDAPI
#ifdef HAVE_LIBUSB
#define HAVE_ENABLE_GAMECUBE_ADAPTORS
#endif
#ifdef HAVE_ENABLE_GAMECUBE_ADAPTORS
extern void SDL_EnableGameCubeAdaptors(void);
#endif
#endif /* SDL_JOYSTICK_HIDAPI */

26
thirdparty/sdl/hidapi/SDL_hidapi_ios.h vendored Normal file
View file

@ -0,0 +1,26 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
/* The implementation for iOS and tvOS is in a separate .m file */
#undef HIDAPI_H__
#include "hidapi/hidapi.h"
#define HAVE_PLATFORM_BACKEND 1
#define udev_ctx 1

View file

@ -0,0 +1,134 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
/* Define standard library functions in terms of SDL */
/* #pragma push_macro/pop_macro works correctly only as of gcc >= 4.4.3
clang-3.0 _seems_ to be OK. */
#pragma push_macro("calloc")
#pragma push_macro("malloc")
#pragma push_macro("realloc")
#pragma push_macro("free")
#pragma push_macro("iconv_t")
#pragma push_macro("iconv")
#pragma push_macro("iconv_open")
#pragma push_macro("iconv_close")
#pragma push_macro("setlocale")
#pragma push_macro("snprintf")
#pragma push_macro("strcmp")
#pragma push_macro("strdup")
#pragma push_macro("strncpy")
#pragma push_macro("tolower")
#pragma push_macro("wcscmp")
#pragma push_macro("wcsdup")
#pragma push_macro("wcsncpy")
#undef calloc
#undef malloc
#undef realloc
#undef free
#undef iconv_t
#undef iconv
#undef iconv_open
#undef iconv_close
#undef setlocale
#undef snprintf
#undef strcmp
#undef strdup
#undef strncpy
#undef tolower
#undef wcscmp
#undef wcsdup
#undef wcsncpy
#define calloc SDL_calloc
#define malloc SDL_malloc
#define realloc SDL_realloc
#define free SDL_free
#define iconv_t SDL_iconv_t
#ifndef ICONV_CONST
#define ICONV_CONST
#define UNDEF_ICONV_CONST
#endif
#define iconv(a,b,c,d,e) SDL_iconv(a, (const char **)b, c, d, e)
#define iconv_open SDL_iconv_open
#define iconv_close SDL_iconv_close
#define setlocale(X, Y) NULL
#define snprintf SDL_snprintf
#define strcmp SDL_strcmp
#define strdup SDL_strdup
#define strncpy SDL_strlcpy
#define tolower SDL_tolower
#define wcscmp SDL_wcscmp
#define wcsdup SDL_wcsdup
#define wcsncpy SDL_wcslcpy
#ifndef SDL_PLATFORM_FREEBSD
/* this is awkwardly inlined, so we need to re-implement it here
* so we can override the libusb_control_transfer call */
static int SDL_libusb_get_string_descriptor(libusb_device_handle *dev,
uint8_t descriptor_index, uint16_t lang_id,
unsigned char *data, int length)
{
return libusb_control_transfer(dev, LIBUSB_ENDPOINT_IN | 0x0, LIBUSB_REQUEST_GET_DESCRIPTOR, (LIBUSB_DT_STRING << 8) | descriptor_index, lang_id,
data, (uint16_t)length, 1000); /* Endpoint 0 IN */
}
#define libusb_get_string_descriptor SDL_libusb_get_string_descriptor
#endif /* SDL_PLATFORM_FREEBSD */
#define HIDAPI_THREAD_MODEL_INCLUDE "hidapi_thread_sdl.h"
#ifndef LIBUSB_API_VERSION
#ifdef LIBUSBX_API_VERSION
#define LIBUSB_API_VERSION LIBUSBX_API_VERSION
#else
#define LIBUSB_API_VERSION 0x0
#endif
#endif
/* we need libusb >= 1.0.16 because of libusb_get_port_numbers */
/* we don't need libusb_wrap_sys_device: */
#define HIDAPI_TARGET_LIBUSB_API_VERSION 0x01000102
#undef HIDAPI_H__
#include "libusb/hid.c"
/* restore libc function macros */
#ifdef UNDEF_ICONV_CONST
#undef ICONV_CONST
#undef UNDEF_ICONV_CONST
#endif
#pragma pop_macro("calloc")
#pragma pop_macro("malloc")
#pragma pop_macro("realloc")
#pragma pop_macro("free")
#pragma pop_macro("iconv_t")
#pragma pop_macro("iconv")
#pragma pop_macro("iconv_open")
#pragma pop_macro("iconv_close")
#pragma pop_macro("setlocale")
#pragma pop_macro("snprintf")
#pragma pop_macro("strcmp")
#pragma pop_macro("strdup")
#pragma pop_macro("strncpy")
#pragma pop_macro("tolower")
#pragma pop_macro("wcscmp")
#pragma pop_macro("wcsdup")
#pragma pop_macro("wcsncpy")

View file

@ -0,0 +1,47 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#ifdef SDL_USE_LIBUDEV
static const SDL_UDEV_Symbols *udev_ctx = NULL;
#define udev_device_get_devnode udev_ctx->udev_device_get_devnode
#define udev_device_get_parent_with_subsystem_devtype udev_ctx->udev_device_get_parent_with_subsystem_devtype
#define udev_device_get_sysattr_value udev_ctx->udev_device_get_sysattr_value
#define udev_device_get_syspath udev_ctx->udev_device_get_syspath
#define udev_device_new_from_devnum udev_ctx->udev_device_new_from_devnum
#define udev_device_new_from_syspath udev_ctx->udev_device_new_from_syspath
#define udev_device_unref udev_ctx->udev_device_unref
#define udev_enumerate_add_match_subsystem udev_ctx->udev_enumerate_add_match_subsystem
#define udev_enumerate_get_list_entry udev_ctx->udev_enumerate_get_list_entry
#define udev_enumerate_new udev_ctx->udev_enumerate_new
#define udev_enumerate_scan_devices udev_ctx->udev_enumerate_scan_devices
#define udev_enumerate_unref udev_ctx->udev_enumerate_unref
#define udev_list_entry_get_name udev_ctx->udev_list_entry_get_name
#define udev_list_entry_get_next udev_ctx->udev_list_entry_get_next
#define udev_new udev_ctx->udev_new
#define udev_unref udev_ctx->udev_unref
#undef HIDAPI_H__
#define HIDAPI_ALLOW_BUILD_WORKAROUND_KERNEL_2_6_39
#include "linux/hid.c"
#define HAVE_PLATFORM_BACKEND 1
#endif /* SDL_USE_LIBUDEV */

25
thirdparty/sdl/hidapi/SDL_hidapi_mac.h vendored Normal file
View file

@ -0,0 +1,25 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#undef HIDAPI_H__
#include "mac/hid.c"
#define HAVE_PLATFORM_BACKEND 1
#define udev_ctx 1

View file

@ -0,0 +1,25 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#undef HIDAPI_H__
#include "netbsd/hid.c"
#define HAVE_PLATFORM_BACKEND 1
#define udev_ctx 1

View file

@ -0,0 +1,23 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#undef HIDAPI_H__
#include "steamxbox/hid.c"

View file

@ -0,0 +1,86 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
/* Define standard library functions in terms of SDL */
/* #pragma push_macro/pop_macro works correctly only as of gcc >= 4.4.3
clang-3.0 _seems_ to be OK. */
#pragma push_macro("calloc")
#pragma push_macro("free")
#pragma push_macro("malloc")
#pragma push_macro("memcmp")
#pragma push_macro("swprintf")
#pragma push_macro("towupper")
#pragma push_macro("wcscmp")
#pragma push_macro("_wcsdup")
#pragma push_macro("wcslen")
#pragma push_macro("wcsncpy")
#pragma push_macro("wcsstr")
#pragma push_macro("wcstol")
#undef calloc
#undef free
#undef malloc
#undef memcmp
#undef swprintf
#undef towupper
#undef wcscmp
#undef _wcsdup
#undef wcslen
#undef wcsncpy
#undef wcsstr
#undef wcstol
#define calloc SDL_calloc
#define free SDL_free
#define malloc SDL_malloc
#define memcmp SDL_memcmp
#define swprintf SDL_swprintf
#define towupper (wchar_t)SDL_toupper
#define wcscmp SDL_wcscmp
#define _wcsdup SDL_wcsdup
#define wcslen SDL_wcslen
#define wcsncpy SDL_wcslcpy
#define wcsstr SDL_wcsstr
#define wcstol SDL_wcstol
// These functions conflict when linking both SDL and hidapi statically
#define hid_winapi_descriptor_reconstruct_pp_data SDL_hid_winapi_descriptor_reconstruct_pp_data
#define hid_winapi_get_container_id SDL_hid_winapi_get_container_id
#undef HIDAPI_H__
#include "windows/hid.c"
#define HAVE_PLATFORM_BACKEND 1
#define udev_ctx 1
/* restore libc function macros */
#pragma pop_macro("calloc")
#pragma pop_macro("free")
#pragma pop_macro("malloc")
#pragma pop_macro("memcmp")
#pragma pop_macro("swprintf")
#pragma pop_macro("towupper")
#pragma pop_macro("wcscmp")
#pragma pop_macro("_wcsdup")
#pragma pop_macro("wcslen")
#pragma pop_macro("wcsncpy")
#pragma pop_macro("wcsstr")
#pragma pop_macro("wcstol")

1
thirdparty/sdl/hidapi/VERSION vendored Normal file
View file

@ -0,0 +1 @@
0.14.0

634
thirdparty/sdl/hidapi/hidapi/hidapi.h vendored Normal file
View file

@ -0,0 +1,634 @@
/*******************************************************
HIDAPI - Multi-Platform library for
communication with HID devices.
Alan Ott
Signal 11 Software
libusb/hidapi Team
Copyright 2023, All Rights Reserved.
At the discretion of the user of this library,
this software may be licensed under the terms of the
GNU General Public License v3, a BSD-Style license, or the
original HIDAPI license as outlined in the LICENSE.txt,
LICENSE-gpl3.txt, LICENSE-bsd.txt, and LICENSE-orig.txt
files located at the root of the source distribution.
These files may also be found in the public source
code repository located at:
https://github.com/libusb/hidapi .
********************************************************/
/** @file
* @defgroup API hidapi API
*/
#ifndef HIDAPI_H__
#define HIDAPI_H__
#include <wchar.h>
/* #480: this is to be refactored properly for v1.0 */
#ifdef _WIN32
#ifndef HID_API_NO_EXPORT_DEFINE
#define HID_API_EXPORT __declspec(dllexport)
#endif
#endif
#ifndef HID_API_EXPORT
#define HID_API_EXPORT /**< API export macro */
#endif
/* To be removed in v1.0 */
#define HID_API_CALL /**< API call macro */
#define HID_API_EXPORT_CALL HID_API_EXPORT HID_API_CALL /**< API export and call macro*/
/** @brief Static/compile-time major version of the library.
@ingroup API
*/
#define HID_API_VERSION_MAJOR 0
/** @brief Static/compile-time minor version of the library.
@ingroup API
*/
#define HID_API_VERSION_MINOR 14
/** @brief Static/compile-time patch version of the library.
@ingroup API
*/
#define HID_API_VERSION_PATCH 0
/* Helper macros */
#define HID_API_AS_STR_IMPL(x) #x
#define HID_API_AS_STR(x) HID_API_AS_STR_IMPL(x)
#define HID_API_TO_VERSION_STR(v1, v2, v3) HID_API_AS_STR(v1.v2.v3)
/** @brief Coverts a version as Major/Minor/Patch into a number:
<8 bit major><16 bit minor><8 bit patch>.
This macro was added in version 0.12.0.
Convenient function to be used for compile-time checks, like:
@code{.c}
#if HID_API_VERSION >= HID_API_MAKE_VERSION(0, 12, 0)
@endcode
@ingroup API
*/
#define HID_API_MAKE_VERSION(mj, mn, p) (((mj) << 24) | ((mn) << 8) | (p))
/** @brief Static/compile-time version of the library.
This macro was added in version 0.12.0.
@see @ref HID_API_MAKE_VERSION.
@ingroup API
*/
#define HID_API_VERSION HID_API_MAKE_VERSION(HID_API_VERSION_MAJOR, HID_API_VERSION_MINOR, HID_API_VERSION_PATCH)
/** @brief Static/compile-time string version of the library.
@ingroup API
*/
#define HID_API_VERSION_STR HID_API_TO_VERSION_STR(HID_API_VERSION_MAJOR, HID_API_VERSION_MINOR, HID_API_VERSION_PATCH)
/** @brief Maximum expected HID Report descriptor size in bytes.
Since version 0.13.0, @ref HID_API_VERSION >= HID_API_MAKE_VERSION(0, 13, 0)
@ingroup API
*/
#define HID_API_MAX_REPORT_DESCRIPTOR_SIZE 4096
#ifdef __cplusplus
extern "C" {
#endif
#ifndef DEFINED_HID_TYPES
#define DEFINED_HID_TYPES
/** A structure to hold the version numbers. */
struct hid_api_version {
int major; /**< major version number */
int minor; /**< minor version number */
int patch; /**< patch version number */
};
struct hid_device_;
typedef struct hid_device_ hid_device; /**< opaque hidapi structure */
/** @brief HID underlying bus types.
@ingroup API
*/
typedef enum {
/** Unknown bus type */
HID_API_BUS_UNKNOWN = 0x00,
/** USB bus
Specifications:
https://usb.org/hid */
HID_API_BUS_USB = 0x01,
/** Bluetooth or Bluetooth LE bus
Specifications:
https://www.bluetooth.com/specifications/specs/human-interface-device-profile-1-1-1/
https://www.bluetooth.com/specifications/specs/hid-service-1-0/
https://www.bluetooth.com/specifications/specs/hid-over-gatt-profile-1-0/ */
HID_API_BUS_BLUETOOTH = 0x02,
/** I2C bus
Specifications:
https://docs.microsoft.com/previous-versions/windows/hardware/design/dn642101(v=vs.85) */
HID_API_BUS_I2C = 0x03,
/** SPI bus
Specifications:
https://www.microsoft.com/download/details.aspx?id=103325 */
HID_API_BUS_SPI = 0x04,
} hid_bus_type;
/** hidapi info structure */
struct hid_device_info {
/** Platform-specific device path */
char *path;
/** Device Vendor ID */
unsigned short vendor_id;
/** Device Product ID */
unsigned short product_id;
/** Serial Number */
wchar_t *serial_number;
/** Device Release Number in binary-coded decimal,
also known as Device Version Number */
unsigned short release_number;
/** Manufacturer String */
wchar_t *manufacturer_string;
/** Product string */
wchar_t *product_string;
/** Usage Page for this Device/Interface
(Windows/Mac/hidraw only) */
unsigned short usage_page;
/** Usage for this Device/Interface
(Windows/Mac/hidraw only) */
unsigned short usage;
/** The USB interface which this logical device
represents.
Valid only if the device is a USB HID device.
Set to -1 in all other cases.
*/
int interface_number;
/** Pointer to the next device */
struct hid_device_info *next;
/** Underlying bus type
Since version 0.13.0, @ref HID_API_VERSION >= HID_API_MAKE_VERSION(0, 13, 0)
*/
hid_bus_type bus_type;
/** Additional information about the USB interface.
(libusb only) */
int interface_class;
int interface_subclass;
int interface_protocol;
};
#endif /* DEFINED_HID_TYPES */
/** @brief Initialize the HIDAPI library.
This function initializes the HIDAPI library. Calling it is not
strictly necessary, as it will be called automatically by
hid_enumerate() and any of the hid_open_*() functions if it is
needed. This function should be called at the beginning of
execution however, if there is a chance of HIDAPI handles
being opened by different threads simultaneously.
@ingroup API
@returns
This function returns 0 on success and -1 on error.
Call hid_error(NULL) to get the failure reason.
*/
int HID_API_EXPORT HID_API_CALL hid_init(void);
/** @brief Finalize the HIDAPI library.
This function frees all of the static data associated with
HIDAPI. It should be called at the end of execution to avoid
memory leaks.
@ingroup API
@returns
This function returns 0 on success and -1 on error.
*/
int HID_API_EXPORT HID_API_CALL hid_exit(void);
/** @brief Enumerate the HID Devices.
This function returns a linked list of all the HID devices
attached to the system which match vendor_id and product_id.
If @p vendor_id is set to 0 then any vendor matches.
If @p product_id is set to 0 then any product matches.
If @p vendor_id and @p product_id are both set to 0, then
all HID devices will be returned.
@ingroup API
@param vendor_id The Vendor ID (VID) of the types of device
to open.
@param product_id The Product ID (PID) of the types of
device to open.
@returns
This function returns a pointer to a linked list of type
struct #hid_device_info, containing information about the HID devices
attached to the system,
or NULL in the case of failure or if no HID devices present in the system.
Call hid_error(NULL) to get the failure reason.
@note The returned value by this function must to be freed by calling hid_free_enumeration(),
when not needed anymore.
*/
struct hid_device_info HID_API_EXPORT * HID_API_CALL hid_enumerate(unsigned short vendor_id, unsigned short product_id);
/** @brief Free an enumeration Linked List
This function frees a linked list created by hid_enumerate().
@ingroup API
@param devs Pointer to a list of struct_device returned from
hid_enumerate().
*/
void HID_API_EXPORT HID_API_CALL hid_free_enumeration(struct hid_device_info *devs);
/** @brief Open a HID device using a Vendor ID (VID), Product ID
(PID) and optionally a serial number.
If @p serial_number is NULL, the first device with the
specified VID and PID is opened.
@ingroup API
@param vendor_id The Vendor ID (VID) of the device to open.
@param product_id The Product ID (PID) of the device to open.
@param serial_number The Serial Number of the device to open
(Optionally NULL).
@returns
This function returns a pointer to a #hid_device object on
success or NULL on failure.
Call hid_error(NULL) to get the failure reason.
@note The returned object must be freed by calling hid_close(),
when not needed anymore.
*/
HID_API_EXPORT hid_device * HID_API_CALL hid_open(unsigned short vendor_id, unsigned short product_id, const wchar_t *serial_number);
/** @brief Open a HID device by its path name.
The path name be determined by calling hid_enumerate(), or a
platform-specific path name can be used (eg: /dev/hidraw0 on
Linux).
@ingroup API
@param path The path name of the device to open
@returns
This function returns a pointer to a #hid_device object on
success or NULL on failure.
Call hid_error(NULL) to get the failure reason.
@note The returned object must be freed by calling hid_close(),
when not needed anymore.
*/
HID_API_EXPORT hid_device * HID_API_CALL hid_open_path(const char *path);
/** @brief Write an Output report to a HID device.
The first byte of @p data[] must contain the Report ID. For
devices which only support a single report, this must be set
to 0x0. The remaining bytes contain the report data. Since
the Report ID is mandatory, calls to hid_write() will always
contain one more byte than the report contains. For example,
if a hid report is 16 bytes long, 17 bytes must be passed to
hid_write(), the Report ID (or 0x0, for devices with a
single report), followed by the report data (16 bytes). In
this example, the length passed in would be 17.
hid_write() will send the data on the first OUT endpoint, if
one exists. If it does not, it will send the data through
the Control Endpoint (Endpoint 0).
@ingroup API
@param dev A device handle returned from hid_open().
@param data The data to send, including the report number as
the first byte.
@param length The length in bytes of the data to send.
@returns
This function returns the actual number of bytes written and
-1 on error.
Call hid_error(dev) to get the failure reason.
*/
int HID_API_EXPORT HID_API_CALL hid_write(hid_device *dev, const unsigned char *data, size_t length);
/** @brief Read an Input report from a HID device with timeout.
Input reports are returned
to the host through the INTERRUPT IN endpoint. The first byte will
contain the Report number if the device uses numbered reports.
@ingroup API
@param dev A device handle returned from hid_open().
@param data A buffer to put the read data into.
@param length The number of bytes to read. For devices with
multiple reports, make sure to read an extra byte for
the report number.
@param milliseconds timeout in milliseconds or -1 for blocking wait.
@returns
This function returns the actual number of bytes read and
-1 on error.
Call hid_error(dev) to get the failure reason.
If no packet was available to be read within
the timeout period, this function returns 0.
*/
int HID_API_EXPORT HID_API_CALL hid_read_timeout(hid_device *dev, unsigned char *data, size_t length, int milliseconds);
/** @brief Read an Input report from a HID device.
Input reports are returned
to the host through the INTERRUPT IN endpoint. The first byte will
contain the Report number if the device uses numbered reports.
@ingroup API
@param dev A device handle returned from hid_open().
@param data A buffer to put the read data into.
@param length The number of bytes to read. For devices with
multiple reports, make sure to read an extra byte for
the report number.
@returns
This function returns the actual number of bytes read and
-1 on error.
Call hid_error(dev) to get the failure reason.
If no packet was available to be read and
the handle is in non-blocking mode, this function returns 0.
*/
int HID_API_EXPORT HID_API_CALL hid_read(hid_device *dev, unsigned char *data, size_t length);
/** @brief Set the device handle to be non-blocking.
In non-blocking mode calls to hid_read() will return
immediately with a value of 0 if there is no data to be
read. In blocking mode, hid_read() will wait (block) until
there is data to read before returning.
Nonblocking can be turned on and off at any time.
@ingroup API
@param dev A device handle returned from hid_open().
@param nonblock enable or not the nonblocking reads
- 1 to enable nonblocking
- 0 to disable nonblocking.
@returns
This function returns 0 on success and -1 on error.
Call hid_error(dev) to get the failure reason.
*/
int HID_API_EXPORT HID_API_CALL hid_set_nonblocking(hid_device *dev, int nonblock);
/** @brief Send a Feature report to the device.
Feature reports are sent over the Control endpoint as a
Set_Report transfer. The first byte of @p data[] must
contain the Report ID. For devices which only support a
single report, this must be set to 0x0. The remaining bytes
contain the report data. Since the Report ID is mandatory,
calls to hid_send_feature_report() will always contain one
more byte than the report contains. For example, if a hid
report is 16 bytes long, 17 bytes must be passed to
hid_send_feature_report(): the Report ID (or 0x0, for
devices which do not use numbered reports), followed by the
report data (16 bytes). In this example, the length passed
in would be 17.
@ingroup API
@param dev A device handle returned from hid_open().
@param data The data to send, including the report number as
the first byte.
@param length The length in bytes of the data to send, including
the report number.
@returns
This function returns the actual number of bytes written and
-1 on error.
Call hid_error(dev) to get the failure reason.
*/
int HID_API_EXPORT HID_API_CALL hid_send_feature_report(hid_device *dev, const unsigned char *data, size_t length);
/** @brief Get a feature report from a HID device.
Set the first byte of @p data[] to the Report ID of the
report to be read. Make sure to allow space for this
extra byte in @p data[]. Upon return, the first byte will
still contain the Report ID, and the report data will
start in data[1].
@ingroup API
@param dev A device handle returned from hid_open().
@param data A buffer to put the read data into, including
the Report ID. Set the first byte of @p data[] to the
Report ID of the report to be read, or set it to zero
if your device does not use numbered reports.
@param length The number of bytes to read, including an
extra byte for the report ID. The buffer can be longer
than the actual report.
@returns
This function returns the number of bytes read plus
one for the report ID (which is still in the first
byte), or -1 on error.
Call hid_error(dev) to get the failure reason.
*/
int HID_API_EXPORT HID_API_CALL hid_get_feature_report(hid_device *dev, unsigned char *data, size_t length);
/** @brief Get a input report from a HID device.
Since version 0.10.0, @ref HID_API_VERSION >= HID_API_MAKE_VERSION(0, 10, 0)
Set the first byte of @p data[] to the Report ID of the
report to be read. Make sure to allow space for this
extra byte in @p data[]. Upon return, the first byte will
still contain the Report ID, and the report data will
start in data[1].
@ingroup API
@param dev A device handle returned from hid_open().
@param data A buffer to put the read data into, including
the Report ID. Set the first byte of @p data[] to the
Report ID of the report to be read, or set it to zero
if your device does not use numbered reports.
@param length The number of bytes to read, including an
extra byte for the report ID. The buffer can be longer
than the actual report.
@returns
This function returns the number of bytes read plus
one for the report ID (which is still in the first
byte), or -1 on error.
Call hid_error(dev) to get the failure reason.
*/
int HID_API_EXPORT HID_API_CALL hid_get_input_report(hid_device *dev, unsigned char *data, size_t length);
/** @brief Close a HID device.
@ingroup API
@param dev A device handle returned from hid_open().
*/
void HID_API_EXPORT HID_API_CALL hid_close(hid_device *dev);
/** @brief Get The Manufacturer String from a HID device.
@ingroup API
@param dev A device handle returned from hid_open().
@param string A wide string buffer to put the data into.
@param maxlen The length of the buffer in multiples of wchar_t.
@returns
This function returns 0 on success and -1 on error.
Call hid_error(dev) to get the failure reason.
*/
int HID_API_EXPORT_CALL hid_get_manufacturer_string(hid_device *dev, wchar_t *string, size_t maxlen);
/** @brief Get The Product String from a HID device.
@ingroup API
@param dev A device handle returned from hid_open().
@param string A wide string buffer to put the data into.
@param maxlen The length of the buffer in multiples of wchar_t.
@returns
This function returns 0 on success and -1 on error.
Call hid_error(dev) to get the failure reason.
*/
int HID_API_EXPORT_CALL hid_get_product_string(hid_device *dev, wchar_t *string, size_t maxlen);
/** @brief Get The Serial Number String from a HID device.
@ingroup API
@param dev A device handle returned from hid_open().
@param string A wide string buffer to put the data into.
@param maxlen The length of the buffer in multiples of wchar_t.
@returns
This function returns 0 on success and -1 on error.
Call hid_error(dev) to get the failure reason.
*/
int HID_API_EXPORT_CALL hid_get_serial_number_string(hid_device *dev, wchar_t *string, size_t maxlen);
/** @brief Get The struct #hid_device_info from a HID device.
Since version 0.13.0, @ref HID_API_VERSION >= HID_API_MAKE_VERSION(0, 13, 0)
@ingroup API
@param dev A device handle returned from hid_open().
@returns
This function returns a pointer to the struct #hid_device_info
for this hid_device, or NULL in the case of failure.
Call hid_error(dev) to get the failure reason.
This struct is valid until the device is closed with hid_close().
@note The returned object is owned by the @p dev, and SHOULD NOT be freed by the user.
*/
struct hid_device_info HID_API_EXPORT * HID_API_CALL hid_get_device_info(hid_device *dev);
/** @brief Get a string from a HID device, based on its string index.
@ingroup API
@param dev A device handle returned from hid_open().
@param string_index The index of the string to get.
@param string A wide string buffer to put the data into.
@param maxlen The length of the buffer in multiples of wchar_t.
@returns
This function returns 0 on success and -1 on error.
Call hid_error(dev) to get the failure reason.
*/
int HID_API_EXPORT_CALL hid_get_indexed_string(hid_device *dev, int string_index, wchar_t *string, size_t maxlen);
/** @brief Get a report descriptor from a HID device.
Since version 0.14.0, @ref HID_API_VERSION >= HID_API_MAKE_VERSION(0, 14, 0)
User has to provide a preallocated buffer where descriptor will be copied to.
The recommended size for preallocated buffer is @ref HID_API_MAX_REPORT_DESCRIPTOR_SIZE bytes.
@ingroup API
@param dev A device handle returned from hid_open().
@param buf The buffer to copy descriptor into.
@param buf_size The size of the buffer in bytes.
@returns
This function returns non-negative number of bytes actually copied, or -1 on error.
*/
int HID_API_EXPORT_CALL hid_get_report_descriptor(hid_device *dev, unsigned char *buf, size_t buf_size);
/** @brief Get a string describing the last error which occurred.
This function is intended for logging/debugging purposes.
This function guarantees to never return NULL.
If there was no error in the last function call -
the returned string clearly indicates that.
Any HIDAPI function that can explicitly indicate an execution failure
(e.g. by an error code, or by returning NULL) - may set the error string,
to be returned by this function.
Strings returned from hid_error() must not be freed by the user,
i.e. owned by HIDAPI library.
Device-specific error string may remain allocated at most until hid_close() is called.
Global error string may remain allocated at most until hid_exit() is called.
@ingroup API
@param dev A device handle returned from hid_open(),
or NULL to get the last non-device-specific error
(e.g. for errors in hid_open() or hid_enumerate()).
@returns
A string describing the last error (if any).
*/
HID_API_EXPORT const wchar_t* HID_API_CALL hid_error(hid_device *dev);
/** @brief Get a runtime version of the library.
This function is thread-safe.
@ingroup API
@returns
Pointer to statically allocated struct, that contains version.
*/
HID_API_EXPORT const struct hid_api_version* HID_API_CALL hid_version(void);
/** @brief Get a runtime version string of the library.
This function is thread-safe.
@ingroup API
@returns
Pointer to statically allocated string, that contains version string.
*/
HID_API_EXPORT const char* HID_API_CALL hid_version_str(void);
#ifdef __cplusplus
}
#endif
#endif

1492
thirdparty/sdl/hidapi/linux/hid.c vendored Normal file

File diff suppressed because it is too large Load diff

1594
thirdparty/sdl/hidapi/mac/hid.c vendored Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,98 @@
/*******************************************************
HIDAPI - Multi-Platform library for
communication with HID devices.
libusb/hidapi Team
Copyright 2022, All Rights Reserved.
At the discretion of the user of this library,
this software may be licensed under the terms of the
GNU General Public License v3, a BSD-Style license, or the
original HIDAPI license as outlined in the LICENSE.txt,
LICENSE-gpl3.txt, LICENSE-bsd.txt, and LICENSE-orig.txt
files located at the root of the source distribution.
These files may also be found in the public source
code repository located at:
https://github.com/libusb/hidapi .
********************************************************/
/** @file
* @defgroup API hidapi API
* Since version 0.12.0, @ref HID_API_VERSION >= HID_API_MAKE_VERSION(0, 12, 0)
*/
#ifndef HIDAPI_DARWIN_H__
#define HIDAPI_DARWIN_H__
#include <stdint.h>
#include "../hidapi/hidapi.h"
#ifdef __cplusplus
extern "C" {
#endif
/** @brief Get the location ID for a HID device.
Since version 0.12.0, @ref HID_API_VERSION >= HID_API_MAKE_VERSION(0, 12, 0)
@ingroup API
@param dev A device handle returned from hid_open().
@param location_id The device's location ID on return.
@returns
This function returns 0 on success and -1 on error.
*/
int HID_API_EXPORT_CALL hid_darwin_get_location_id(hid_device *dev, uint32_t *location_id);
/** @brief Changes the behavior of all further calls to @ref hid_open or @ref hid_open_path.
By default on Darwin platform all devices opened by HIDAPI with @ref hid_open or @ref hid_open_path
are opened in exclusive mode (see kIOHIDOptionsTypeSeizeDevice).
Since version 0.12.0, @ref HID_API_VERSION >= HID_API_MAKE_VERSION(0, 12, 0)
@ingroup API
@param open_exclusive When set to 0 - all further devices will be opened
in non-exclusive mode. Otherwise - all further devices will be opened
in exclusive mode.
@note During the initialisation by @ref hid_init - this property is set to 1 (TRUE).
This is done to preserve full backward compatibility with previous behavior.
@note Calling this function before @ref hid_init or after @ref hid_exit has no effect.
*/
void HID_API_EXPORT_CALL hid_darwin_set_open_exclusive(int open_exclusive);
/** @brief Getter for option set by @ref hid_darwin_set_open_exclusive.
Since version 0.12.0, @ref HID_API_VERSION >= HID_API_MAKE_VERSION(0, 12, 0)
@ingroup API
@return 1 if all further devices will be opened in exclusive mode.
@note Value returned by this function before calling to @ref hid_init or after @ref hid_exit
is not reliable.
*/
int HID_API_EXPORT_CALL hid_darwin_get_open_exclusive(void);
/** @brief Check how the device was opened.
Since version 0.12.0, @ref HID_API_VERSION >= HID_API_MAKE_VERSION(0, 12, 0)
@ingroup API
@param dev A device to get property from.
@return 1 if the device is opened in exclusive mode, 0 - opened in non-exclusive,
-1 - if dev is invalid.
*/
int HID_API_EXPORT_CALL hid_darwin_is_device_open_exclusive(hid_device *dev);
#ifdef __cplusplus
}
#endif
#endif

1728
thirdparty/sdl/hidapi/windows/hid.c vendored Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,86 @@
/*******************************************************
HIDAPI - Multi-Platform library for
communication with HID devices.
libusb/hidapi Team
Copyright 2022, All Rights Reserved.
At the discretion of the user of this library,
this software may be licensed under the terms of the
GNU General Public License v3, a BSD-Style license, or the
original HIDAPI license as outlined in the LICENSE.txt,
LICENSE-gpl3.txt, LICENSE-bsd.txt, and LICENSE-orig.txt
files located at the root of the source distribution.
These files may also be found in the public source
code repository located at:
https://github.com/libusb/hidapi .
********************************************************/
#ifndef HIDAPI_CFGMGR32_H
#define HIDAPI_CFGMGR32_H
#ifdef HIDAPI_USE_DDK
#include <cfgmgr32.h>
#include <initguid.h>
#include <devpkey.h>
#include <propkey.h>
#else
/* This part of the header mimics cfgmgr32.h,
but only what is used by HIDAPI */
//#include <initguid.h>
#include <devpropdef.h>
//#include <propkeydef.h>
#ifndef PROPERTYKEY_DEFINED
#define PROPERTYKEY_DEFINED
typedef struct
{
GUID fmtid;
DWORD pid;
} PROPERTYKEY;
#endif /* PROPERTYKEY_DEFINED */
typedef DWORD RETURN_TYPE;
typedef RETURN_TYPE CONFIGRET;
typedef DWORD DEVNODE, DEVINST;
typedef DEVNODE* PDEVNODE, * PDEVINST;
typedef WCHAR* DEVNODEID_W, * DEVINSTID_W;
#define CR_SUCCESS (0x00000000)
#define CR_BUFFER_SMALL (0x0000001A)
#define CR_FAILURE (0x00000013)
#define CM_LOCATE_DEVNODE_NORMAL 0x00000000
#define CM_GET_DEVICE_INTERFACE_LIST_PRESENT (0x00000000)
typedef CONFIGRET(__stdcall* CM_Locate_DevNodeW_)(PDEVINST pdnDevInst, DEVINSTID_W pDeviceID, ULONG ulFlags);
typedef CONFIGRET(__stdcall* CM_Get_Parent_)(PDEVINST pdnDevInst, DEVINST dnDevInst, ULONG ulFlags);
typedef CONFIGRET(__stdcall* CM_Get_DevNode_PropertyW_)(DEVINST dnDevInst, CONST DEVPROPKEY* PropertyKey, DEVPROPTYPE* PropertyType, PBYTE PropertyBuffer, PULONG PropertyBufferSize, ULONG ulFlags);
typedef CONFIGRET(__stdcall* CM_Get_Device_Interface_PropertyW_)(LPCWSTR pszDeviceInterface, CONST DEVPROPKEY* PropertyKey, DEVPROPTYPE* PropertyType, PBYTE PropertyBuffer, PULONG PropertyBufferSize, ULONG ulFlags);
typedef CONFIGRET(__stdcall* CM_Get_Device_Interface_List_SizeW_)(PULONG pulLen, LPGUID InterfaceClassGuid, DEVINSTID_W pDeviceID, ULONG ulFlags);
typedef CONFIGRET(__stdcall* CM_Get_Device_Interface_ListW_)(LPGUID InterfaceClassGuid, DEVINSTID_W pDeviceID, WCHAR* /*PZZWSTR*/ Buffer, ULONG BufferLen, ULONG ulFlags);
// from devpkey.h
static DEVPROPKEY DEVPKEY_NAME = { { 0xb725f130, 0x47ef, 0x101a, {0xa5, 0xf1, 0x02, 0x60, 0x8c, 0x9e, 0xeb, 0xac} }, 10 }; // DEVPROP_TYPE_STRING
static DEVPROPKEY DEVPKEY_Device_Manufacturer = { { 0xa45c254e, 0xdf1c, 0x4efd, {0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0} }, 13 }; // DEVPROP_TYPE_STRING
static DEVPROPKEY DEVPKEY_Device_InstanceId = { { 0x78c34fc8, 0x104a, 0x4aca, {0x9e, 0xa4, 0x52, 0x4d, 0x52, 0x99, 0x6e, 0x57} }, 256 }; // DEVPROP_TYPE_STRING
static DEVPROPKEY DEVPKEY_Device_HardwareIds = { { 0xa45c254e, 0xdf1c, 0x4efd, {0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0} }, 3 }; // DEVPROP_TYPE_STRING_LIST
static DEVPROPKEY DEVPKEY_Device_CompatibleIds = { { 0xa45c254e, 0xdf1c, 0x4efd, {0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0} }, 4 }; // DEVPROP_TYPE_STRING_LIST
static DEVPROPKEY DEVPKEY_Device_ContainerId = { { 0x8c7ed206, 0x3f8a, 0x4827, {0xb3, 0xab, 0xae, 0x9e, 0x1f, 0xae, 0xfc, 0x6c} }, 2 }; // DEVPROP_TYPE_GUID
// from propkey.h
static PROPERTYKEY PKEY_DeviceInterface_Bluetooth_DeviceAddress = { { 0x2bd67d8b, 0x8beb, 0x48d5, {0x87, 0xe0, 0x6c, 0xda, 0x34, 0x28, 0x04, 0x0a} }, 1 }; // DEVPROP_TYPE_STRING
static PROPERTYKEY PKEY_DeviceInterface_Bluetooth_Manufacturer = { { 0x2bd67d8b, 0x8beb, 0x48d5, {0x87, 0xe0, 0x6c, 0xda, 0x34, 0x28, 0x04, 0x0a} }, 4 }; // DEVPROP_TYPE_STRING
static PROPERTYKEY PKEY_DeviceInterface_Bluetooth_ModelNumber = { { 0x2BD67D8B, 0x8BEB, 0x48D5, {0x87, 0xE0, 0x6C, 0xDA, 0x34, 0x28, 0x04, 0x0A} }, 5 }; // DEVPROP_TYPE_STRING
#endif
#endif /* HIDAPI_CFGMGR32_H */

View file

@ -0,0 +1,990 @@
/*******************************************************
HIDAPI - Multi-Platform library for
communication with HID devices.
libusb/hidapi Team
Copyright 2022, All Rights Reserved.
At the discretion of the user of this library,
this software may be licensed under the terms of the
GNU General Public License v3, a BSD-Style license, or the
original HIDAPI license as outlined in the LICENSE.txt,
LICENSE-gpl3.txt, LICENSE-bsd.txt, and LICENSE-orig.txt
files located at the root of the source distribution.
These files may also be found in the public source
code repository located at:
https://github.com/libusb/hidapi .
********************************************************/
#include "hidapi_descriptor_reconstruct.h"
/**
* @brief References to report descriptor buffer.
*
*/
struct rd_buffer {
unsigned char* buf; /* Pointer to the array which stores the reconstructed descriptor */
size_t buf_size; /* Size of the buffer in bytes */
size_t byte_idx; /* Index of the next report byte to write to buf array */
};
/**
* @brief Function that appends a byte to encoded report descriptor buffer.
*
* @param[in] byte Single byte to append.
* @param rpt_desc Pointer to report descriptor buffer struct.
*/
static void rd_append_byte(unsigned char byte, struct rd_buffer* rpt_desc) {
if (rpt_desc->byte_idx < rpt_desc->buf_size) {
rpt_desc->buf[rpt_desc->byte_idx] = byte;
rpt_desc->byte_idx++;
}
}
/**
* @brief Writes a short report descriptor item according USB HID spec 1.11 chapter 6.2.2.2.
*
* @param[in] rd_item Enumeration identifying type (Main, Global, Local) and function (e.g Usage or Report Count) of the item.
* @param[in] data Data (Size depends on rd_item 0,1,2 or 4bytes).
* @param rpt_desc Pointer to report descriptor buffer struct.
*
* @return Returns 0 if successful, -1 for error.
*/
static int rd_write_short_item(rd_items rd_item, LONG64 data, struct rd_buffer* rpt_desc) {
if (rd_item & 0x03) {
// Invalid input data, last to bits are reserved for data size
return -1;
}
if (rd_item == rd_main_collection_end) {
// Item without data (1Byte prefix only)
unsigned char oneBytePrefix = (unsigned char) rd_item + 0x00;
rd_append_byte(oneBytePrefix, rpt_desc);
}
else if ((rd_item == rd_global_logical_minimum) ||
(rd_item == rd_global_logical_maximum) ||
(rd_item == rd_global_physical_minimum) ||
(rd_item == rd_global_physical_maximum)) {
// Item with signed integer data
if ((data >= -128) && (data <= 127)) {
// 1Byte prefix + 1Byte data
unsigned char oneBytePrefix = (unsigned char) rd_item + 0x01;
char localData = (char)data;
rd_append_byte(oneBytePrefix, rpt_desc);
rd_append_byte(localData & 0xFF, rpt_desc);
}
else if ((data >= -32768) && (data <= 32767)) {
// 1Byte prefix + 2Byte data
unsigned char oneBytePrefix = (unsigned char) rd_item + 0x02;
INT16 localData = (INT16)data;
rd_append_byte(oneBytePrefix, rpt_desc);
rd_append_byte(localData & 0xFF, rpt_desc);
rd_append_byte(localData >> 8 & 0xFF, rpt_desc);
}
else if ((data >= -2147483648LL) && (data <= 2147483647)) {
// 1Byte prefix + 4Byte data
unsigned char oneBytePrefix = (unsigned char) rd_item + 0x03;
INT32 localData = (INT32)data;
rd_append_byte(oneBytePrefix, rpt_desc);
rd_append_byte(localData & 0xFF, rpt_desc);
rd_append_byte(localData >> 8 & 0xFF, rpt_desc);
rd_append_byte(localData >> 16 & 0xFF, rpt_desc);
rd_append_byte(localData >> 24 & 0xFF, rpt_desc);
}
else {
// Data out of 32 bit signed integer range
return -1;
}
}
else {
// Item with unsigned integer data
if ((data >= 0) && (data <= 0xFF)) {
// 1Byte prefix + 1Byte data
unsigned char oneBytePrefix = (unsigned char) rd_item + 0x01;
unsigned char localData = (unsigned char)data;
rd_append_byte(oneBytePrefix, rpt_desc);
rd_append_byte(localData & 0xFF, rpt_desc);
}
else if ((data >= 0) && (data <= 0xFFFF)) {
// 1Byte prefix + 2Byte data
unsigned char oneBytePrefix = (unsigned char) rd_item + 0x02;
UINT16 localData = (UINT16)data;
rd_append_byte(oneBytePrefix, rpt_desc);
rd_append_byte(localData & 0xFF, rpt_desc);
rd_append_byte(localData >> 8 & 0xFF, rpt_desc);
}
else if ((data >= 0) && (data <= 0xFFFFFFFF)) {
// 1Byte prefix + 4Byte data
unsigned char oneBytePrefix = (unsigned char) rd_item + 0x03;
UINT32 localData = (UINT32)data;
rd_append_byte(oneBytePrefix, rpt_desc);
rd_append_byte(localData & 0xFF, rpt_desc);
rd_append_byte(localData >> 8 & 0xFF, rpt_desc);
rd_append_byte(localData >> 16 & 0xFF, rpt_desc);
rd_append_byte(localData >> 24 & 0xFF, rpt_desc);
}
else {
// Data out of 32 bit unsigned integer range
return -1;
}
}
return 0;
}
static struct rd_main_item_node * rd_append_main_item_node(int first_bit, int last_bit, rd_node_type type_of_node, int caps_index, int collection_index, rd_main_items main_item_type, unsigned char report_id, struct rd_main_item_node **list) {
struct rd_main_item_node *new_list_node;
// Determine last node in the list
while (*list != NULL)
{
list = &(*list)->next;
}
new_list_node = malloc(sizeof(*new_list_node)); // Create new list entry
new_list_node->FirstBit = first_bit;
new_list_node->LastBit = last_bit;
new_list_node->TypeOfNode = type_of_node;
new_list_node->CapsIndex = caps_index;
new_list_node->CollectionIndex = collection_index;
new_list_node->MainItemType = main_item_type;
new_list_node->ReportID = report_id;
new_list_node->next = NULL; // NULL marks last node in the list
*list = new_list_node;
return new_list_node;
}
static struct rd_main_item_node * rd_insert_main_item_node(int first_bit, int last_bit, rd_node_type type_of_node, int caps_index, int collection_index, rd_main_items main_item_type, unsigned char report_id, struct rd_main_item_node **list) {
// Insert item after the main item node referenced by list
struct rd_main_item_node *next_item = (*list)->next;
(*list)->next = NULL;
rd_append_main_item_node(first_bit, last_bit, type_of_node, caps_index, collection_index, main_item_type, report_id, list);
(*list)->next->next = next_item;
return (*list)->next;
}
static struct rd_main_item_node * rd_search_main_item_list_for_bit_position(int search_bit, rd_main_items main_item_type, unsigned char report_id, struct rd_main_item_node **list) {
// Determine first INPUT/OUTPUT/FEATURE main item, where the last bit position is equal or greater than the search bit position
while (((*list)->next->MainItemType != rd_collection) &&
((*list)->next->MainItemType != rd_collection_end) &&
!(((*list)->next->LastBit >= search_bit) &&
((*list)->next->ReportID == report_id) &&
((*list)->next->MainItemType == main_item_type))
)
{
list = &(*list)->next;
}
return *list;
}
int hid_winapi_descriptor_reconstruct_pp_data(void *preparsed_data, unsigned char *buf, size_t buf_size)
{
hidp_preparsed_data *pp_data = (hidp_preparsed_data *) preparsed_data;
// Check if MagicKey is correct, to ensure that pp_data points to an valid preparse data structure
if (memcmp(pp_data->MagicKey, "HidP KDR", 8) != 0) {
return -1;
}
struct rd_buffer rpt_desc;
rpt_desc.buf = buf;
rpt_desc.buf_size = buf_size;
rpt_desc.byte_idx = 0;
// Set pointer to the first node of link_collection_nodes
phid_pp_link_collection_node link_collection_nodes = (phid_pp_link_collection_node)(((unsigned char*)&pp_data->caps[0]) + pp_data->FirstByteOfLinkCollectionArray);
// ****************************************************************************************************************************
// Create lookup tables for the bit range of each report per collection (position of first bit and last bit in each collection)
// coll_bit_range[COLLECTION_INDEX][REPORT_ID][INPUT/OUTPUT/FEATURE]
// ****************************************************************************************************************************
// Allocate memory and initialize lookup table
rd_bit_range ****coll_bit_range;
coll_bit_range = malloc(pp_data->NumberLinkCollectionNodes * sizeof(*coll_bit_range));
for (USHORT collection_node_idx = 0; collection_node_idx < pp_data->NumberLinkCollectionNodes; collection_node_idx++) {
coll_bit_range[collection_node_idx] = malloc(256 * sizeof(*coll_bit_range[0])); // 256 possible report IDs (incl. 0x00)
for (int reportid_idx = 0; reportid_idx < 256; reportid_idx++) {
coll_bit_range[collection_node_idx][reportid_idx] = malloc(NUM_OF_HIDP_REPORT_TYPES * sizeof(*coll_bit_range[0][0]));
for (HIDP_REPORT_TYPE rt_idx = 0; rt_idx < NUM_OF_HIDP_REPORT_TYPES; rt_idx++) {
coll_bit_range[collection_node_idx][reportid_idx][rt_idx] = malloc(sizeof(rd_bit_range));
coll_bit_range[collection_node_idx][reportid_idx][rt_idx]->FirstBit = -1;
coll_bit_range[collection_node_idx][reportid_idx][rt_idx]->LastBit = -1;
}
}
}
// Fill the lookup table where caps exist
for (HIDP_REPORT_TYPE rt_idx = 0; rt_idx < NUM_OF_HIDP_REPORT_TYPES; rt_idx++) {
for (USHORT caps_idx = pp_data->caps_info[rt_idx].FirstCap; caps_idx < pp_data->caps_info[rt_idx].LastCap; caps_idx++) {
int first_bit, last_bit;
first_bit = (pp_data->caps[caps_idx].BytePosition - 1) * 8
+ pp_data->caps[caps_idx].BitPosition;
last_bit = first_bit + pp_data->caps[caps_idx].ReportSize
* pp_data->caps[caps_idx].ReportCount - 1;
if (coll_bit_range[pp_data->caps[caps_idx].LinkCollection][pp_data->caps[caps_idx].ReportID][rt_idx]->FirstBit == -1 ||
coll_bit_range[pp_data->caps[caps_idx].LinkCollection][pp_data->caps[caps_idx].ReportID][rt_idx]->FirstBit > first_bit) {
coll_bit_range[pp_data->caps[caps_idx].LinkCollection][pp_data->caps[caps_idx].ReportID][rt_idx]->FirstBit = first_bit;
}
if (coll_bit_range[pp_data->caps[caps_idx].LinkCollection][pp_data->caps[caps_idx].ReportID][rt_idx]->LastBit < last_bit) {
coll_bit_range[pp_data->caps[caps_idx].LinkCollection][pp_data->caps[caps_idx].ReportID][rt_idx]->LastBit = last_bit;
}
}
}
// *************************************************************************
// -Determine hierarchy levels of each collections and store it in:
// coll_levels[COLLECTION_INDEX]
// -Determine number of direct childs of each collections and store it in:
// coll_number_of_direct_childs[COLLECTION_INDEX]
// *************************************************************************
int max_coll_level = 0;
int *coll_levels = malloc(pp_data->NumberLinkCollectionNodes * sizeof(coll_levels[0]));
int *coll_number_of_direct_childs = malloc(pp_data->NumberLinkCollectionNodes * sizeof(coll_number_of_direct_childs[0]));
for (USHORT collection_node_idx = 0; collection_node_idx < pp_data->NumberLinkCollectionNodes; collection_node_idx++) {
coll_levels[collection_node_idx] = -1;
coll_number_of_direct_childs[collection_node_idx] = 0;
}
{
int actual_coll_level = 0;
USHORT collection_node_idx = 0;
while (actual_coll_level >= 0) {
coll_levels[collection_node_idx] = actual_coll_level;
if ((link_collection_nodes[collection_node_idx].NumberOfChildren > 0) &&
(coll_levels[link_collection_nodes[collection_node_idx].FirstChild] == -1)) {
actual_coll_level++;
coll_levels[collection_node_idx] = actual_coll_level;
if (max_coll_level < actual_coll_level) {
max_coll_level = actual_coll_level;
}
coll_number_of_direct_childs[collection_node_idx]++;
collection_node_idx = link_collection_nodes[collection_node_idx].FirstChild;
}
else if (link_collection_nodes[collection_node_idx].NextSibling != 0) {
coll_number_of_direct_childs[link_collection_nodes[collection_node_idx].Parent]++;
collection_node_idx = link_collection_nodes[collection_node_idx].NextSibling;
}
else {
actual_coll_level--;
if (actual_coll_level >= 0) {
collection_node_idx = link_collection_nodes[collection_node_idx].Parent;
}
}
}
}
// *********************************************************************************
// Propagate the bit range of each report from the child collections to their parent
// and store the merged result for the parent
// *********************************************************************************
for (int actual_coll_level = max_coll_level - 1; actual_coll_level >= 0; actual_coll_level--) {
for (USHORT collection_node_idx = 0; collection_node_idx < pp_data->NumberLinkCollectionNodes; collection_node_idx++) {
if (coll_levels[collection_node_idx] == actual_coll_level) {
USHORT child_idx = link_collection_nodes[collection_node_idx].FirstChild;
while (child_idx) {
for (int reportid_idx = 0; reportid_idx < 256; reportid_idx++) {
for (HIDP_REPORT_TYPE rt_idx = 0; rt_idx < NUM_OF_HIDP_REPORT_TYPES; rt_idx++) {
// Merge bit range from childs
if ((coll_bit_range[child_idx][reportid_idx][rt_idx]->FirstBit != -1) &&
(coll_bit_range[collection_node_idx][reportid_idx][rt_idx]->FirstBit > coll_bit_range[child_idx][reportid_idx][rt_idx]->FirstBit)) {
coll_bit_range[collection_node_idx][reportid_idx][rt_idx]->FirstBit = coll_bit_range[child_idx][reportid_idx][rt_idx]->FirstBit;
}
if (coll_bit_range[collection_node_idx][reportid_idx][rt_idx]->LastBit < coll_bit_range[child_idx][reportid_idx][rt_idx]->LastBit) {
coll_bit_range[collection_node_idx][reportid_idx][rt_idx]->LastBit = coll_bit_range[child_idx][reportid_idx][rt_idx]->LastBit;
}
child_idx = link_collection_nodes[child_idx].NextSibling;
}
}
}
}
}
}
// **************************************************************************************************
// Determine child collection order of the whole hierarchy, based on previously determined bit ranges
// and store it this index coll_child_order[COLLECTION_INDEX][DIRECT_CHILD_INDEX]
// **************************************************************************************************
USHORT **coll_child_order;
coll_child_order = malloc(pp_data->NumberLinkCollectionNodes * sizeof(*coll_child_order));
{
BOOLEAN *coll_parsed_flag;
coll_parsed_flag = malloc(pp_data->NumberLinkCollectionNodes * sizeof(coll_parsed_flag[0]));
for (USHORT collection_node_idx = 0; collection_node_idx < pp_data->NumberLinkCollectionNodes; collection_node_idx++) {
coll_parsed_flag[collection_node_idx] = FALSE;
}
int actual_coll_level = 0;
USHORT collection_node_idx = 0;
while (actual_coll_level >= 0) {
if ((coll_number_of_direct_childs[collection_node_idx] != 0) &&
(coll_parsed_flag[link_collection_nodes[collection_node_idx].FirstChild] == FALSE)) {
coll_parsed_flag[link_collection_nodes[collection_node_idx].FirstChild] = TRUE;
coll_child_order[collection_node_idx] = malloc((coll_number_of_direct_childs[collection_node_idx]) * sizeof(*coll_child_order[0]));
{
// Create list of child collection indices
// sorted reverse to the order returned to HidP_GetLinkCollectionNodeschild
// which seems to match the original order, as long as no bit position needs to be considered
USHORT child_idx = link_collection_nodes[collection_node_idx].FirstChild;
int child_count = coll_number_of_direct_childs[collection_node_idx] - 1;
coll_child_order[collection_node_idx][child_count] = child_idx;
while (link_collection_nodes[child_idx].NextSibling) {
child_count--;
child_idx = link_collection_nodes[child_idx].NextSibling;
coll_child_order[collection_node_idx][child_count] = child_idx;
}
}
if (coll_number_of_direct_childs[collection_node_idx] > 1) {
// Sort child collections indices by bit positions
for (HIDP_REPORT_TYPE rt_idx = 0; rt_idx < NUM_OF_HIDP_REPORT_TYPES; rt_idx++) {
for (int reportid_idx = 0; reportid_idx < 256; reportid_idx++) {
for (int child_idx = 1; child_idx < coll_number_of_direct_childs[collection_node_idx]; child_idx++) {
// since the coll_bit_range array is not sorted, we need to reference the collection index in
// our sorted coll_child_order array, and look up the corresponding bit ranges for comparing values to sort
int prev_coll_idx = coll_child_order[collection_node_idx][child_idx - 1];
int cur_coll_idx = coll_child_order[collection_node_idx][child_idx];
if ((coll_bit_range[prev_coll_idx][reportid_idx][rt_idx]->FirstBit != -1) &&
(coll_bit_range[cur_coll_idx][reportid_idx][rt_idx]->FirstBit != -1) &&
(coll_bit_range[prev_coll_idx][reportid_idx][rt_idx]->FirstBit > coll_bit_range[cur_coll_idx][reportid_idx][rt_idx]->FirstBit)) {
// Swap position indices of the two compared child collections
USHORT idx_latch = coll_child_order[collection_node_idx][child_idx - 1];
coll_child_order[collection_node_idx][child_idx - 1] = coll_child_order[collection_node_idx][child_idx];
coll_child_order[collection_node_idx][child_idx] = idx_latch;
}
}
}
}
}
actual_coll_level++;
collection_node_idx = link_collection_nodes[collection_node_idx].FirstChild;
}
else if (link_collection_nodes[collection_node_idx].NextSibling != 0) {
collection_node_idx = link_collection_nodes[collection_node_idx].NextSibling;
}
else {
actual_coll_level--;
if (actual_coll_level >= 0) {
collection_node_idx = link_collection_nodes[collection_node_idx].Parent;
}
}
}
free(coll_parsed_flag);
}
// ***************************************************************************************
// Create sorted main_item_list containing all the Collection and CollectionEnd main items
// ***************************************************************************************
struct rd_main_item_node *main_item_list = NULL; // List root
// Lookup table to find the Collection items in the list by index
struct rd_main_item_node **coll_begin_lookup = malloc(pp_data->NumberLinkCollectionNodes * sizeof(*coll_begin_lookup));
struct rd_main_item_node **coll_end_lookup = malloc(pp_data->NumberLinkCollectionNodes * sizeof(*coll_end_lookup));
{
int *coll_last_written_child = malloc(pp_data->NumberLinkCollectionNodes * sizeof(coll_last_written_child[0]));
for (USHORT collection_node_idx = 0; collection_node_idx < pp_data->NumberLinkCollectionNodes; collection_node_idx++) {
coll_last_written_child[collection_node_idx] = -1;
}
int actual_coll_level = 0;
USHORT collection_node_idx = 0;
struct rd_main_item_node *firstDelimiterNode = NULL;
struct rd_main_item_node *delimiterCloseNode = NULL;
coll_begin_lookup[0] = rd_append_main_item_node(0, 0, rd_item_node_collection, 0, collection_node_idx, rd_collection, 0, &main_item_list);
while (actual_coll_level >= 0) {
if ((coll_number_of_direct_childs[collection_node_idx] != 0) &&
(coll_last_written_child[collection_node_idx] == -1)) {
// Collection has child collections, but none is written to the list yet
coll_last_written_child[collection_node_idx] = coll_child_order[collection_node_idx][0];
collection_node_idx = coll_child_order[collection_node_idx][0];
// In a HID Report Descriptor, the first usage declared is the most preferred usage for the control.
// While the order in the WIN32 capabiliy strutures is the opposite:
// Here the preferred usage is the last aliased usage in the sequence.
if (link_collection_nodes[collection_node_idx].IsAlias && (firstDelimiterNode == NULL)) {
// Alliased Collection (First node in link_collection_nodes -> Last entry in report descriptor output)
firstDelimiterNode = main_item_list;
coll_begin_lookup[collection_node_idx] = rd_append_main_item_node(0, 0, rd_item_node_collection, 0, collection_node_idx, rd_delimiter_usage, 0, &main_item_list);
coll_begin_lookup[collection_node_idx] = rd_append_main_item_node(0, 0, rd_item_node_collection, 0, collection_node_idx, rd_delimiter_close, 0, &main_item_list);
delimiterCloseNode = main_item_list;
}
else {
// Normal not aliased collection
coll_begin_lookup[collection_node_idx] = rd_append_main_item_node(0, 0, rd_item_node_collection, 0, collection_node_idx, rd_collection, 0, &main_item_list);
actual_coll_level++;
}
}
else if ((coll_number_of_direct_childs[collection_node_idx] > 1) &&
(coll_last_written_child[collection_node_idx] != coll_child_order[collection_node_idx][coll_number_of_direct_childs[collection_node_idx] - 1])) {
// Collection has child collections, and this is not the first child
int nextChild = 1;
while (coll_last_written_child[collection_node_idx] != coll_child_order[collection_node_idx][nextChild - 1]) {
nextChild++;
}
coll_last_written_child[collection_node_idx] = coll_child_order[collection_node_idx][nextChild];
collection_node_idx = coll_child_order[collection_node_idx][nextChild];
if (link_collection_nodes[collection_node_idx].IsAlias && (firstDelimiterNode == NULL)) {
// Alliased Collection (First node in link_collection_nodes -> Last entry in report descriptor output)
firstDelimiterNode = main_item_list;
coll_begin_lookup[collection_node_idx] = rd_append_main_item_node(0, 0, rd_item_node_collection, 0, collection_node_idx, rd_delimiter_usage, 0, &main_item_list);
coll_begin_lookup[collection_node_idx] = rd_append_main_item_node(0, 0, rd_item_node_collection, 0, collection_node_idx, rd_delimiter_close, 0, &main_item_list);
delimiterCloseNode = main_item_list;
}
else if (link_collection_nodes[collection_node_idx].IsAlias && (firstDelimiterNode != NULL)) {
coll_begin_lookup[collection_node_idx] = rd_insert_main_item_node(0, 0, rd_item_node_collection, 0, collection_node_idx, rd_delimiter_usage, 0, &firstDelimiterNode);
}
else if (!link_collection_nodes[collection_node_idx].IsAlias && (firstDelimiterNode != NULL)) {
coll_begin_lookup[collection_node_idx] = rd_insert_main_item_node(0, 0, rd_item_node_collection, 0, collection_node_idx, rd_delimiter_usage, 0, &firstDelimiterNode);
coll_begin_lookup[collection_node_idx] = rd_insert_main_item_node(0, 0, rd_item_node_collection, 0, collection_node_idx, rd_delimiter_open, 0, &firstDelimiterNode);
firstDelimiterNode = NULL;
main_item_list = delimiterCloseNode;
delimiterCloseNode = NULL; // Last entry of alias has .IsAlias == FALSE
}
if (!link_collection_nodes[collection_node_idx].IsAlias) {
coll_begin_lookup[collection_node_idx] = rd_append_main_item_node(0, 0, rd_item_node_collection, 0, collection_node_idx, rd_collection, 0, &main_item_list);
actual_coll_level++;
}
}
else {
actual_coll_level--;
coll_end_lookup[collection_node_idx] = rd_append_main_item_node(0, 0, rd_item_node_collection, 0, collection_node_idx, rd_collection_end, 0, &main_item_list);
collection_node_idx = link_collection_nodes[collection_node_idx].Parent;
}
}
free(coll_last_written_child);
}
// ****************************************************************
// Inserted Input/Output/Feature main items into the main_item_list
// in order of reconstructed bit positions
// ****************************************************************
for (HIDP_REPORT_TYPE rt_idx = 0; rt_idx < NUM_OF_HIDP_REPORT_TYPES; rt_idx++) {
// Add all value caps to node list
struct rd_main_item_node *firstDelimiterNode = NULL;
struct rd_main_item_node *delimiterCloseNode = NULL;
for (USHORT caps_idx = pp_data->caps_info[rt_idx].FirstCap; caps_idx < pp_data->caps_info[rt_idx].LastCap; caps_idx++) {
struct rd_main_item_node *coll_begin = coll_begin_lookup[pp_data->caps[caps_idx].LinkCollection];
int first_bit, last_bit;
first_bit = (pp_data->caps[caps_idx].BytePosition - 1) * 8 +
pp_data->caps[caps_idx].BitPosition;
last_bit = first_bit + pp_data->caps[caps_idx].ReportSize *
pp_data->caps[caps_idx].ReportCount - 1;
for (int child_idx = 0; child_idx < coll_number_of_direct_childs[pp_data->caps[caps_idx].LinkCollection]; child_idx++) {
// Determine in which section before/between/after child collection the item should be inserted
if (first_bit < coll_bit_range[coll_child_order[pp_data->caps[caps_idx].LinkCollection][child_idx]][pp_data->caps[caps_idx].ReportID][rt_idx]->FirstBit)
{
// Note, that the default value for undefined coll_bit_range is -1, which can't be greater than the bit position
break;
}
coll_begin = coll_end_lookup[coll_child_order[pp_data->caps[caps_idx].LinkCollection][child_idx]];
}
struct rd_main_item_node *list_node;
list_node = rd_search_main_item_list_for_bit_position(first_bit, (rd_main_items) rt_idx, pp_data->caps[caps_idx].ReportID, &coll_begin);
// In a HID Report Descriptor, the first usage declared is the most preferred usage for the control.
// While the order in the WIN32 capabiliy strutures is the opposite:
// Here the preferred usage is the last aliased usage in the sequence.
if (pp_data->caps[caps_idx].IsAlias && (firstDelimiterNode == NULL)) {
// Alliased Usage (First node in pp_data->caps -> Last entry in report descriptor output)
firstDelimiterNode = list_node;
rd_insert_main_item_node(first_bit, last_bit, rd_item_node_cap, caps_idx, pp_data->caps[caps_idx].LinkCollection, rd_delimiter_usage, pp_data->caps[caps_idx].ReportID, &list_node);
rd_insert_main_item_node(first_bit, last_bit, rd_item_node_cap, caps_idx, pp_data->caps[caps_idx].LinkCollection, rd_delimiter_close, pp_data->caps[caps_idx].ReportID, &list_node);
delimiterCloseNode = list_node;
} else if (pp_data->caps[caps_idx].IsAlias && (firstDelimiterNode != NULL)) {
rd_insert_main_item_node(first_bit, last_bit, rd_item_node_cap, caps_idx, pp_data->caps[caps_idx].LinkCollection, rd_delimiter_usage, pp_data->caps[caps_idx].ReportID, &list_node);
}
else if (!pp_data->caps[caps_idx].IsAlias && (firstDelimiterNode != NULL)) {
// Alliased Collection (Last node in pp_data->caps -> First entry in report descriptor output)
rd_insert_main_item_node(first_bit, last_bit, rd_item_node_cap, caps_idx, pp_data->caps[caps_idx].LinkCollection, rd_delimiter_usage, pp_data->caps[caps_idx].ReportID, &list_node);
rd_insert_main_item_node(first_bit, last_bit, rd_item_node_cap, caps_idx, pp_data->caps[caps_idx].LinkCollection, rd_delimiter_open, pp_data->caps[caps_idx].ReportID, &list_node);
firstDelimiterNode = NULL;
list_node = delimiterCloseNode;
delimiterCloseNode = NULL; // Last entry of alias has .IsAlias == FALSE
}
if (!pp_data->caps[caps_idx].IsAlias) {
rd_insert_main_item_node(first_bit, last_bit, rd_item_node_cap, caps_idx, pp_data->caps[caps_idx].LinkCollection, (rd_main_items) rt_idx, pp_data->caps[caps_idx].ReportID, &list_node);
}
}
}
// ***********************************************************
// Add const main items for padding to main_item_list
// -To fill all bit gaps
// -At each report end for 8bit padding
// Note that information about the padding at the report end,
// is not stored in the preparsed data, but in practice all
// report descriptors seem to have it, as assumed here.
// ***********************************************************
{
int *last_bit_position[NUM_OF_HIDP_REPORT_TYPES];
struct rd_main_item_node **last_report_item_lookup[NUM_OF_HIDP_REPORT_TYPES];
for (HIDP_REPORT_TYPE rt_idx = 0; rt_idx < NUM_OF_HIDP_REPORT_TYPES; rt_idx++) {
last_bit_position[rt_idx] = malloc(256 * sizeof(*last_bit_position[rt_idx]));
last_report_item_lookup[rt_idx] = malloc(256 * sizeof(*last_report_item_lookup[rt_idx]));
for (int reportid_idx = 0; reportid_idx < 256; reportid_idx++) {
last_bit_position[rt_idx][reportid_idx] = -1;
last_report_item_lookup[rt_idx][reportid_idx] = NULL;
}
}
struct rd_main_item_node *list = main_item_list; // List root;
while (list->next != NULL)
{
if ((list->MainItemType >= rd_input) &&
(list->MainItemType <= rd_feature)) {
// INPUT, OUTPUT or FEATURE
if (list->FirstBit != -1) {
if ((last_bit_position[list->MainItemType][list->ReportID] + 1 != list->FirstBit) &&
(last_report_item_lookup[list->MainItemType][list->ReportID] != NULL) &&
(last_report_item_lookup[list->MainItemType][list->ReportID]->FirstBit != list->FirstBit) // Happens in case of IsMultipleItemsForArray for multiple dedicated usages for a multi-button array
) {
struct rd_main_item_node *list_node = rd_search_main_item_list_for_bit_position(last_bit_position[list->MainItemType][list->ReportID], list->MainItemType, list->ReportID, &last_report_item_lookup[list->MainItemType][list->ReportID]);
rd_insert_main_item_node(last_bit_position[list->MainItemType][list->ReportID] + 1, list->FirstBit - 1, rd_item_node_padding, -1, 0, list->MainItemType, list->ReportID, &list_node);
}
last_bit_position[list->MainItemType][list->ReportID] = list->LastBit;
last_report_item_lookup[list->MainItemType][list->ReportID] = list;
}
}
list = list->next;
}
// Add 8 bit padding at each report end
for (HIDP_REPORT_TYPE rt_idx = 0; rt_idx < NUM_OF_HIDP_REPORT_TYPES; rt_idx++) {
for (int reportid_idx = 0; reportid_idx < 256; reportid_idx++) {
if (last_bit_position[rt_idx][reportid_idx] != -1) {
int padding = 8 - ((last_bit_position[rt_idx][reportid_idx] + 1) % 8);
if (padding < 8) {
// Insert padding item after item referenced in last_report_item_lookup
rd_insert_main_item_node(last_bit_position[rt_idx][reportid_idx] + 1, last_bit_position[rt_idx][reportid_idx] + padding, rd_item_node_padding, -1, 0, (rd_main_items) rt_idx, (unsigned char) reportid_idx, &last_report_item_lookup[rt_idx][reportid_idx]);
}
}
}
free(last_bit_position[rt_idx]);
free(last_report_item_lookup[rt_idx]);
}
}
// ***********************************
// Encode the report descriptor output
// ***********************************
UCHAR last_report_id = 0;
USAGE last_usage_page = 0;
LONG last_physical_min = 0;// If both, Physical Minimum and Physical Maximum are 0, the logical limits should be taken as physical limits according USB HID spec 1.11 chapter 6.2.2.7
LONG last_physical_max = 0;
ULONG last_unit_exponent = 0; // If Unit Exponent is Undefined it should be considered as 0 according USB HID spec 1.11 chapter 6.2.2.7
ULONG last_unit = 0; // If the first nibble is 7, or second nibble of Unit is 0, the unit is None according USB HID spec 1.11 chapter 6.2.2.7
BOOLEAN inhibit_write_of_usage = FALSE; // Needed in case of delimited usage print, before the normal collection or cap
int report_count = 0;
while (main_item_list != NULL)
{
int rt_idx = main_item_list->MainItemType;
int caps_idx = main_item_list->CapsIndex;
if (main_item_list->MainItemType == rd_collection) {
if (last_usage_page != link_collection_nodes[main_item_list->CollectionIndex].LinkUsagePage) {
// Write "Usage Page" at the begin of a collection - except it refers the same table as wrote last
rd_write_short_item(rd_global_usage_page, link_collection_nodes[main_item_list->CollectionIndex].LinkUsagePage, &rpt_desc);
last_usage_page = link_collection_nodes[main_item_list->CollectionIndex].LinkUsagePage;
}
if (inhibit_write_of_usage) {
// Inhibit only once after DELIMITER statement
inhibit_write_of_usage = FALSE;
}
else {
// Write "Usage" of collection
rd_write_short_item(rd_local_usage, link_collection_nodes[main_item_list->CollectionIndex].LinkUsage, &rpt_desc);
}
// Write begin of "Collection"
rd_write_short_item(rd_main_collection, link_collection_nodes[main_item_list->CollectionIndex].CollectionType, &rpt_desc);
}
else if (main_item_list->MainItemType == rd_collection_end) {
// Write "End Collection"
rd_write_short_item(rd_main_collection_end, 0, &rpt_desc);
}
else if (main_item_list->MainItemType == rd_delimiter_open) {
if (main_item_list->CollectionIndex != -1) {
// Write "Usage Page" inside of a collection delmiter section
if (last_usage_page != link_collection_nodes[main_item_list->CollectionIndex].LinkUsagePage) {
rd_write_short_item(rd_global_usage_page, link_collection_nodes[main_item_list->CollectionIndex].LinkUsagePage, &rpt_desc);
last_usage_page = link_collection_nodes[main_item_list->CollectionIndex].LinkUsagePage;
}
}
else if (main_item_list->CapsIndex != 0) {
// Write "Usage Page" inside of a main item delmiter section
if (pp_data->caps[caps_idx].UsagePage != last_usage_page) {
rd_write_short_item(rd_global_usage_page, pp_data->caps[caps_idx].UsagePage, &rpt_desc);
last_usage_page = pp_data->caps[caps_idx].UsagePage;
}
}
// Write "Delimiter Open"
rd_write_short_item(rd_local_delimiter, 1, &rpt_desc); // 1 = open set of aliased usages
}
else if (main_item_list->MainItemType == rd_delimiter_usage) {
if (main_item_list->CollectionIndex != -1) {
// Write aliased collection "Usage"
rd_write_short_item(rd_local_usage, link_collection_nodes[main_item_list->CollectionIndex].LinkUsage, &rpt_desc);
} if (main_item_list->CapsIndex != 0) {
// Write aliased main item range from "Usage Minimum" to "Usage Maximum"
if (pp_data->caps[caps_idx].IsRange) {
rd_write_short_item(rd_local_usage_minimum, pp_data->caps[caps_idx].Range.UsageMin, &rpt_desc);
rd_write_short_item(rd_local_usage_maximum, pp_data->caps[caps_idx].Range.UsageMax, &rpt_desc);
}
else {
// Write single aliased main item "Usage"
rd_write_short_item(rd_local_usage, pp_data->caps[caps_idx].NotRange.Usage, &rpt_desc);
}
}
}
else if (main_item_list->MainItemType == rd_delimiter_close) {
// Write "Delimiter Close"
rd_write_short_item(rd_local_delimiter, 0, &rpt_desc); // 0 = close set of aliased usages
// Inhibit next usage write
inhibit_write_of_usage = TRUE;
}
else if (main_item_list->TypeOfNode == rd_item_node_padding) {
// Padding
// The preparsed data doesn't contain any information about padding. Therefore all undefined gaps
// in the reports are filled with the same style of constant padding.
// Write "Report Size" with number of padding bits
rd_write_short_item(rd_global_report_size, (main_item_list->LastBit - main_item_list->FirstBit + 1), &rpt_desc);
// Write "Report Count" for padding always as 1
rd_write_short_item(rd_global_report_count, 1, &rpt_desc);
if (rt_idx == HidP_Input) {
// Write "Input" main item - We know it's Constant - We can only guess the other bits, but they don't matter in case of const
rd_write_short_item(rd_main_input, 0x03, &rpt_desc); // Const / Abs
}
else if (rt_idx == HidP_Output) {
// Write "Output" main item - We know it's Constant - We can only guess the other bits, but they don't matter in case of const
rd_write_short_item(rd_main_output, 0x03, &rpt_desc); // Const / Abs
}
else if (rt_idx == HidP_Feature) {
// Write "Feature" main item - We know it's Constant - We can only guess the other bits, but they don't matter in case of const
rd_write_short_item(rd_main_feature, 0x03, &rpt_desc); // Const / Abs
}
report_count = 0;
}
else if (pp_data->caps[caps_idx].IsButtonCap) {
// Button
// (The preparsed data contain different data for 1 bit Button caps, than for parametric Value caps)
if (last_report_id != pp_data->caps[caps_idx].ReportID) {
// Write "Report ID" if changed
rd_write_short_item(rd_global_report_id, pp_data->caps[caps_idx].ReportID, &rpt_desc);
last_report_id = pp_data->caps[caps_idx].ReportID;
}
// Write "Usage Page" when changed
if (pp_data->caps[caps_idx].UsagePage != last_usage_page) {
rd_write_short_item(rd_global_usage_page, pp_data->caps[caps_idx].UsagePage, &rpt_desc);
last_usage_page = pp_data->caps[caps_idx].UsagePage;
}
// Write only local report items for each cap, if ReportCount > 1
if (pp_data->caps[caps_idx].IsRange) {
report_count += (pp_data->caps[caps_idx].Range.DataIndexMax - pp_data->caps[caps_idx].Range.DataIndexMin);
}
if (inhibit_write_of_usage) {
// Inhibit only once after Delimiter - Reset flag
inhibit_write_of_usage = FALSE;
}
else {
if (pp_data->caps[caps_idx].IsRange) {
// Write range from "Usage Minimum" to "Usage Maximum"
rd_write_short_item(rd_local_usage_minimum, pp_data->caps[caps_idx].Range.UsageMin, &rpt_desc);
rd_write_short_item(rd_local_usage_maximum, pp_data->caps[caps_idx].Range.UsageMax, &rpt_desc);
}
else {
// Write single "Usage"
rd_write_short_item(rd_local_usage, pp_data->caps[caps_idx].NotRange.Usage, &rpt_desc);
}
}
if (pp_data->caps[caps_idx].IsDesignatorRange) {
// Write physical descriptor indices range from "Designator Minimum" to "Designator Maximum"
rd_write_short_item(rd_local_designator_minimum, pp_data->caps[caps_idx].Range.DesignatorMin, &rpt_desc);
rd_write_short_item(rd_local_designator_maximum, pp_data->caps[caps_idx].Range.DesignatorMax, &rpt_desc);
}
else if (pp_data->caps[caps_idx].NotRange.DesignatorIndex != 0) {
// Designator set 0 is a special descriptor set (of the HID Physical Descriptor),
// that specifies the number of additional descriptor sets.
// Therefore Designator Index 0 can never be a useful reference for a control and we can inhibit it.
// Write single "Designator Index"
rd_write_short_item(rd_local_designator_index, pp_data->caps[caps_idx].NotRange.DesignatorIndex, &rpt_desc);
}
if (pp_data->caps[caps_idx].IsStringRange) {
// Write range of indices of the USB string descriptor, from "String Minimum" to "String Maximum"
rd_write_short_item(rd_local_string_minimum, pp_data->caps[caps_idx].Range.StringMin, &rpt_desc);
rd_write_short_item(rd_local_string_maximum, pp_data->caps[caps_idx].Range.StringMax, &rpt_desc);
}
else if (pp_data->caps[caps_idx].NotRange.StringIndex != 0) {
// String Index 0 is a special entry of the USB string descriptor, that contains a list of supported languages,
// therefore Designator Index 0 can never be a useful reference for a control and we can inhibit it.
// Write single "String Index"
rd_write_short_item(rd_local_string, pp_data->caps[caps_idx].NotRange.StringIndex, &rpt_desc);
}
if ((main_item_list->next != NULL) &&
((int)main_item_list->next->MainItemType == rt_idx) &&
(main_item_list->next->TypeOfNode == rd_item_node_cap) &&
(pp_data->caps[main_item_list->next->CapsIndex].IsButtonCap) &&
(!pp_data->caps[caps_idx].IsRange) && // This node in list is no array
(!pp_data->caps[main_item_list->next->CapsIndex].IsRange) && // Next node in list is no array
(pp_data->caps[main_item_list->next->CapsIndex].UsagePage == pp_data->caps[caps_idx].UsagePage) &&
(pp_data->caps[main_item_list->next->CapsIndex].ReportID == pp_data->caps[caps_idx].ReportID) &&
(pp_data->caps[main_item_list->next->CapsIndex].BitField == pp_data->caps[caps_idx].BitField)
) {
if (main_item_list->next->FirstBit != main_item_list->FirstBit) {
// In case of IsMultipleItemsForArray for multiple dedicated usages for a multi-button array, the report count should be incremented
// Skip global items until any of them changes, than use ReportCount item to write the count of identical report fields
report_count++;
}
}
else {
if ((pp_data->caps[caps_idx].Button.LogicalMin == 0) &&
(pp_data->caps[caps_idx].Button.LogicalMax == 0)) {
// While a HID report descriptor must always contain LogicalMinimum and LogicalMaximum,
// the preparsed data contain both fields set to zero, for the case of simple buttons
// Write "Logical Minimum" set to 0 and "Logical Maximum" set to 1
rd_write_short_item(rd_global_logical_minimum, 0, &rpt_desc);
rd_write_short_item(rd_global_logical_maximum, 1, &rpt_desc);
}
else {
// Write logical range from "Logical Minimum" to "Logical Maximum"
rd_write_short_item(rd_global_logical_minimum, pp_data->caps[caps_idx].Button.LogicalMin, &rpt_desc);
rd_write_short_item(rd_global_logical_maximum, pp_data->caps[caps_idx].Button.LogicalMax, &rpt_desc);
}
// Write "Report Size"
rd_write_short_item(rd_global_report_size, pp_data->caps[caps_idx].ReportSize, &rpt_desc);
// Write "Report Count"
if (!pp_data->caps[caps_idx].IsRange) {
// Variable bit field with one bit per button
// In case of multiple usages with the same items, only "Usage" is written per cap, and "Report Count" is incremented
rd_write_short_item(rd_global_report_count, pp_data->caps[caps_idx].ReportCount + report_count, &rpt_desc);
}
else {
// Button array of "Report Size" x "Report Count
rd_write_short_item(rd_global_report_count, pp_data->caps[caps_idx].ReportCount, &rpt_desc);
}
// Buttons have only 1 bit and therefore no physical limits/units -> Set to undefined state
if (last_physical_min != 0) {
// Write "Physical Minimum", but only if changed
last_physical_min = 0;
rd_write_short_item(rd_global_physical_minimum, last_physical_min, &rpt_desc);
}
if (last_physical_max != 0) {
// Write "Physical Maximum", but only if changed
last_physical_max = 0;
rd_write_short_item(rd_global_physical_maximum, last_physical_max, &rpt_desc);
}
if (last_unit_exponent != 0) {
// Write "Unit Exponent", but only if changed
last_unit_exponent = 0;
rd_write_short_item(rd_global_unit_exponent, last_unit_exponent, &rpt_desc);
}
if (last_unit != 0) {
// Write "Unit",but only if changed
last_unit = 0;
rd_write_short_item(rd_global_unit, last_unit, &rpt_desc);
}
// Write "Input" main item
if (rt_idx == HidP_Input) {
rd_write_short_item(rd_main_input, pp_data->caps[caps_idx].BitField, &rpt_desc);
}
// Write "Output" main item
else if (rt_idx == HidP_Output) {
rd_write_short_item(rd_main_output, pp_data->caps[caps_idx].BitField, &rpt_desc);
}
// Write "Feature" main item
else if (rt_idx == HidP_Feature) {
rd_write_short_item(rd_main_feature, pp_data->caps[caps_idx].BitField, &rpt_desc);
}
report_count = 0;
}
}
else {
if (last_report_id != pp_data->caps[caps_idx].ReportID) {
// Write "Report ID" if changed
rd_write_short_item(rd_global_report_id, pp_data->caps[caps_idx].ReportID, &rpt_desc);
last_report_id = pp_data->caps[caps_idx].ReportID;
}
// Write "Usage Page" if changed
if (pp_data->caps[caps_idx].UsagePage != last_usage_page) {
rd_write_short_item(rd_global_usage_page, pp_data->caps[caps_idx].UsagePage, &rpt_desc);
last_usage_page = pp_data->caps[caps_idx].UsagePage;
}
if (inhibit_write_of_usage) {
// Inhibit only once after Delimiter - Reset flag
inhibit_write_of_usage = FALSE;
}
else {
if (pp_data->caps[caps_idx].IsRange) {
// Write usage range from "Usage Minimum" to "Usage Maximum"
rd_write_short_item(rd_local_usage_minimum, pp_data->caps[caps_idx].Range.UsageMin, &rpt_desc);
rd_write_short_item(rd_local_usage_maximum, pp_data->caps[caps_idx].Range.UsageMax, &rpt_desc);
}
else {
// Write single "Usage"
rd_write_short_item(rd_local_usage, pp_data->caps[caps_idx].NotRange.Usage, &rpt_desc);
}
}
if (pp_data->caps[caps_idx].IsDesignatorRange) {
// Write physical descriptor indices range from "Designator Minimum" to "Designator Maximum"
rd_write_short_item(rd_local_designator_minimum, pp_data->caps[caps_idx].Range.DesignatorMin, &rpt_desc);
rd_write_short_item(rd_local_designator_maximum, pp_data->caps[caps_idx].Range.DesignatorMax, &rpt_desc);
}
else if (pp_data->caps[caps_idx].NotRange.DesignatorIndex != 0) {
// Designator set 0 is a special descriptor set (of the HID Physical Descriptor),
// that specifies the number of additional descriptor sets.
// Therefore Designator Index 0 can never be a useful reference for a control and we can inhibit it.
// Write single "Designator Index"
rd_write_short_item(rd_local_designator_index, pp_data->caps[caps_idx].NotRange.DesignatorIndex, &rpt_desc);
}
if (pp_data->caps[caps_idx].IsStringRange) {
// Write range of indices of the USB string descriptor, from "String Minimum" to "String Maximum"
rd_write_short_item(rd_local_string_minimum, pp_data->caps[caps_idx].Range.StringMin, &rpt_desc);
rd_write_short_item(rd_local_string_maximum, pp_data->caps[caps_idx].Range.StringMax, &rpt_desc);
}
else if (pp_data->caps[caps_idx].NotRange.StringIndex != 0) {
// String Index 0 is a special entry of the USB string descriptor, that contains a list of supported languages,
// therefore Designator Index 0 can never be a useful reference for a control and we can inhibit it.
// Write single "String Index"
rd_write_short_item(rd_local_string, pp_data->caps[caps_idx].NotRange.StringIndex, &rpt_desc);
}
if ((pp_data->caps[caps_idx].BitField & 0x02) != 0x02) {
// In case of an value array overwrite "Report Count"
pp_data->caps[caps_idx].ReportCount = pp_data->caps[caps_idx].Range.DataIndexMax - pp_data->caps[caps_idx].Range.DataIndexMin + 1;
}
// Print only local report items for each cap, if ReportCount > 1
if ((main_item_list->next != NULL) &&
((int) main_item_list->next->MainItemType == rt_idx) &&
(main_item_list->next->TypeOfNode == rd_item_node_cap) &&
(!pp_data->caps[main_item_list->next->CapsIndex].IsButtonCap) &&
(!pp_data->caps[caps_idx].IsRange) && // This node in list is no array
(!pp_data->caps[main_item_list->next->CapsIndex].IsRange) && // Next node in list is no array
(pp_data->caps[main_item_list->next->CapsIndex].UsagePage == pp_data->caps[caps_idx].UsagePage) &&
(pp_data->caps[main_item_list->next->CapsIndex].NotButton.LogicalMin == pp_data->caps[caps_idx].NotButton.LogicalMin) &&
(pp_data->caps[main_item_list->next->CapsIndex].NotButton.LogicalMax == pp_data->caps[caps_idx].NotButton.LogicalMax) &&
(pp_data->caps[main_item_list->next->CapsIndex].NotButton.PhysicalMin == pp_data->caps[caps_idx].NotButton.PhysicalMin) &&
(pp_data->caps[main_item_list->next->CapsIndex].NotButton.PhysicalMax == pp_data->caps[caps_idx].NotButton.PhysicalMax) &&
(pp_data->caps[main_item_list->next->CapsIndex].UnitsExp == pp_data->caps[caps_idx].UnitsExp) &&
(pp_data->caps[main_item_list->next->CapsIndex].Units == pp_data->caps[caps_idx].Units) &&
(pp_data->caps[main_item_list->next->CapsIndex].ReportSize == pp_data->caps[caps_idx].ReportSize) &&
(pp_data->caps[main_item_list->next->CapsIndex].ReportID == pp_data->caps[caps_idx].ReportID) &&
(pp_data->caps[main_item_list->next->CapsIndex].BitField == pp_data->caps[caps_idx].BitField) &&
(pp_data->caps[main_item_list->next->CapsIndex].ReportCount == 1) &&
(pp_data->caps[caps_idx].ReportCount == 1)
) {
// Skip global items until any of them changes, than use ReportCount item to write the count of identical report fields
report_count++;
}
else {
// Value
// Write logical range from "Logical Minimum" to "Logical Maximum"
rd_write_short_item(rd_global_logical_minimum, pp_data->caps[caps_idx].NotButton.LogicalMin, &rpt_desc);
rd_write_short_item(rd_global_logical_maximum, pp_data->caps[caps_idx].NotButton.LogicalMax, &rpt_desc);
if ((last_physical_min != pp_data->caps[caps_idx].NotButton.PhysicalMin) ||
(last_physical_max != pp_data->caps[caps_idx].NotButton.PhysicalMax)) {
// Write range from "Physical Minimum" to " Physical Maximum", but only if one of them changed
rd_write_short_item(rd_global_physical_minimum, pp_data->caps[caps_idx].NotButton.PhysicalMin, &rpt_desc);
last_physical_min = pp_data->caps[caps_idx].NotButton.PhysicalMin;
rd_write_short_item(rd_global_physical_maximum, pp_data->caps[caps_idx].NotButton.PhysicalMax, &rpt_desc);
last_physical_max = pp_data->caps[caps_idx].NotButton.PhysicalMax;
}
if (last_unit_exponent != pp_data->caps[caps_idx].UnitsExp) {
// Write "Unit Exponent", but only if changed
rd_write_short_item(rd_global_unit_exponent, pp_data->caps[caps_idx].UnitsExp, &rpt_desc);
last_unit_exponent = pp_data->caps[caps_idx].UnitsExp;
}
if (last_unit != pp_data->caps[caps_idx].Units) {
// Write physical "Unit", but only if changed
rd_write_short_item(rd_global_unit, pp_data->caps[caps_idx].Units, &rpt_desc);
last_unit = pp_data->caps[caps_idx].Units;
}
// Write "Report Size"
rd_write_short_item(rd_global_report_size, pp_data->caps[caps_idx].ReportSize, &rpt_desc);
// Write "Report Count"
rd_write_short_item(rd_global_report_count, pp_data->caps[caps_idx].ReportCount + report_count, &rpt_desc);
if (rt_idx == HidP_Input) {
// Write "Input" main item
rd_write_short_item(rd_main_input, pp_data->caps[caps_idx].BitField, &rpt_desc);
}
else if (rt_idx == HidP_Output) {
// Write "Output" main item
rd_write_short_item(rd_main_output, pp_data->caps[caps_idx].BitField, &rpt_desc);
}
else if (rt_idx == HidP_Feature) {
// Write "Feature" main item
rd_write_short_item(rd_main_feature, pp_data->caps[caps_idx].BitField, &rpt_desc);
}
report_count = 0;
}
}
// Go to next item in main_item_list and free the memory of the actual item
struct rd_main_item_node *main_item_list_prev = main_item_list;
main_item_list = main_item_list->next;
free(main_item_list_prev);
}
// Free multidimensionable array: coll_bit_range[COLLECTION_INDEX][REPORT_ID][INPUT/OUTPUT/FEATURE]
// Free multidimensionable array: coll_child_order[COLLECTION_INDEX][DIRECT_CHILD_INDEX]
for (USHORT collection_node_idx = 0; collection_node_idx < pp_data->NumberLinkCollectionNodes; collection_node_idx++) {
for (int reportid_idx = 0; reportid_idx < 256; reportid_idx++) {
for (HIDP_REPORT_TYPE rt_idx = 0; rt_idx < NUM_OF_HIDP_REPORT_TYPES; rt_idx++) {
free(coll_bit_range[collection_node_idx][reportid_idx][rt_idx]);
}
free(coll_bit_range[collection_node_idx][reportid_idx]);
}
free(coll_bit_range[collection_node_idx]);
if (coll_number_of_direct_childs[collection_node_idx] != 0) free(coll_child_order[collection_node_idx]);
}
free(coll_bit_range);
free(coll_child_order);
// Free one dimensional arrays
free(coll_begin_lookup);
free(coll_end_lookup);
free(coll_levels);
free(coll_number_of_direct_childs);
return (int) rpt_desc.byte_idx;
}

View file

@ -0,0 +1,254 @@
/*******************************************************
HIDAPI - Multi-Platform library for
communication with HID devices.
libusb/hidapi Team
Copyright 2022, All Rights Reserved.
At the discretion of the user of this library,
this software may be licensed under the terms of the
GNU General Public License v3, a BSD-Style license, or the
original HIDAPI license as outlined in the LICENSE.txt,
LICENSE-gpl3.txt, LICENSE-bsd.txt, and LICENSE-orig.txt
files located at the root of the source distribution.
These files may also be found in the public source
code repository located at:
https://github.com/libusb/hidapi .
********************************************************/
#include "SDL_internal.h"
#ifndef HIDAPI_DESCRIPTOR_RECONSTRUCT_H__
#define HIDAPI_DESCRIPTOR_RECONSTRUCT_H__
#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS)
/* Do not warn about wcsncpy usage.
https://docs.microsoft.com/cpp/c-runtime-library/security-features-in-the-crt */
#define _CRT_SECURE_NO_WARNINGS
#endif
#include "hidapi_winapi.h"
#ifdef _MSC_VER
#pragma warning(push)
#pragma warning(disable: 4200)
#pragma warning(disable: 4201)
#pragma warning(disable: 4214)
#endif
#include <windows.h>
#include "hidapi_hidsdi.h"
/*#include <assert.h>*/
#define NUM_OF_HIDP_REPORT_TYPES 3
typedef enum rd_items_ {
rd_main_input = 0x80, /* 1000 00 nn */
rd_main_output = 0x90, /* 1001 00 nn */
rd_main_feature = 0xB0, /* 1011 00 nn */
rd_main_collection = 0xA0, /* 1010 00 nn */
rd_main_collection_end = 0xC0, /* 1100 00 nn */
rd_global_usage_page = 0x04, /* 0000 01 nn */
rd_global_logical_minimum = 0x14, /* 0001 01 nn */
rd_global_logical_maximum = 0x24, /* 0010 01 nn */
rd_global_physical_minimum = 0x34, /* 0011 01 nn */
rd_global_physical_maximum = 0x44, /* 0100 01 nn */
rd_global_unit_exponent = 0x54, /* 0101 01 nn */
rd_global_unit = 0x64, /* 0110 01 nn */
rd_global_report_size = 0x74, /* 0111 01 nn */
rd_global_report_id = 0x84, /* 1000 01 nn */
rd_global_report_count = 0x94, /* 1001 01 nn */
rd_global_push = 0xA4, /* 1010 01 nn */
rd_global_pop = 0xB4, /* 1011 01 nn */
rd_local_usage = 0x08, /* 0000 10 nn */
rd_local_usage_minimum = 0x18, /* 0001 10 nn */
rd_local_usage_maximum = 0x28, /* 0010 10 nn */
rd_local_designator_index = 0x38, /* 0011 10 nn */
rd_local_designator_minimum = 0x48, /* 0100 10 nn */
rd_local_designator_maximum = 0x58, /* 0101 10 nn */
rd_local_string = 0x78, /* 0111 10 nn */
rd_local_string_minimum = 0x88, /* 1000 10 nn */
rd_local_string_maximum = 0x98, /* 1001 10 nn */
rd_local_delimiter = 0xA8 /* 1010 10 nn */
} rd_items;
typedef enum rd_main_items_ {
rd_input = HidP_Input,
rd_output = HidP_Output,
rd_feature = HidP_Feature,
rd_collection,
rd_collection_end,
rd_delimiter_open,
rd_delimiter_usage,
rd_delimiter_close,
} rd_main_items;
typedef struct rd_bit_range_ {
int FirstBit;
int LastBit;
} rd_bit_range;
typedef enum rd_item_node_type_ {
rd_item_node_cap,
rd_item_node_padding,
rd_item_node_collection,
} rd_node_type;
struct rd_main_item_node {
int FirstBit; /* Position of first bit in report (counting from 0) */
int LastBit; /* Position of last bit in report (counting from 0) */
rd_node_type TypeOfNode; /* Information if caps index refers to the array of button caps, value caps,
or if the node is just a padding element to fill unused bit positions.
The node can also be a collection node without any bits in the report. */
int CapsIndex; /* Index in the array of caps */
int CollectionIndex; /* Index in the array of link collections */
rd_main_items MainItemType; /* Input, Output, Feature, Collection or Collection End */
unsigned char ReportID;
struct rd_main_item_node* next;
};
typedef struct hid_pp_caps_info_ {
USHORT FirstCap;
USHORT NumberOfCaps; // Includes empty caps after LastCap
USHORT LastCap;
USHORT ReportByteLength;
} hid_pp_caps_info, *phid_pp_caps_info;
typedef struct hid_pp_link_collection_node_ {
USAGE LinkUsage;
USAGE LinkUsagePage;
USHORT Parent;
USHORT NumberOfChildren;
USHORT NextSibling;
USHORT FirstChild;
ULONG CollectionType : 8;
ULONG IsAlias : 1;
ULONG Reserved : 23;
// Same as the public API structure HIDP_LINK_COLLECTION_NODE, but without PVOID UserContext at the end
} hid_pp_link_collection_node, *phid_pp_link_collection_node;
// Note: This is risk-reduction-measure for this specific struct, as it has ULONG bit-field.
// Although very unlikely, it might still be possible that the compiler creates a memory layout that is
// not binary compatile.
// Other structs are not checked at the time of writing.
//static_assert(sizeof(struct hid_pp_link_collection_node_) == 16,
// "Size of struct hid_pp_link_collection_node_ not as expected. This might break binary compatibility");
SDL_COMPILE_TIME_ASSERT(hid_pp_link_collection_node_, sizeof(struct hid_pp_link_collection_node_) == 16);
typedef struct hidp_unknown_token_ {
UCHAR Token; /* Specifies the one-byte prefix of a global item. */
UCHAR Reserved[3];
ULONG BitField; /* Specifies the data part of the global item. */
} hidp_unknown_token, * phidp_unknown_token;
typedef struct hid_pp_cap_ {
USAGE UsagePage;
UCHAR ReportID;
UCHAR BitPosition;
USHORT ReportSize; // WIN32 term for this is BitSize
USHORT ReportCount;
USHORT BytePosition;
USHORT BitCount;
ULONG BitField;
USHORT NextBytePosition;
USHORT LinkCollection;
USAGE LinkUsagePage;
USAGE LinkUsage;
// Start of 8 Flags in one byte
BOOLEAN IsMultipleItemsForArray:1;
BOOLEAN IsPadding:1;
BOOLEAN IsButtonCap:1;
BOOLEAN IsAbsolute:1;
BOOLEAN IsRange:1;
BOOLEAN IsAlias:1; // IsAlias is set to TRUE in the first n-1 capability structures added to the capability array. IsAlias set to FALSE in the nth capability structure.
BOOLEAN IsStringRange:1;
BOOLEAN IsDesignatorRange:1;
// End of 8 Flags in one byte
BOOLEAN Reserved1[3];
hidp_unknown_token UnknownTokens[4]; // 4 x 8 Byte
union {
struct {
USAGE UsageMin;
USAGE UsageMax;
USHORT StringMin;
USHORT StringMax;
USHORT DesignatorMin;
USHORT DesignatorMax;
USHORT DataIndexMin;
USHORT DataIndexMax;
} Range;
struct {
USAGE Usage;
USAGE Reserved1;
USHORT StringIndex;
USHORT Reserved2;
USHORT DesignatorIndex;
USHORT Reserved3;
USHORT DataIndex;
USHORT Reserved4;
} NotRange;
};
union {
struct {
LONG LogicalMin;
LONG LogicalMax;
} Button;
struct {
BOOLEAN HasNull;
UCHAR Reserved4[3];
LONG LogicalMin;
LONG LogicalMax;
LONG PhysicalMin;
LONG PhysicalMax;
} NotButton;
};
ULONG Units;
ULONG UnitsExp;
} hid_pp_cap, *phid_pp_cap;
typedef struct hidp_preparsed_data_ {
UCHAR MagicKey[8];
USAGE Usage;
USAGE UsagePage;
USHORT Reserved[2];
// CAPS structure for Input, Output and Feature
hid_pp_caps_info caps_info[3];
USHORT FirstByteOfLinkCollectionArray;
USHORT NumberLinkCollectionNodes;
#ifndef _MSC_VER
// MINGW fails with: Flexible array member in union not supported
// Solution: https://gcc.gnu.org/onlinedocs/gcc/Zero-Length.html
union {
#ifdef HAVE_GCC_DIAGNOSTIC_PRAGMA
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wpedantic"
#endif
hid_pp_cap caps[0];
hid_pp_link_collection_node LinkCollectionArray[0];
#ifdef HAVE_GCC_DIAGNOSTIC_PRAGMA
#pragma GCC diagnostic pop
#endif
};
#else
union {
hid_pp_cap caps[];
hid_pp_link_collection_node LinkCollectionArray[];
};
#endif
} hidp_preparsed_data;
#ifdef _MSC_VER
#pragma warning(pop)
#endif
#endif

View file

@ -0,0 +1,40 @@
/*******************************************************
HIDAPI - Multi-Platform library for
communication with HID devices.
libusb/hidapi Team
Copyright 2022, All Rights Reserved.
At the discretion of the user of this library,
this software may be licensed under the terms of the
GNU General Public License v3, a BSD-Style license, or the
original HIDAPI license as outlined in the LICENSE.txt,
LICENSE-gpl3.txt, LICENSE-bsd.txt, and LICENSE-orig.txt
files located at the root of the source distribution.
These files may also be found in the public source
code repository located at:
https://github.com/libusb/hidapi .
********************************************************/
#ifndef HIDAPI_HIDCLASS_H
#define HIDAPI_HIDCLASS_H
#ifdef HIDAPI_USE_DDK
#include <hidclass.h>
#else
#include <winioctl.h>
/* This part of the header mimics hidclass.h,
but only what is used by HIDAPI */
#define HID_OUT_CTL_CODE(id) CTL_CODE(FILE_DEVICE_KEYBOARD, (id), METHOD_OUT_DIRECT, FILE_ANY_ACCESS)
#define IOCTL_HID_GET_FEATURE HID_OUT_CTL_CODE(100)
#define IOCTL_HID_GET_INPUT_REPORT HID_OUT_CTL_CODE(104)
#endif
#endif /* HIDAPI_HIDCLASS_H */

Some files were not shown because too many files have changed in this diff Show more