1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#include "SDL_hints_c.h"
24
25typedef struct SDL_HintWatch
26{
27 SDL_HintCallback callback;
28 void *userdata;
29 struct SDL_HintWatch *next;
30} SDL_HintWatch;
31
32typedef struct SDL_Hint
33{
34 char *value;
35 SDL_HintPriority priority;
36 SDL_HintWatch *callbacks;
37} SDL_Hint;
38
39static SDL_AtomicU32 SDL_hint_props;
40
41
42void SDL_InitHints(void)
43{
44}
45
46void SDL_QuitHints(void)
47{
48 SDL_PropertiesID props;
49 do {
50 props = SDL_GetAtomicU32(&SDL_hint_props);
51 } while (!SDL_CompareAndSwapAtomicU32(&SDL_hint_props, props, 0));
52
53 if (props) {
54 SDL_DestroyProperties(props);
55 }
56}
57
58static SDL_PropertiesID GetHintProperties(bool create)
59{
60 SDL_PropertiesID props = SDL_GetAtomicU32(&SDL_hint_props);
61 if (!props && create) {
62 props = SDL_CreateProperties();
63 if (!SDL_CompareAndSwapAtomicU32(&SDL_hint_props, 0, props)) {
64 // Somebody else created hint properties before us, just use those
65 SDL_DestroyProperties(props);
66 props = SDL_GetAtomicU32(&SDL_hint_props);
67 }
68 }
69 return props;
70}
71
72static void SDLCALL CleanupHintProperty(void *userdata, void *value)
73{
74 SDL_Hint *hint = (SDL_Hint *) value;
75 SDL_free(hint->value);
76
77 SDL_HintWatch *entry = hint->callbacks;
78 while (entry) {
79 SDL_HintWatch *freeable = entry;
80 entry = entry->next;
81 SDL_free(freeable);
82 }
83 SDL_free(hint);
84}
85
86static const char* GetHintEnvironmentVariable(const char *name)
87{
88 const char *result = SDL_getenv(name);
89 if (!result && name && *name) {
90 // fall back to old (SDL2) names of environment variables that
91 // are important to users (e.g. many use SDL_VIDEODRIVER=wayland)
92 if (SDL_strcmp(name, SDL_HINT_VIDEO_DRIVER) == 0) {
93 result = SDL_getenv("SDL_VIDEODRIVER");
94 } else if (SDL_strcmp(name, SDL_HINT_AUDIO_DRIVER) == 0) {
95 result = SDL_getenv("SDL_AUDIODRIVER");
96 }
97 }
98 return result;
99}
100
101bool SDL_SetHintWithPriority(const char *name, const char *value, SDL_HintPriority priority)
102{
103 if (!name || !*name) {
104 return SDL_InvalidParamError("name");
105 }
106
107 const char *env = GetHintEnvironmentVariable(name);
108 if (env && (priority < SDL_HINT_OVERRIDE)) {
109 return SDL_SetError("An environment variable is taking priority");
110 }
111
112 const SDL_PropertiesID hints = GetHintProperties(true);
113 if (!hints) {
114 return false;
115 }
116
117 bool result = false;
118
119 SDL_LockProperties(hints);
120
121 SDL_Hint *hint = (SDL_Hint *)SDL_GetPointerProperty(hints, name, NULL);
122 if (hint) {
123 if (priority >= hint->priority) {
124 if (hint->value != value && (!value || !hint->value || SDL_strcmp(hint->value, value) != 0)) {
125 char *old_value = hint->value;
126
127 hint->value = value ? SDL_strdup(value) : NULL;
128 SDL_HintWatch *entry = hint->callbacks;
129 while (entry) {
130 // Save the next entry in case this one is deleted
131 SDL_HintWatch *next = entry->next;
132 entry->callback(entry->userdata, name, old_value, value);
133 entry = next;
134 }
135 SDL_free(old_value);
136 }
137 hint->priority = priority;
138 result = true;
139 }
140 } else { // Couldn't find the hint? Add a new one.
141 hint = (SDL_Hint *)SDL_malloc(sizeof(*hint));
142 if (hint) {
143 hint->value = value ? SDL_strdup(value) : NULL;
144 hint->priority = priority;
145 hint->callbacks = NULL;
146 result = SDL_SetPointerPropertyWithCleanup(hints, name, hint, CleanupHintProperty, NULL);
147 }
148 }
149
150 SDL_UnlockProperties(hints);
151
152 return result;
153}
154
155bool SDL_ResetHint(const char *name)
156{
157 if (!name || !*name) {
158 return SDL_InvalidParamError("name");
159 }
160
161 const char *env = GetHintEnvironmentVariable(name);
162
163 const SDL_PropertiesID hints = GetHintProperties(false);
164 if (!hints) {
165 return false;
166 }
167
168 bool result = false;
169
170 SDL_LockProperties(hints);
171
172 SDL_Hint *hint = (SDL_Hint *)SDL_GetPointerProperty(hints, name, NULL);
173 if (hint) {
174 if ((!env && hint->value) || (env && !hint->value) || (env && SDL_strcmp(env, hint->value) != 0)) {
175 for (SDL_HintWatch *entry = hint->callbacks; entry;) {
176 // Save the next entry in case this one is deleted
177 SDL_HintWatch *next = entry->next;
178 entry->callback(entry->userdata, name, hint->value, env);
179 entry = next;
180 }
181 }
182 SDL_free(hint->value);
183 hint->value = NULL;
184 hint->priority = SDL_HINT_DEFAULT;
185 result = true;
186 }
187
188 SDL_UnlockProperties(hints);
189
190 return result;
191}
192
193static void SDLCALL ResetHintsCallback(void *userdata, SDL_PropertiesID hints, const char *name)
194{
195 SDL_Hint *hint = (SDL_Hint *)SDL_GetPointerProperty(hints, name, NULL);
196 if (!hint) {
197 return; // uh...okay.
198 }
199
200 const char *env = GetHintEnvironmentVariable(name);
201 if ((!env && hint->value) || (env && !hint->value) || (env && SDL_strcmp(env, hint->value) != 0)) {
202 SDL_HintWatch *entry = hint->callbacks;
203 while (entry) {
204 // Save the next entry in case this one is deleted
205 SDL_HintWatch *next = entry->next;
206 entry->callback(entry->userdata, name, hint->value, env);
207 entry = next;
208 }
209 }
210 SDL_free(hint->value);
211 hint->value = NULL;
212 hint->priority = SDL_HINT_DEFAULT;
213}
214
215void SDL_ResetHints(void)
216{
217 SDL_EnumerateProperties(GetHintProperties(false), ResetHintsCallback, NULL);
218}
219
220bool SDL_SetHint(const char *name, const char *value)
221{
222 return SDL_SetHintWithPriority(name, value, SDL_HINT_NORMAL);
223}
224
225const char *SDL_GetHint(const char *name)
226{
227 if (!name) {
228 return NULL;
229 }
230
231 const char *result = GetHintEnvironmentVariable(name);
232
233 const SDL_PropertiesID hints = GetHintProperties(false);
234 if (hints) {
235 SDL_LockProperties(hints);
236
237 SDL_Hint *hint = (SDL_Hint *)SDL_GetPointerProperty(hints, name, NULL);
238 if (hint) {
239 if (!result || hint->priority == SDL_HINT_OVERRIDE) {
240 result = SDL_GetPersistentString(hint->value);
241 }
242 }
243
244 SDL_UnlockProperties(hints);
245 }
246
247 return result;
248}
249
250int SDL_GetStringInteger(const char *value, int default_value)
251{
252 if (!value || !*value) {
253 return default_value;
254 }
255 if (SDL_strcasecmp(value, "false") == 0) {
256 return 0;
257 }
258 if (SDL_strcasecmp(value, "true") == 0) {
259 return 1;
260 }
261 if (*value == '-' || SDL_isdigit(*value)) {
262 return SDL_atoi(value);
263 }
264 return default_value;
265}
266
267bool SDL_GetStringBoolean(const char *value, bool default_value)
268{
269 if (!value || !*value) {
270 return default_value;
271 }
272 if (*value == '0' || SDL_strcasecmp(value, "false") == 0) {
273 return false;
274 }
275 return true;
276}
277
278bool SDL_GetHintBoolean(const char *name, bool default_value)
279{
280 const char *hint = SDL_GetHint(name);
281 return SDL_GetStringBoolean(hint, default_value);
282}
283
284bool SDL_AddHintCallback(const char *name, SDL_HintCallback callback, void *userdata)
285{
286 if (!name || !*name) {
287 return SDL_InvalidParamError("name");
288 } else if (!callback) {
289 return SDL_InvalidParamError("callback");
290 }
291
292 const SDL_PropertiesID hints = GetHintProperties(true);
293 if (!hints) {
294 return false;
295 }
296
297 SDL_HintWatch *entry = (SDL_HintWatch *)SDL_malloc(sizeof(*entry));
298 if (!entry) {
299 return false;
300 }
301 entry->callback = callback;
302 entry->userdata = userdata;
303
304 bool result = false;
305
306 SDL_LockProperties(hints);
307
308 SDL_RemoveHintCallback(name, callback, userdata);
309
310 SDL_Hint *hint = (SDL_Hint *)SDL_GetPointerProperty(hints, name, NULL);
311 if (hint) {
312 result = true;
313 } else { // Need to add a hint entry for this watcher
314 hint = (SDL_Hint *)SDL_malloc(sizeof(*hint));
315 if (!hint) {
316 SDL_free(entry);
317 SDL_UnlockProperties(hints);
318 return false;
319 } else {
320 hint->value = NULL;
321 hint->priority = SDL_HINT_DEFAULT;
322 hint->callbacks = NULL;
323 result = SDL_SetPointerPropertyWithCleanup(hints, name, hint, CleanupHintProperty, NULL);
324 }
325 }
326
327 // Add it to the callbacks for this hint
328 entry->next = hint->callbacks;
329 hint->callbacks = entry;
330
331 // Now call it with the current value
332 const char *value = SDL_GetHint(name);
333 callback(userdata, name, value, value);
334
335 SDL_UnlockProperties(hints);
336
337 return result;
338}
339
340void SDL_RemoveHintCallback(const char *name, SDL_HintCallback callback, void *userdata)
341{
342 if (!name || !*name) {
343 return;
344 }
345
346 const SDL_PropertiesID hints = GetHintProperties(false);
347 if (!hints) {
348 return;
349 }
350
351 SDL_LockProperties(hints);
352 SDL_Hint *hint = (SDL_Hint *)SDL_GetPointerProperty(hints, name, NULL);
353 if (hint) {
354 SDL_HintWatch *prev = NULL;
355 for (SDL_HintWatch *entry = hint->callbacks; entry; entry = entry->next) {
356 if ((callback == entry->callback) && (userdata == entry->userdata)) {
357 if (prev) {
358 prev->next = entry->next;
359 } else {
360 hint->callbacks = entry->next;
361 }
362 SDL_free(entry);
363 break;
364 }
365 prev = entry;
366 }
367 }
368 SDL_UnlockProperties(hints);
369}
370
371