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 | |
25 | typedef struct SDL_HintWatch |
26 | { |
27 | SDL_HintCallback callback; |
28 | void *userdata; |
29 | struct SDL_HintWatch *next; |
30 | } SDL_HintWatch; |
31 | |
32 | typedef struct SDL_Hint |
33 | { |
34 | char *value; |
35 | SDL_HintPriority priority; |
36 | SDL_HintWatch *callbacks; |
37 | } SDL_Hint; |
38 | |
39 | static SDL_AtomicU32 SDL_hint_props; |
40 | |
41 | |
42 | void SDL_InitHints(void) |
43 | { |
44 | } |
45 | |
46 | void 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 | |
58 | static 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 | |
72 | static 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 | |
86 | static 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 | |
101 | bool 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 | |
155 | bool 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 | |
193 | static 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 | |
215 | void SDL_ResetHints(void) |
216 | { |
217 | SDL_EnumerateProperties(GetHintProperties(false), ResetHintsCallback, NULL); |
218 | } |
219 | |
220 | bool SDL_SetHint(const char *name, const char *value) |
221 | { |
222 | return SDL_SetHintWithPriority(name, value, SDL_HINT_NORMAL); |
223 | } |
224 | |
225 | const 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 | |
250 | int 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 | |
267 | bool 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 | |
278 | bool 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 | |
284 | bool 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 | |
340 | void 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 | |