SDL: Added SDL_ResetHint() to reset a hint to the default value

From d4192850c1efdb24064fbf0057978ff91947cf56 Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Wed, 10 Aug 2022 07:59:12 -0700
Subject: [PATCH] Added SDL_ResetHint() to reset a hint to the default value

Resolves question of how to clear an override hint raised by @pionere in https://github.com/libsdl-org/SDL/pull/5309
---
 WhatsNew.txt                      |  1 +
 include/SDL_hints.h               | 17 ++++++++++
 src/SDL_hints.c                   | 37 ++++++++++++++++++++++
 src/dynapi/SDL2.exports           |  1 +
 src/dynapi/SDL_dynapi_overrides.h |  1 +
 src/dynapi/SDL_dynapi_procs.h     |  1 +
 test/testautomation_hints.c       | 52 +++++++++++++++++++++++++++++--
 7 files changed, 108 insertions(+), 2 deletions(-)

diff --git a/WhatsNew.txt b/WhatsNew.txt
index feb4aaca3d5..8997d0bf8f3 100644
--- a/WhatsNew.txt
+++ b/WhatsNew.txt
@@ -20,6 +20,7 @@ General:
         the SDL 2.24.0 stable release.
 * Added SDL_bsearch() and SDL_utf8strnlen() to the stdlib routines
 * Added SDL_size_mul_overflow() and SDL_size_add_overflow() for better size overflow protection
+* Added SDL_ResetHint() to reset a hint to the default value
 * The hint SDL_HINT_JOYSTICK_HIDAPI_JOY_CONS now defaults on
 * Added support for mini-gamepad mode for Nintendo Joy-Con controllers using the HIDAPI driver
 * Added the hint SDL_HINT_JOYSTICK_HIDAPI_COMBINE_JOY_CONS to control whether Joy-Con controllers are automatically merged into a unified gamepad when using the HIDAPI driver. This hint defaults on.
diff --git a/include/SDL_hints.h b/include/SDL_hints.h
index 02d5fc5f845..2c7a33aec82 100644
--- a/include/SDL_hints.h
+++ b/include/SDL_hints.h
@@ -2286,6 +2286,23 @@ extern DECLSPEC SDL_bool SDLCALL SDL_SetHintWithPriority(const char *name,
 extern DECLSPEC SDL_bool SDLCALL SDL_SetHint(const char *name,
                                              const char *value);
 
+/**
+ * Reset a hint to the default value.
+ *
+ * This will reset a hint to the value of the environment variable, or NULL
+ * if the environment isn't set. Callbacks will be called normally with
+ * this change.
+ *
+ * \param name the hint to set
+ * \returns SDL_TRUE if the hint was set, SDL_FALSE otherwise.
+ *
+ * \since This function is available since SDL 2.24.0.
+ *
+ * \sa SDL_GetHint
+ * \sa SDL_SetHint
+ */
+extern DECLSPEC SDL_bool SDLCALL SDL_ResetHint(const char *name);
+
 /**
  * Get the value of a hint.
  *
diff --git a/src/SDL_hints.c b/src/SDL_hints.c
index 423851a8382..93bcc47cab3 100644
--- a/src/SDL_hints.c
+++ b/src/SDL_hints.c
@@ -96,6 +96,43 @@ SDL_SetHintWithPriority(const char *name, const char *value,
     return SDL_TRUE;
 }
 
+SDL_bool
+SDL_ResetHint(const char *name)
+{
+    const char *env;
+    SDL_Hint *hint, *prev;
+    SDL_HintWatch *entry;
+
+    if (!name) {
+        return SDL_FALSE;
+    }
+
+    env = SDL_getenv(name);
+    for (prev = NULL, hint = SDL_hints; hint; prev = hint, hint = hint->next) {
+        if (SDL_strcmp(name, hint->name) == 0) {
+            if ((env == NULL && hint->value != NULL) ||
+                (env != NULL && hint->value == NULL) ||
+                (env && SDL_strcmp(env, hint->value) != 0)) {
+                for (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;
+                }
+            }
+            if (prev) {
+                prev->next = hint->next;
+            } else {
+                SDL_hints = hint->next;
+            }
+            SDL_free(hint->value);
+            SDL_free(hint);
+            return SDL_TRUE;
+        }
+    }
+    return SDL_FALSE;
+}
+
 SDL_bool
 SDL_SetHint(const char *name, const char *value)
 {
diff --git a/src/dynapi/SDL2.exports b/src/dynapi/SDL2.exports
index 80d15ee5a61..c790e2582ce 100644
--- a/src/dynapi/SDL2.exports
+++ b/src/dynapi/SDL2.exports
@@ -856,3 +856,4 @@
 ++'_SDL_GetDefaultAudioInfo'.'SDL2.dll'.'SDL_GetDefaultAudioInfo'
 ++'_SDL_GetPointDisplayIndex'.'SDL2.dll'.'SDL_GetPointDisplayIndex'
 ++'_SDL_GetRectDisplayIndex'.'SDL2.dll'.'SDL_GetRectDisplayIndex'
+++'_SDL_ResetHint'.'SDL2.dll'.'SDL_ResetHint'
diff --git a/src/dynapi/SDL_dynapi_overrides.h b/src/dynapi/SDL_dynapi_overrides.h
index 91f13c3e316..48c599bbbc4 100644
--- a/src/dynapi/SDL_dynapi_overrides.h
+++ b/src/dynapi/SDL_dynapi_overrides.h
@@ -882,3 +882,4 @@
 #define SDL_GetDefaultAudioInfo SDL_GetDefaultAudioInfo_REAL
 #define SDL_GetPointDisplayIndex SDL_GetPointDisplayIndex_REAL
 #define SDL_GetRectDisplayIndex SDL_GetRectDisplayIndex_REAL
+#define SDL_ResetHint SDL_ResetHint_REAL
diff --git a/src/dynapi/SDL_dynapi_procs.h b/src/dynapi/SDL_dynapi_procs.h
index f23cdf7c996..1d2cbfe230d 100644
--- a/src/dynapi/SDL_dynapi_procs.h
+++ b/src/dynapi/SDL_dynapi_procs.h
@@ -965,3 +965,4 @@ SDL_DYNAPI_PROC(void,SDL_ResetKeyboard,(void),(),)
 SDL_DYNAPI_PROC(int,SDL_GetDefaultAudioInfo,(char **a, SDL_AudioSpec *b, int c),(a,b,c),return)
 SDL_DYNAPI_PROC(int,SDL_GetPointDisplayIndex,(const SDL_Point *a),(a),return)
 SDL_DYNAPI_PROC(int,SDL_GetRectDisplayIndex,(const SDL_Rect *a),(a),return)
+SDL_DYNAPI_PROC(SDL_bool,SDL_ResetHint,(const char *a),(a),return)
diff --git a/test/testautomation_hints.c b/test/testautomation_hints.c
index 91b8c02ab4b..bbcc9fd219f 100644
--- a/test/testautomation_hints.c
+++ b/test/testautomation_hints.c
@@ -98,8 +98,9 @@ hints_getHint(void *arg)
 int
 hints_setHint(void *arg)
 {
+  const char *testHint = "SDL_AUTOMATED_TEST_HINT";
   const char *originalValue;
-  const char *value;
+  char *value;
   const char *testValue;
   SDL_bool result;
   int i, j;
@@ -142,7 +143,54 @@ hints_setHint(void *arg)
     SDL_free((void *)originalValue);
   }
 
-  SDL_free((void *)value);
+  SDL_free(value);
+
+  /* Set default value in environment */
+  SDL_setenv(testHint, "original", 1);
+
+  SDLTest_AssertPass("Call to SDL_GetHint() after saving and restoring hint");
+  originalValue = SDL_GetHint(testHint);
+  value = (originalValue == NULL) ? NULL : SDL_strdup(originalValue);
+  SDL_SetHint(testHint, "temp");
+  SDL_SetHint(testHint, value);
+  SDL_free(value);
+  testValue = SDL_GetHint(testHint);
+  SDLTest_AssertCheck(
+    testValue && SDL_strcmp(testValue, "original") == 0,
+    "testValue = %s, expected \"original\"",
+    testValue);
+
+  SDLTest_AssertPass("Call to SDL_SetHintWithPriority(NULL, SDL_HINT_DEFAULT)");
+  SDL_SetHintWithPriority(testHint, NULL, SDL_HINT_DEFAULT);
+  testValue = SDL_GetHint(testHint);
+  SDLTest_AssertCheck(
+    testValue && SDL_strcmp(testValue, "original") == 0,
+    "testValue = %s, expected \"original\"",
+    testValue);
+
+  SDLTest_AssertPass("Call to SDL_SetHintWithPriority(\"temp\", SDL_HINT_OVERRIDE)");
+  SDL_SetHintWithPriority(testHint, "temp", SDL_HINT_OVERRIDE);
+  testValue = SDL_GetHint(testHint);
+  SDLTest_AssertCheck(
+    testValue && SDL_strcmp(testValue, "temp") == 0,
+    "testValue = %s, expected \"temp\"",
+    testValue);
+
+  SDLTest_AssertPass("Call to SDL_SetHintWithPriority(NULL, SDL_HINT_OVERRIDE)");
+  SDL_SetHintWithPriority(testHint, NULL, SDL_HINT_OVERRIDE);
+  testValue = SDL_GetHint(testHint);
+  SDLTest_AssertCheck(
+    testValue == NULL,
+    "testValue = %s, expected NULL",
+    testValue);
+
+  SDLTest_AssertPass("Call to SDL_ResetHint()");
+  SDL_ResetHint(testHint);
+  testValue = SDL_GetHint(testHint);
+  SDLTest_AssertCheck(
+    testValue && SDL_strcmp(testValue, "original") == 0,
+    "testValue = %s, expected \"original\"",
+    testValue);
 
   return TEST_COMPLETED;
 }