Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/thirdparty/sdl/core/windows/SDL_windows.c
21724 views
1
/*
2
Simple DirectMedia Layer
3
Copyright (C) 1997-2025 Sam Lantinga <[email protected]>
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
#if defined(SDL_PLATFORM_WINDOWS)
24
25
#include "SDL_windows.h"
26
27
#include <objbase.h> // for CoInitialize/CoUninitialize (Win32 only)
28
#ifdef HAVE_ROAPI_H
29
#include <roapi.h> // For RoInitialize/RoUninitialize (Win32 only)
30
#else
31
typedef enum RO_INIT_TYPE
32
{
33
RO_INIT_SINGLETHREADED = 0,
34
RO_INIT_MULTITHREADED = 1
35
} RO_INIT_TYPE;
36
#endif
37
38
#ifndef _WIN32_WINNT_VISTA
39
#define _WIN32_WINNT_VISTA 0x0600
40
#endif
41
#ifndef _WIN32_WINNT_WIN7
42
#define _WIN32_WINNT_WIN7 0x0601
43
#endif
44
#ifndef _WIN32_WINNT_WIN8
45
#define _WIN32_WINNT_WIN8 0x0602
46
#endif
47
48
#ifndef LOAD_LIBRARY_SEARCH_SYSTEM32
49
#define LOAD_LIBRARY_SEARCH_SYSTEM32 0x00000800
50
#endif
51
52
#ifndef WC_ERR_INVALID_CHARS
53
#define WC_ERR_INVALID_CHARS 0x00000080
54
#endif
55
56
// Fake window to help with DirectInput events.
57
HWND SDL_HelperWindow = NULL;
58
static const TCHAR *SDL_HelperWindowClassName = TEXT("SDLHelperWindowInputCatcher");
59
static const TCHAR *SDL_HelperWindowName = TEXT("SDLHelperWindowInputMsgWindow");
60
static ATOM SDL_HelperWindowClass = 0;
61
62
/*
63
* Creates a HelperWindow used for DirectInput.
64
*/
65
bool SDL_HelperWindowCreate(void)
66
{
67
HINSTANCE hInstance = GetModuleHandle(NULL);
68
WNDCLASS wce;
69
70
// Make sure window isn't created twice.
71
if (SDL_HelperWindow != NULL) {
72
return true;
73
}
74
75
// Create the class.
76
SDL_zero(wce);
77
wce.lpfnWndProc = DefWindowProc;
78
wce.lpszClassName = SDL_HelperWindowClassName;
79
wce.hInstance = hInstance;
80
81
// Register the class.
82
SDL_HelperWindowClass = RegisterClass(&wce);
83
if (SDL_HelperWindowClass == 0 && GetLastError() != ERROR_CLASS_ALREADY_EXISTS) {
84
return WIN_SetError("Unable to create Helper Window Class");
85
}
86
87
// Create the window.
88
SDL_HelperWindow = CreateWindowEx(0, SDL_HelperWindowClassName,
89
SDL_HelperWindowName,
90
WS_OVERLAPPED, CW_USEDEFAULT,
91
CW_USEDEFAULT, CW_USEDEFAULT,
92
CW_USEDEFAULT, HWND_MESSAGE, NULL,
93
hInstance, NULL);
94
if (!SDL_HelperWindow) {
95
UnregisterClass(SDL_HelperWindowClassName, hInstance);
96
return WIN_SetError("Unable to create Helper Window");
97
}
98
99
return true;
100
}
101
102
/*
103
* Destroys the HelperWindow previously created with SDL_HelperWindowCreate.
104
*/
105
void SDL_HelperWindowDestroy(void)
106
{
107
HINSTANCE hInstance = GetModuleHandle(NULL);
108
109
// Destroy the window.
110
if (SDL_HelperWindow != NULL) {
111
if (DestroyWindow(SDL_HelperWindow) == 0) {
112
WIN_SetError("Unable to destroy Helper Window");
113
return;
114
}
115
SDL_HelperWindow = NULL;
116
}
117
118
// Unregister the class.
119
if (SDL_HelperWindowClass != 0) {
120
if ((UnregisterClass(SDL_HelperWindowClassName, hInstance)) == 0) {
121
WIN_SetError("Unable to destroy Helper Window Class");
122
return;
123
}
124
SDL_HelperWindowClass = 0;
125
}
126
}
127
128
// Sets an error message based on an HRESULT
129
bool WIN_SetErrorFromHRESULT(const char *prefix, HRESULT hr)
130
{
131
TCHAR buffer[1024];
132
char *message;
133
TCHAR *p = buffer;
134
DWORD c = FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, hr, 0,
135
buffer, SDL_arraysize(buffer), NULL);
136
buffer[c] = 0;
137
// kill CR/LF that FormatMessage() sticks at the end
138
while (*p) {
139
if (*p == '\r') {
140
*p = 0;
141
break;
142
}
143
++p;
144
}
145
message = WIN_StringToUTF8(buffer);
146
SDL_SetError("%s%s%s", prefix ? prefix : "", prefix ? ": " : "", message);
147
SDL_free(message);
148
return false;
149
}
150
151
// Sets an error message based on GetLastError()
152
bool WIN_SetError(const char *prefix)
153
{
154
return WIN_SetErrorFromHRESULT(prefix, GetLastError());
155
}
156
157
HRESULT
158
WIN_CoInitialize(void)
159
{
160
/* SDL handles any threading model, so initialize with the default, which
161
is compatible with OLE and if that doesn't work, try multi-threaded mode.
162
163
If you need multi-threaded mode, call CoInitializeEx() before SDL_Init()
164
*/
165
#if defined(SDL_PLATFORM_XBOXONE) || defined(SDL_PLATFORM_XBOXSERIES)
166
// On Xbox, there's no need to call CoInitializeEx (and it's not implemented)
167
return S_OK;
168
#else
169
HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
170
if (hr == RPC_E_CHANGED_MODE) {
171
hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
172
}
173
174
// S_FALSE means success, but someone else already initialized.
175
// You still need to call CoUninitialize in this case!
176
if (hr == S_FALSE) {
177
return S_OK;
178
}
179
180
return hr;
181
#endif
182
}
183
184
void WIN_CoUninitialize(void)
185
{
186
CoUninitialize();
187
}
188
189
FARPROC WIN_LoadComBaseFunction(const char *name)
190
{
191
static bool s_bLoaded;
192
static HMODULE s_hComBase;
193
194
if (!s_bLoaded) {
195
s_hComBase = LoadLibraryEx(TEXT("combase.dll"), NULL, LOAD_LIBRARY_SEARCH_SYSTEM32);
196
s_bLoaded = true;
197
}
198
if (s_hComBase) {
199
return GetProcAddress(s_hComBase, name);
200
} else {
201
return NULL;
202
}
203
}
204
205
HRESULT
206
WIN_RoInitialize(void)
207
{
208
typedef HRESULT(WINAPI * RoInitialize_t)(RO_INIT_TYPE initType);
209
RoInitialize_t RoInitializeFunc = (RoInitialize_t)WIN_LoadComBaseFunction("RoInitialize");
210
if (RoInitializeFunc) {
211
// RO_INIT_SINGLETHREADED is equivalent to COINIT_APARTMENTTHREADED
212
HRESULT hr = RoInitializeFunc(RO_INIT_SINGLETHREADED);
213
if (hr == RPC_E_CHANGED_MODE) {
214
hr = RoInitializeFunc(RO_INIT_MULTITHREADED);
215
}
216
217
// S_FALSE means success, but someone else already initialized.
218
// You still need to call RoUninitialize in this case!
219
if (hr == S_FALSE) {
220
return S_OK;
221
}
222
223
return hr;
224
} else {
225
return E_NOINTERFACE;
226
}
227
}
228
229
void WIN_RoUninitialize(void)
230
{
231
typedef void(WINAPI * RoUninitialize_t)(void);
232
RoUninitialize_t RoUninitializeFunc = (RoUninitialize_t)WIN_LoadComBaseFunction("RoUninitialize");
233
if (RoUninitializeFunc) {
234
RoUninitializeFunc();
235
}
236
}
237
238
#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
239
static BOOL IsWindowsVersionOrGreater(WORD wMajorVersion, WORD wMinorVersion, WORD wServicePackMajor)
240
{
241
OSVERSIONINFOEXW osvi;
242
DWORDLONG const dwlConditionMask = VerSetConditionMask(
243
VerSetConditionMask(
244
VerSetConditionMask(
245
0, VER_MAJORVERSION, VER_GREATER_EQUAL),
246
VER_MINORVERSION, VER_GREATER_EQUAL),
247
VER_SERVICEPACKMAJOR, VER_GREATER_EQUAL);
248
249
SDL_zero(osvi);
250
osvi.dwOSVersionInfoSize = sizeof(osvi);
251
osvi.dwMajorVersion = wMajorVersion;
252
osvi.dwMinorVersion = wMinorVersion;
253
osvi.wServicePackMajor = wServicePackMajor;
254
255
return VerifyVersionInfoW(&osvi, VER_MAJORVERSION | VER_MINORVERSION | VER_SERVICEPACKMAJOR, dwlConditionMask) != FALSE;
256
}
257
#endif
258
259
// apply some static variables so we only call into the Win32 API once per process for each check.
260
#if defined(SDL_PLATFORM_XBOXONE) || defined(SDL_PLATFORM_XBOXSERIES)
261
#define CHECKWINVER(notdesktop_platform_result, test) return (notdesktop_platform_result);
262
#else
263
#define CHECKWINVER(notdesktop_platform_result, test) \
264
static bool checked = false; \
265
static BOOL result = FALSE; \
266
if (!checked) { \
267
result = (test); \
268
checked = true; \
269
} \
270
return result;
271
#endif
272
273
BOOL WIN_IsWine(void)
274
{
275
static bool checked;
276
static bool is_wine;
277
278
if (!checked) {
279
HMODULE ntdll = LoadLibrary(TEXT("ntdll.dll"));
280
if (ntdll) {
281
if (GetProcAddress(ntdll, "wine_get_version") != NULL) {
282
is_wine = true;
283
}
284
FreeLibrary(ntdll);
285
}
286
checked = true;
287
}
288
return is_wine;
289
}
290
291
// this is the oldest thing we run on (and we may lose support for this in SDL3 at any time!),
292
// so there's no "OrGreater" as that would always be TRUE. The other functions are here to
293
// ask "can we support a specific feature?" but this function is here to ask "do we need to do
294
// something different for an OS version we probably should abandon?" :)
295
BOOL WIN_IsWindowsXP(void)
296
{
297
CHECKWINVER(FALSE, !WIN_IsWindowsVistaOrGreater() && IsWindowsVersionOrGreater(HIBYTE(_WIN32_WINNT_WINXP), LOBYTE(_WIN32_WINNT_WINXP), 0));
298
}
299
300
BOOL WIN_IsWindowsVistaOrGreater(void)
301
{
302
CHECKWINVER(TRUE, IsWindowsVersionOrGreater(HIBYTE(_WIN32_WINNT_VISTA), LOBYTE(_WIN32_WINNT_VISTA), 0));
303
}
304
305
BOOL WIN_IsWindows7OrGreater(void)
306
{
307
CHECKWINVER(TRUE, IsWindowsVersionOrGreater(HIBYTE(_WIN32_WINNT_WIN7), LOBYTE(_WIN32_WINNT_WIN7), 0));
308
}
309
310
BOOL WIN_IsWindows8OrGreater(void)
311
{
312
CHECKWINVER(TRUE, IsWindowsVersionOrGreater(HIBYTE(_WIN32_WINNT_WIN8), LOBYTE(_WIN32_WINNT_WIN8), 0));
313
}
314
315
#undef CHECKWINVER
316
317
318
/*
319
WAVExxxCAPS gives you 31 bytes for the device name, and just truncates if it's
320
longer. However, since WinXP, you can use the WAVExxxCAPS2 structure, which
321
will give you a name GUID. The full name is in the Windows Registry under
322
that GUID, located here: HKLM\System\CurrentControlSet\Control\MediaCategories
323
324
Note that drivers can report GUID_NULL for the name GUID, in which case,
325
Windows makes a best effort to fill in those 31 bytes in the usual place.
326
This info summarized from MSDN:
327
328
http://web.archive.org/web/20131027093034/http://msdn.microsoft.com/en-us/library/windows/hardware/ff536382(v=vs.85).aspx
329
330
Always look this up in the registry if possible, because the strings are
331
different! At least on Win10, I see "Yeti Stereo Microphone" in the
332
Registry, and a unhelpful "Microphone(Yeti Stereo Microph" in winmm. Sigh.
333
334
(Also, DirectSound shouldn't be limited to 32 chars, but its device enum
335
has the same problem.)
336
337
WASAPI doesn't need this. This is just for DirectSound/WinMM.
338
*/
339
char *WIN_LookupAudioDeviceName(const WCHAR *name, const GUID *guid)
340
{
341
#if defined(SDL_PLATFORM_XBOXONE) || defined(SDL_PLATFORM_XBOXSERIES)
342
return WIN_StringToUTF8W(name); // No registry access on Xbox, go with what we've got.
343
#else
344
static const GUID nullguid = { 0 };
345
const unsigned char *ptr;
346
char keystr[128];
347
WCHAR *strw = NULL;
348
bool rc;
349
HKEY hkey;
350
DWORD len = 0;
351
char *result = NULL;
352
353
if (WIN_IsEqualGUID(guid, &nullguid)) {
354
return WIN_StringToUTF8(name); // No GUID, go with what we've got.
355
}
356
357
ptr = (const unsigned char *)guid;
358
(void)SDL_snprintf(keystr, sizeof(keystr),
359
"System\\CurrentControlSet\\Control\\MediaCategories\\{%02X%02X%02X%02X-%02X%02X-%02X%02X-%02X%02X-%02X%02X%02X%02X%02X%02X}",
360
ptr[3], ptr[2], ptr[1], ptr[0], ptr[5], ptr[4], ptr[7], ptr[6],
361
ptr[8], ptr[9], ptr[10], ptr[11], ptr[12], ptr[13], ptr[14], ptr[15]);
362
363
strw = WIN_UTF8ToString(keystr);
364
rc = (RegOpenKeyExW(HKEY_LOCAL_MACHINE, strw, 0, KEY_QUERY_VALUE, &hkey) == ERROR_SUCCESS);
365
SDL_free(strw);
366
if (!rc) {
367
return WIN_StringToUTF8(name); // oh well.
368
}
369
370
rc = (RegQueryValueExW(hkey, L"Name", NULL, NULL, NULL, &len) == ERROR_SUCCESS);
371
if (!rc) {
372
RegCloseKey(hkey);
373
return WIN_StringToUTF8(name); // oh well.
374
}
375
376
strw = (WCHAR *)SDL_malloc(len + sizeof(WCHAR));
377
if (!strw) {
378
RegCloseKey(hkey);
379
return WIN_StringToUTF8(name); // oh well.
380
}
381
382
rc = (RegQueryValueExW(hkey, L"Name", NULL, NULL, (LPBYTE)strw, &len) == ERROR_SUCCESS);
383
RegCloseKey(hkey);
384
if (!rc) {
385
SDL_free(strw);
386
return WIN_StringToUTF8(name); // oh well.
387
}
388
389
strw[len / 2] = 0; // make sure it's null-terminated.
390
391
result = WIN_StringToUTF8(strw);
392
SDL_free(strw);
393
return result ? result : WIN_StringToUTF8(name);
394
#endif
395
}
396
397
BOOL WIN_IsEqualGUID(const GUID *a, const GUID *b)
398
{
399
return (SDL_memcmp(a, b, sizeof(*a)) == 0);
400
}
401
402
BOOL WIN_IsEqualIID(REFIID a, REFIID b)
403
{
404
return (SDL_memcmp(a, b, sizeof(*a)) == 0);
405
}
406
407
void WIN_RECTToRect(const RECT *winrect, SDL_Rect *sdlrect)
408
{
409
sdlrect->x = winrect->left;
410
sdlrect->w = (winrect->right - winrect->left) + 1;
411
sdlrect->y = winrect->top;
412
sdlrect->h = (winrect->bottom - winrect->top) + 1;
413
}
414
415
void WIN_RectToRECT(const SDL_Rect *sdlrect, RECT *winrect)
416
{
417
winrect->left = sdlrect->x;
418
winrect->right = sdlrect->x + sdlrect->w - 1;
419
winrect->top = sdlrect->y;
420
winrect->bottom = sdlrect->y + sdlrect->h - 1;
421
}
422
423
bool WIN_WindowRectValid(const RECT *rect)
424
{
425
// A window can be resized to zero height, but not zero width
426
return (rect->right > 0);
427
}
428
429
// Some GUIDs we need to know without linking to libraries that aren't available before Vista.
430
/* *INDENT-OFF* */ // clang-format off
431
static const GUID SDL_KSDATAFORMAT_SUBTYPE_PCM = { 0x00000001, 0x0000, 0x0010,{ 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 } };
432
static const GUID SDL_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT = { 0x00000003, 0x0000, 0x0010,{ 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 } };
433
/* *INDENT-ON* */ // clang-format on
434
435
SDL_AudioFormat SDL_WaveFormatExToSDLFormat(WAVEFORMATEX *waveformat)
436
{
437
if ((waveformat->wFormatTag == WAVE_FORMAT_IEEE_FLOAT) && (waveformat->wBitsPerSample == 32)) {
438
return SDL_AUDIO_F32;
439
} else if ((waveformat->wFormatTag == WAVE_FORMAT_PCM) && (waveformat->wBitsPerSample == 16)) {
440
return SDL_AUDIO_S16;
441
} else if ((waveformat->wFormatTag == WAVE_FORMAT_PCM) && (waveformat->wBitsPerSample == 32)) {
442
return SDL_AUDIO_S32;
443
} else if (waveformat->wFormatTag == WAVE_FORMAT_EXTENSIBLE) {
444
const WAVEFORMATEXTENSIBLE *ext = (const WAVEFORMATEXTENSIBLE *)waveformat;
445
if ((SDL_memcmp(&ext->SubFormat, &SDL_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, sizeof(GUID)) == 0) && (waveformat->wBitsPerSample == 32)) {
446
return SDL_AUDIO_F32;
447
} else if ((SDL_memcmp(&ext->SubFormat, &SDL_KSDATAFORMAT_SUBTYPE_PCM, sizeof(GUID)) == 0) && (waveformat->wBitsPerSample == 16)) {
448
return SDL_AUDIO_S16;
449
} else if ((SDL_memcmp(&ext->SubFormat, &SDL_KSDATAFORMAT_SUBTYPE_PCM, sizeof(GUID)) == 0) && (waveformat->wBitsPerSample == 32)) {
450
return SDL_AUDIO_S32;
451
}
452
}
453
return SDL_AUDIO_UNKNOWN;
454
}
455
456
457
int WIN_WideCharToMultiByte(UINT CodePage, DWORD dwFlags, LPCWCH lpWideCharStr, int cchWideChar, LPSTR lpMultiByteStr, int cbMultiByte, LPCCH lpDefaultChar, LPBOOL lpUsedDefaultChar)
458
{
459
if (WIN_IsWindowsXP()) {
460
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.
461
}
462
return WideCharToMultiByte(CodePage, dwFlags, lpWideCharStr, cchWideChar, lpMultiByteStr, cbMultiByte, lpDefaultChar, lpUsedDefaultChar);
463
}
464
465
#endif // defined(SDL_PLATFORM_WINDOWS)
466
467