Path: blob/master/thirdparty/sdl/core/windows/SDL_windows.c
21724 views
/*1Simple DirectMedia Layer2Copyright (C) 1997-2025 Sam Lantinga <[email protected]>34This software is provided 'as-is', without any express or implied5warranty. In no event will the authors be held liable for any damages6arising from the use of this software.78Permission is granted to anyone to use this software for any purpose,9including commercial applications, and to alter it and redistribute it10freely, subject to the following restrictions:11121. The origin of this software must not be misrepresented; you must not13claim that you wrote the original software. If you use this software14in a product, an acknowledgment in the product documentation would be15appreciated but is not required.162. Altered source versions must be plainly marked as such, and must not be17misrepresented as being the original software.183. This notice may not be removed or altered from any source distribution.19*/20#include "SDL_internal.h"2122#if defined(SDL_PLATFORM_WINDOWS)2324#include "SDL_windows.h"2526#include <objbase.h> // for CoInitialize/CoUninitialize (Win32 only)27#ifdef HAVE_ROAPI_H28#include <roapi.h> // For RoInitialize/RoUninitialize (Win32 only)29#else30typedef enum RO_INIT_TYPE31{32RO_INIT_SINGLETHREADED = 0,33RO_INIT_MULTITHREADED = 134} RO_INIT_TYPE;35#endif3637#ifndef _WIN32_WINNT_VISTA38#define _WIN32_WINNT_VISTA 0x060039#endif40#ifndef _WIN32_WINNT_WIN741#define _WIN32_WINNT_WIN7 0x060142#endif43#ifndef _WIN32_WINNT_WIN844#define _WIN32_WINNT_WIN8 0x060245#endif4647#ifndef LOAD_LIBRARY_SEARCH_SYSTEM3248#define LOAD_LIBRARY_SEARCH_SYSTEM32 0x0000080049#endif5051#ifndef WC_ERR_INVALID_CHARS52#define WC_ERR_INVALID_CHARS 0x0000008053#endif5455// Fake window to help with DirectInput events.56HWND SDL_HelperWindow = NULL;57static const TCHAR *SDL_HelperWindowClassName = TEXT("SDLHelperWindowInputCatcher");58static const TCHAR *SDL_HelperWindowName = TEXT("SDLHelperWindowInputMsgWindow");59static ATOM SDL_HelperWindowClass = 0;6061/*62* Creates a HelperWindow used for DirectInput.63*/64bool SDL_HelperWindowCreate(void)65{66HINSTANCE hInstance = GetModuleHandle(NULL);67WNDCLASS wce;6869// Make sure window isn't created twice.70if (SDL_HelperWindow != NULL) {71return true;72}7374// Create the class.75SDL_zero(wce);76wce.lpfnWndProc = DefWindowProc;77wce.lpszClassName = SDL_HelperWindowClassName;78wce.hInstance = hInstance;7980// Register the class.81SDL_HelperWindowClass = RegisterClass(&wce);82if (SDL_HelperWindowClass == 0 && GetLastError() != ERROR_CLASS_ALREADY_EXISTS) {83return WIN_SetError("Unable to create Helper Window Class");84}8586// Create the window.87SDL_HelperWindow = CreateWindowEx(0, SDL_HelperWindowClassName,88SDL_HelperWindowName,89WS_OVERLAPPED, CW_USEDEFAULT,90CW_USEDEFAULT, CW_USEDEFAULT,91CW_USEDEFAULT, HWND_MESSAGE, NULL,92hInstance, NULL);93if (!SDL_HelperWindow) {94UnregisterClass(SDL_HelperWindowClassName, hInstance);95return WIN_SetError("Unable to create Helper Window");96}9798return true;99}100101/*102* Destroys the HelperWindow previously created with SDL_HelperWindowCreate.103*/104void SDL_HelperWindowDestroy(void)105{106HINSTANCE hInstance = GetModuleHandle(NULL);107108// Destroy the window.109if (SDL_HelperWindow != NULL) {110if (DestroyWindow(SDL_HelperWindow) == 0) {111WIN_SetError("Unable to destroy Helper Window");112return;113}114SDL_HelperWindow = NULL;115}116117// Unregister the class.118if (SDL_HelperWindowClass != 0) {119if ((UnregisterClass(SDL_HelperWindowClassName, hInstance)) == 0) {120WIN_SetError("Unable to destroy Helper Window Class");121return;122}123SDL_HelperWindowClass = 0;124}125}126127// Sets an error message based on an HRESULT128bool WIN_SetErrorFromHRESULT(const char *prefix, HRESULT hr)129{130TCHAR buffer[1024];131char *message;132TCHAR *p = buffer;133DWORD c = FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, hr, 0,134buffer, SDL_arraysize(buffer), NULL);135buffer[c] = 0;136// kill CR/LF that FormatMessage() sticks at the end137while (*p) {138if (*p == '\r') {139*p = 0;140break;141}142++p;143}144message = WIN_StringToUTF8(buffer);145SDL_SetError("%s%s%s", prefix ? prefix : "", prefix ? ": " : "", message);146SDL_free(message);147return false;148}149150// Sets an error message based on GetLastError()151bool WIN_SetError(const char *prefix)152{153return WIN_SetErrorFromHRESULT(prefix, GetLastError());154}155156HRESULT157WIN_CoInitialize(void)158{159/* SDL handles any threading model, so initialize with the default, which160is compatible with OLE and if that doesn't work, try multi-threaded mode.161162If you need multi-threaded mode, call CoInitializeEx() before SDL_Init()163*/164#if defined(SDL_PLATFORM_XBOXONE) || defined(SDL_PLATFORM_XBOXSERIES)165// On Xbox, there's no need to call CoInitializeEx (and it's not implemented)166return S_OK;167#else168HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);169if (hr == RPC_E_CHANGED_MODE) {170hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);171}172173// S_FALSE means success, but someone else already initialized.174// You still need to call CoUninitialize in this case!175if (hr == S_FALSE) {176return S_OK;177}178179return hr;180#endif181}182183void WIN_CoUninitialize(void)184{185CoUninitialize();186}187188FARPROC WIN_LoadComBaseFunction(const char *name)189{190static bool s_bLoaded;191static HMODULE s_hComBase;192193if (!s_bLoaded) {194s_hComBase = LoadLibraryEx(TEXT("combase.dll"), NULL, LOAD_LIBRARY_SEARCH_SYSTEM32);195s_bLoaded = true;196}197if (s_hComBase) {198return GetProcAddress(s_hComBase, name);199} else {200return NULL;201}202}203204HRESULT205WIN_RoInitialize(void)206{207typedef HRESULT(WINAPI * RoInitialize_t)(RO_INIT_TYPE initType);208RoInitialize_t RoInitializeFunc = (RoInitialize_t)WIN_LoadComBaseFunction("RoInitialize");209if (RoInitializeFunc) {210// RO_INIT_SINGLETHREADED is equivalent to COINIT_APARTMENTTHREADED211HRESULT hr = RoInitializeFunc(RO_INIT_SINGLETHREADED);212if (hr == RPC_E_CHANGED_MODE) {213hr = RoInitializeFunc(RO_INIT_MULTITHREADED);214}215216// S_FALSE means success, but someone else already initialized.217// You still need to call RoUninitialize in this case!218if (hr == S_FALSE) {219return S_OK;220}221222return hr;223} else {224return E_NOINTERFACE;225}226}227228void WIN_RoUninitialize(void)229{230typedef void(WINAPI * RoUninitialize_t)(void);231RoUninitialize_t RoUninitializeFunc = (RoUninitialize_t)WIN_LoadComBaseFunction("RoUninitialize");232if (RoUninitializeFunc) {233RoUninitializeFunc();234}235}236237#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)238static BOOL IsWindowsVersionOrGreater(WORD wMajorVersion, WORD wMinorVersion, WORD wServicePackMajor)239{240OSVERSIONINFOEXW osvi;241DWORDLONG const dwlConditionMask = VerSetConditionMask(242VerSetConditionMask(243VerSetConditionMask(2440, VER_MAJORVERSION, VER_GREATER_EQUAL),245VER_MINORVERSION, VER_GREATER_EQUAL),246VER_SERVICEPACKMAJOR, VER_GREATER_EQUAL);247248SDL_zero(osvi);249osvi.dwOSVersionInfoSize = sizeof(osvi);250osvi.dwMajorVersion = wMajorVersion;251osvi.dwMinorVersion = wMinorVersion;252osvi.wServicePackMajor = wServicePackMajor;253254return VerifyVersionInfoW(&osvi, VER_MAJORVERSION | VER_MINORVERSION | VER_SERVICEPACKMAJOR, dwlConditionMask) != FALSE;255}256#endif257258// apply some static variables so we only call into the Win32 API once per process for each check.259#if defined(SDL_PLATFORM_XBOXONE) || defined(SDL_PLATFORM_XBOXSERIES)260#define CHECKWINVER(notdesktop_platform_result, test) return (notdesktop_platform_result);261#else262#define CHECKWINVER(notdesktop_platform_result, test) \263static bool checked = false; \264static BOOL result = FALSE; \265if (!checked) { \266result = (test); \267checked = true; \268} \269return result;270#endif271272BOOL WIN_IsWine(void)273{274static bool checked;275static bool is_wine;276277if (!checked) {278HMODULE ntdll = LoadLibrary(TEXT("ntdll.dll"));279if (ntdll) {280if (GetProcAddress(ntdll, "wine_get_version") != NULL) {281is_wine = true;282}283FreeLibrary(ntdll);284}285checked = true;286}287return is_wine;288}289290// this is the oldest thing we run on (and we may lose support for this in SDL3 at any time!),291// so there's no "OrGreater" as that would always be TRUE. The other functions are here to292// ask "can we support a specific feature?" but this function is here to ask "do we need to do293// something different for an OS version we probably should abandon?" :)294BOOL WIN_IsWindowsXP(void)295{296CHECKWINVER(FALSE, !WIN_IsWindowsVistaOrGreater() && IsWindowsVersionOrGreater(HIBYTE(_WIN32_WINNT_WINXP), LOBYTE(_WIN32_WINNT_WINXP), 0));297}298299BOOL WIN_IsWindowsVistaOrGreater(void)300{301CHECKWINVER(TRUE, IsWindowsVersionOrGreater(HIBYTE(_WIN32_WINNT_VISTA), LOBYTE(_WIN32_WINNT_VISTA), 0));302}303304BOOL WIN_IsWindows7OrGreater(void)305{306CHECKWINVER(TRUE, IsWindowsVersionOrGreater(HIBYTE(_WIN32_WINNT_WIN7), LOBYTE(_WIN32_WINNT_WIN7), 0));307}308309BOOL WIN_IsWindows8OrGreater(void)310{311CHECKWINVER(TRUE, IsWindowsVersionOrGreater(HIBYTE(_WIN32_WINNT_WIN8), LOBYTE(_WIN32_WINNT_WIN8), 0));312}313314#undef CHECKWINVER315316317/*318WAVExxxCAPS gives you 31 bytes for the device name, and just truncates if it's319longer. However, since WinXP, you can use the WAVExxxCAPS2 structure, which320will give you a name GUID. The full name is in the Windows Registry under321that GUID, located here: HKLM\System\CurrentControlSet\Control\MediaCategories322323Note that drivers can report GUID_NULL for the name GUID, in which case,324Windows makes a best effort to fill in those 31 bytes in the usual place.325This info summarized from MSDN:326327http://web.archive.org/web/20131027093034/http://msdn.microsoft.com/en-us/library/windows/hardware/ff536382(v=vs.85).aspx328329Always look this up in the registry if possible, because the strings are330different! At least on Win10, I see "Yeti Stereo Microphone" in the331Registry, and a unhelpful "Microphone(Yeti Stereo Microph" in winmm. Sigh.332333(Also, DirectSound shouldn't be limited to 32 chars, but its device enum334has the same problem.)335336WASAPI doesn't need this. This is just for DirectSound/WinMM.337*/338char *WIN_LookupAudioDeviceName(const WCHAR *name, const GUID *guid)339{340#if defined(SDL_PLATFORM_XBOXONE) || defined(SDL_PLATFORM_XBOXSERIES)341return WIN_StringToUTF8W(name); // No registry access on Xbox, go with what we've got.342#else343static const GUID nullguid = { 0 };344const unsigned char *ptr;345char keystr[128];346WCHAR *strw = NULL;347bool rc;348HKEY hkey;349DWORD len = 0;350char *result = NULL;351352if (WIN_IsEqualGUID(guid, &nullguid)) {353return WIN_StringToUTF8(name); // No GUID, go with what we've got.354}355356ptr = (const unsigned char *)guid;357(void)SDL_snprintf(keystr, sizeof(keystr),358"System\\CurrentControlSet\\Control\\MediaCategories\\{%02X%02X%02X%02X-%02X%02X-%02X%02X-%02X%02X-%02X%02X%02X%02X%02X%02X}",359ptr[3], ptr[2], ptr[1], ptr[0], ptr[5], ptr[4], ptr[7], ptr[6],360ptr[8], ptr[9], ptr[10], ptr[11], ptr[12], ptr[13], ptr[14], ptr[15]);361362strw = WIN_UTF8ToString(keystr);363rc = (RegOpenKeyExW(HKEY_LOCAL_MACHINE, strw, 0, KEY_QUERY_VALUE, &hkey) == ERROR_SUCCESS);364SDL_free(strw);365if (!rc) {366return WIN_StringToUTF8(name); // oh well.367}368369rc = (RegQueryValueExW(hkey, L"Name", NULL, NULL, NULL, &len) == ERROR_SUCCESS);370if (!rc) {371RegCloseKey(hkey);372return WIN_StringToUTF8(name); // oh well.373}374375strw = (WCHAR *)SDL_malloc(len + sizeof(WCHAR));376if (!strw) {377RegCloseKey(hkey);378return WIN_StringToUTF8(name); // oh well.379}380381rc = (RegQueryValueExW(hkey, L"Name", NULL, NULL, (LPBYTE)strw, &len) == ERROR_SUCCESS);382RegCloseKey(hkey);383if (!rc) {384SDL_free(strw);385return WIN_StringToUTF8(name); // oh well.386}387388strw[len / 2] = 0; // make sure it's null-terminated.389390result = WIN_StringToUTF8(strw);391SDL_free(strw);392return result ? result : WIN_StringToUTF8(name);393#endif394}395396BOOL WIN_IsEqualGUID(const GUID *a, const GUID *b)397{398return (SDL_memcmp(a, b, sizeof(*a)) == 0);399}400401BOOL WIN_IsEqualIID(REFIID a, REFIID b)402{403return (SDL_memcmp(a, b, sizeof(*a)) == 0);404}405406void WIN_RECTToRect(const RECT *winrect, SDL_Rect *sdlrect)407{408sdlrect->x = winrect->left;409sdlrect->w = (winrect->right - winrect->left) + 1;410sdlrect->y = winrect->top;411sdlrect->h = (winrect->bottom - winrect->top) + 1;412}413414void WIN_RectToRECT(const SDL_Rect *sdlrect, RECT *winrect)415{416winrect->left = sdlrect->x;417winrect->right = sdlrect->x + sdlrect->w - 1;418winrect->top = sdlrect->y;419winrect->bottom = sdlrect->y + sdlrect->h - 1;420}421422bool WIN_WindowRectValid(const RECT *rect)423{424// A window can be resized to zero height, but not zero width425return (rect->right > 0);426}427428// Some GUIDs we need to know without linking to libraries that aren't available before Vista.429/* *INDENT-OFF* */ // clang-format off430static const GUID SDL_KSDATAFORMAT_SUBTYPE_PCM = { 0x00000001, 0x0000, 0x0010,{ 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 } };431static const GUID SDL_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT = { 0x00000003, 0x0000, 0x0010,{ 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 } };432/* *INDENT-ON* */ // clang-format on433434SDL_AudioFormat SDL_WaveFormatExToSDLFormat(WAVEFORMATEX *waveformat)435{436if ((waveformat->wFormatTag == WAVE_FORMAT_IEEE_FLOAT) && (waveformat->wBitsPerSample == 32)) {437return SDL_AUDIO_F32;438} else if ((waveformat->wFormatTag == WAVE_FORMAT_PCM) && (waveformat->wBitsPerSample == 16)) {439return SDL_AUDIO_S16;440} else if ((waveformat->wFormatTag == WAVE_FORMAT_PCM) && (waveformat->wBitsPerSample == 32)) {441return SDL_AUDIO_S32;442} else if (waveformat->wFormatTag == WAVE_FORMAT_EXTENSIBLE) {443const WAVEFORMATEXTENSIBLE *ext = (const WAVEFORMATEXTENSIBLE *)waveformat;444if ((SDL_memcmp(&ext->SubFormat, &SDL_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, sizeof(GUID)) == 0) && (waveformat->wBitsPerSample == 32)) {445return SDL_AUDIO_F32;446} else if ((SDL_memcmp(&ext->SubFormat, &SDL_KSDATAFORMAT_SUBTYPE_PCM, sizeof(GUID)) == 0) && (waveformat->wBitsPerSample == 16)) {447return SDL_AUDIO_S16;448} else if ((SDL_memcmp(&ext->SubFormat, &SDL_KSDATAFORMAT_SUBTYPE_PCM, sizeof(GUID)) == 0) && (waveformat->wBitsPerSample == 32)) {449return SDL_AUDIO_S32;450}451}452return SDL_AUDIO_UNKNOWN;453}454455456int WIN_WideCharToMultiByte(UINT CodePage, DWORD dwFlags, LPCWCH lpWideCharStr, int cchWideChar, LPSTR lpMultiByteStr, int cbMultiByte, LPCCH lpDefaultChar, LPBOOL lpUsedDefaultChar)457{458if (WIN_IsWindowsXP()) {459dwFlags &= ~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.460}461return WideCharToMultiByte(CodePage, dwFlags, lpWideCharStr, cchWideChar, lpMultiByteStr, cbMultiByte, lpDefaultChar, lpUsedDefaultChar);462}463464#endif // defined(SDL_PLATFORM_WINDOWS)465466467