sdl12-compat: env: Deal with environment variables in the 1.2 way.

From 57847a9ee21e7f6fb5ea0a1c88c423edac87b17a Mon Sep 17 00:00:00 2001
From: "Ryan C. Gordon" <[EMAIL REDACTED]>
Date: Tue, 4 Nov 2025 11:03:17 -0500
Subject: [PATCH] env: Deal with environment variables in the 1.2 way.

There was no Hint API, so environment variables might change on the fly as
apps needed different things. SDL3 (an thus, sdl2-compat) will ignore envvar
changes after startup, since it copies the OS environment table to an
SDL_Environment, and will miss these later changes.

So sdl12-compat will use the "unsafe" (that is: not thread safe) methods to
pull in environment variables, ignoring SDL2's SDL_getenv, and will take
measures to explicitly set hints that SDL2 and later might need, so they are
seen internally despite the state of the SDL3 internal SDL_Environment table.

Reference Issue https://github.com/libsdl-org/sdl2-compat/issues/540
---
 src/SDL12_compat.c | 107 +++++++++++++++++++++++++--------------------
 src/SDL20_syms.h   |   3 +-
 2 files changed, 62 insertions(+), 48 deletions(-)

diff --git a/src/SDL12_compat.c b/src/SDL12_compat.c
index ba5e25522..fab38d40b 100644
--- a/src/SDL12_compat.c
+++ b/src/SDL12_compat.c
@@ -1181,22 +1181,20 @@ static SDL_bool SDL12COMPAT_strequal(const char *a, const char *b)
     return SDL_TRUE;
 }
 
-/* log a string using platform-specific code for before SDL2 is fully available. */
-static void SDL12COMPAT_LogAtStartup(const char *str)
+/* SDL3 (and thus sdl2-compat) will build an SDL_Environment, which isn't useful if the SDL-1.2 app is calling getenv()/setenv() directly, so use system APIs instead. */
+/* despite the "unsafe" name (they are NOT thread-safe), these are actually _safe_ to call at startup, since it won't call into SDL2 before everything is properly initialized! */
+static char *SDL12COMPAT_getenv_unsafe(const char *name)
 {
     #ifdef _WIN32
-    OutputDebugStringA(str);
-    #elif defined(__APPLE__)
-    extern void SDL12COMPAT_NSLog(const char *prefix, const char *text);
-    SDL12COMPAT_NSLog(NULL, str);
-    #else
-    fputs(str, stderr);
-    fputs("\n", stderr);
+    static char buf[256];  /* overflows will just report as environment variable being unset. But most of our environment vars don't come through here. */
+    const DWORD rc = GetEnvironmentVariableA(name, buf, (DWORD) sizeof (buf));
+    return ((rc != 0) && (rc < sizeof (buf))) ? buf : NULL;
+    #else  /* we might need other platforms, or a simple `return NULL;` for platforms without an environment table. */
+    return getenv(name);
     #endif
 }
 
-/* this talks right to the OS environment table. Don't use SDL20_setenv at startup. */
-static void SDL12COMPAT_SetEnvAtStartup(const char *name, const char *value)
+static void SDL12COMPAT_setenv_unsafe(const char *name, const char *value)
 {
     #ifdef _WIN32
     SetEnvironmentVariableA(name, value);
@@ -1211,15 +1209,27 @@ static void SDL12COMPAT_SetEnvAtStartup(const char *name, const char *value)
     #endif
 }
 
-/* this talks right to the OS environment table. Don't use SDL20_getenv at startup. */
 static const char *SDL12COMPAT_GetEnvAtStartup(const char *name)
+{
+    return SDL12COMPAT_getenv_unsafe(name);  /* don't talk to SDL2 yet, we aren't set up. Go right to the OS interfaces. */
+}
+
+static void SDL12COMPAT_SetEnvAtStartup(const char *name, const char *value)
+{
+    SDL12COMPAT_setenv_unsafe(name, value);  /* don't talk to SDL2 yet, we aren't set up. Go right to the OS interfaces. */
+}
+
+/* log a string using platform-specific code for before SDL2 is fully available. */
+static void SDL12COMPAT_LogAtStartup(const char *str)
 {
     #ifdef _WIN32
-    static char buf[256];  /* overflows will just report as environment variable being unset. But most of our environment vars don't come through here. */
-    const DWORD rc = GetEnvironmentVariableA(name, buf, (DWORD) sizeof (buf));
-    return ((rc != 0) && (rc < sizeof (buf))) ? buf : NULL;
-    #else  /* we might need other platforms, or a simple `return NULL;` for platforms without an environment table. */
-    return getenv(name);
+    OutputDebugStringA(str);
+    #elif defined(__APPLE__)
+    extern void SDL12COMPAT_NSLog(const char *prefix, const char *text);
+    SDL12COMPAT_NSLog(NULL, str);
+    #else
+    fputs(str, stderr);
+    fputs("\n", stderr);
     #endif
 }
 
@@ -1310,7 +1320,7 @@ static char loaderror[256];
                     homedir = pwent->pw_dir;
                 }
                 if (!homedir) {
-                    homedir = getenv("HOME");
+                    homedir = SDL12COMPAT_getenv_unsafe("HOME");
                 }
                 if (homedir) {
                     char framework[512];
@@ -1531,7 +1541,7 @@ SDL12Compat_GetExeName(void)
 static const char *
 SDL12Compat_GetHint(const char *name)
 {
-    return SDL20_getenv(name);
+    return SDL12COMPAT_getenv_unsafe(name);
 }
 
 static SDL_bool
@@ -2395,9 +2405,9 @@ static int
 GetVideoDisplay(void)
 {
     const char *variable;
-    variable = SDL20_getenv("SDL_VIDEO_FULLSCREEN_DISPLAY");
+    variable = SDL12COMPAT_getenv_unsafe("SDL_VIDEO_FULLSCREEN_DISPLAY");
     if (!variable) {
-        variable = SDL20_getenv("SDL_VIDEO_FULLSCREEN_HEAD");
+        variable = SDL12COMPAT_getenv_unsafe("SDL_VIDEO_FULLSCREEN_HEAD");
     }
     if (variable) {
         int preferred_display = SDL20_atoi(variable);
@@ -2780,23 +2790,16 @@ static void QuitCDSubsystem(void);
 DECLSPEC12 int SDLCALL
 SDL_InitSubSystem(Uint32 sdl12flags)
 {
+    const char *videodriver = SDL12COMPAT_getenv_unsafe("SDL_VIDEODRIVER");
+    const char *audiodriver = SDL12COMPAT_getenv_unsafe("SDL_AUDIODRIVER");
     Uint32 sdl20flags = 0;
     int rc;
 
 #ifdef __WINDOWS__
     /* DOSBox (and probably other things), try to force the "windib" video
        backend, but it doesn't exist in SDL2. Force to "windows" instead. */
-    const char *origvidenv = NULL;
-    const char *env = SDL20_getenv("SDL_VIDEODRIVER");
-    if (env) {
-        if (SDL20_strcmp(env, "windib") == 0) {
-            origvidenv = "windib";
-            SDL20_setenv("SDL_VIDEODRIVER", "windows", 1);
-        } else
-        if (SDL20_strcmp(env, "directx") == 0) {
-            origvidenv = "directx";
-            SDL20_setenv("SDL_VIDEODRIVER", "windows", 1);
-        }
+    if (videodriver && (SDL20_strcmp(videodriver, "windib") == 0) || (SDL20_strcmp(videodriver, "directx") == 0)) {
+        videodriver = "windows";
     }
 #endif
 
@@ -2835,6 +2838,17 @@ SDL_InitSubSystem(Uint32 sdl12flags)
         InitializeCDSubsystem();
     }
 
+    /* In SDL3 (via sdl2-compat), these will ignore changes to environment variables after startup, but SDL-1.2 apps might
+       change envvars on the fly, so we need to manually force these into SDL2 hints. */
+
+    if (videodriver) {
+        SDL20_SetHintWithPriority(SDL_HINT_VIDEODRIVER, videodriver, SDL_HINT_OVERRIDE);
+    }
+
+    if (audiodriver) {
+        SDL20_SetHintWithPriority(SDL_HINT_AUDIODRIVER, audiodriver, SDL_HINT_OVERRIDE);
+    }
+
     rc = SDL20_Init(sdl20flags);
     if ((rc == 0) && (sdl20flags & SDL_INIT_VIDEO)) {
         if (Init12Video() < 0) {
@@ -2845,12 +2859,6 @@ SDL_InitSubSystem(Uint32 sdl12flags)
         EventThreadEnabled = (sdl12flags & SDL12_INIT_EVENTTHREAD) ? SDL_TRUE : SDL_FALSE;
     }
 
-#ifdef __WINDOWS__
-    if (origvidenv) {  /* set this back to minimize surprise state changes. */
-        SDL20_setenv("SDL_VIDEODRIVER", origvidenv, 1);
-    }
-#endif
-
     if ((rc == 0) && (sdl20flags & SDL_INIT_AUDIO)) {
         Init12Audio();
     }
@@ -3086,7 +3094,7 @@ DECLSPEC12 const char * SDLCALL
 SDL_VideoDriverName(char *namebuf, int maxlen)
 {
 #ifdef __WINDOWS__
-    const char *val = SDL20_getenv("SDL_VIDEODRIVER");
+    const char *val = SDL12COMPAT_getenv_unsafe("SDL_VIDEODRIVER");
     if (val) {
         /* give them back what they requested: */
         if (SDL20_strcmp(val,  "windib") == 0  ||
@@ -5949,8 +5957,8 @@ static void
 GetEnvironmentWindowPosition(int *x, int *y)
 {
     int display = VideoDisplayIndex;
-    const char *window = SDL20_getenv("SDL_VIDEO_WINDOW_POS");
-    const char *center = SDL20_getenv("SDL_VIDEO_CENTERED");
+    const char *window = SDL12COMPAT_getenv_unsafe("SDL_VIDEO_WINDOW_POS");
+    const char *center = SDL12COMPAT_getenv_unsafe("SDL_VIDEO_CENTERED");
     if (window) {
         if (SDL20_strcmp(window, "center") == 0) {
             center = window;
@@ -6576,7 +6584,7 @@ SetVideoModeImpl(int width, int height, int bpp, Uint32 flags12)
         fullscreen_flags20 |= SDL_WINDOW_FULLSCREEN_DESKTOP;
     }
 
-    fromwin_env = SDL20_getenv("SDL_WINDOWID");
+    fromwin_env = SDL12COMPAT_getenv_unsafe("SDL_WINDOWID");
 
     if (fromwin_env) {
         window_size_scaling = 1.0f;  /* don't scale for external windows */
@@ -8599,6 +8607,11 @@ SDL_Delay(Uint32 ticks)
     SDL20_Delay(ticks);
 }
 
+DECLSPEC12 char * SDLCALL
+SDL_getenv(const char *name)
+{
+    return SDL12COMPAT_getenv_unsafe(name);
+}
 
 DECLSPEC12 int SDLCALL
 SDL_putenv(const char *_var)
@@ -8616,7 +8629,7 @@ SDL_putenv(const char *_var)
     }
 
     *ptr = '\0';  /* split the string into name and value. */
-    SDL20_setenv(var, ptr + 1, 1);
+    SDL12COMPAT_setenv_unsafe(var, ptr + 1);
     SDL20_free(var);
     return 0;
 }
@@ -9999,7 +10012,7 @@ SDL_OpenAudio(SDL_AudioSpec *want, SDL_AudioSpec *obtained)
     }
 
     if (!want->format) {
-        const char *env = SDL20_getenv("SDL_AUDIO_FORMAT");  /* SDL 1.2 checks this. */
+        const char *env = SDL12COMPAT_getenv_unsafe("SDL_AUDIO_FORMAT");  /* SDL 1.2 checks this. */
         if (env != NULL) {
             if      (SDL20_strcmp(env, "U8") == 0) { want->format = AUDIO_U8; }
             else if (SDL20_strcmp(env, "S8") == 0) { want->format = AUDIO_S8; }
@@ -10018,7 +10031,7 @@ SDL_OpenAudio(SDL_AudioSpec *want, SDL_AudioSpec *obtained)
     }
 
     if (!want->freq) {
-        const char *env = SDL20_getenv("SDL_AUDIO_FREQUENCY");  /* SDL 1.2 checks this. */
+        const char *env = SDL12COMPAT_getenv_unsafe("SDL_AUDIO_FREQUENCY");  /* SDL 1.2 checks this. */
         if (env != NULL) {
             want->freq = SDL20_atoi(env);
         }
@@ -10029,7 +10042,7 @@ SDL_OpenAudio(SDL_AudioSpec *want, SDL_AudioSpec *obtained)
     }
 
     if (!want->channels) {
-        const char *env = SDL20_getenv("SDL_AUDIO_CHANNELS");  /* SDL 1.2 checks this. */
+        const char *env = SDL12COMPAT_getenv_unsafe("SDL_AUDIO_CHANNELS");  /* SDL 1.2 checks this. */
         if (env != NULL) {
             want->channels = SDL20_atoi(env);
         }
@@ -10039,7 +10052,7 @@ SDL_OpenAudio(SDL_AudioSpec *want, SDL_AudioSpec *obtained)
     }
 
     if (!want->samples) {
-        const char *env = SDL20_getenv("SDL_AUDIO_SAMPLES");  /* SDL 1.2 checks this. */
+        const char *env = SDL12COMPAT_getenv_unsafe("SDL_AUDIO_SAMPLES");  /* SDL 1.2 checks this. */
         if (env != NULL) {
             want->samples = SDL20_atoi(env);
         }
diff --git a/src/SDL20_syms.h b/src/SDL20_syms.h
index ab7572d1b..5f1a2c9cb 100644
--- a/src/SDL20_syms.h
+++ b/src/SDL20_syms.h
@@ -273,7 +273,6 @@ SDL20_SYM_PASSTHROUGH(void *,malloc,(size_t a),(a),return)
 SDL20_SYM_PASSTHROUGH(void *,calloc,(size_t a, size_t b),(a,b),return)
 SDL20_SYM_PASSTHROUGH(void *,realloc,(void *a, size_t b),(a,b),return)
 SDL20_SYM_PASSTHROUGH(void,free,(void *a),(a),)
-SDL20_SYM_PASSTHROUGH(char *,getenv,(const char *a),(a),return)
 SDL20_SYM_PASSTHROUGH(void,qsort,(void *a, size_t b, size_t c, int (SDLCALL *d)(const void *, const void *)),(a,b,c,d),)
 SDL20_SYM_PASSTHROUGH(void *,memset,(void *a, int b, size_t c),(a,b,c),return)
 SDL20_SYM_PASSTHROUGH(void *,memcpy,(void *a, const void *b, size_t c),(a,b,c),return)
@@ -337,6 +336,8 @@ SDL20_SYM(void,DestroyTexture,(SDL_Texture *a),(a),)
 SDL20_SYM(void,DestroyRenderer,(SDL_Renderer *a),(a),)
 SDL20_SYM(void,RenderPresent,(SDL_Renderer *a),(a),)
 
+SDL20_SYM(SDL_bool,SetHintWithPriority,(const char *a, const char *b, SDL_HintPriority c),(a,b,c),return)
+
 #ifdef _WIN32
 SDL20_SYM_PASSTHROUGH(int,RegisterApp,(const char *a, Uint32 b, void *c),(a,b,c),return)
 SDL20_SYM_PASSTHROUGH(void,UnregisterApp,(void),(),)