Path: blob/master/thirdparty/sdl/core/windows/SDL_windows.c
9905 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// Sets an error message based on an HRESULT56bool WIN_SetErrorFromHRESULT(const char *prefix, HRESULT hr)57{58TCHAR buffer[1024];59char *message;60TCHAR *p = buffer;61DWORD c = FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, hr, 0,62buffer, SDL_arraysize(buffer), NULL);63buffer[c] = 0;64// kill CR/LF that FormatMessage() sticks at the end65while (*p) {66if (*p == '\r') {67*p = 0;68break;69}70++p;71}72message = WIN_StringToUTF8(buffer);73SDL_SetError("%s%s%s", prefix ? prefix : "", prefix ? ": " : "", message);74SDL_free(message);75return false;76}7778// Sets an error message based on GetLastError()79bool WIN_SetError(const char *prefix)80{81return WIN_SetErrorFromHRESULT(prefix, GetLastError());82}8384HRESULT85WIN_CoInitialize(void)86{87/* SDL handles any threading model, so initialize with the default, which88is compatible with OLE and if that doesn't work, try multi-threaded mode.8990If you need multi-threaded mode, call CoInitializeEx() before SDL_Init()91*/92#if defined(SDL_PLATFORM_XBOXONE) || defined(SDL_PLATFORM_XBOXSERIES)93// On Xbox, there's no need to call CoInitializeEx (and it's not implemented)94return S_OK;95#else96HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);97if (hr == RPC_E_CHANGED_MODE) {98hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);99}100101// S_FALSE means success, but someone else already initialized.102// You still need to call CoUninitialize in this case!103if (hr == S_FALSE) {104return S_OK;105}106107return hr;108#endif109}110111void WIN_CoUninitialize(void)112{113CoUninitialize();114}115116FARPROC WIN_LoadComBaseFunction(const char *name)117{118static bool s_bLoaded;119static HMODULE s_hComBase;120121if (!s_bLoaded) {122s_hComBase = LoadLibraryEx(TEXT("combase.dll"), NULL, LOAD_LIBRARY_SEARCH_SYSTEM32);123s_bLoaded = true;124}125if (s_hComBase) {126return GetProcAddress(s_hComBase, name);127} else {128return NULL;129}130}131132HRESULT133WIN_RoInitialize(void)134{135typedef HRESULT(WINAPI * RoInitialize_t)(RO_INIT_TYPE initType);136RoInitialize_t RoInitializeFunc = (RoInitialize_t)WIN_LoadComBaseFunction("RoInitialize");137if (RoInitializeFunc) {138// RO_INIT_SINGLETHREADED is equivalent to COINIT_APARTMENTTHREADED139HRESULT hr = RoInitializeFunc(RO_INIT_SINGLETHREADED);140if (hr == RPC_E_CHANGED_MODE) {141hr = RoInitializeFunc(RO_INIT_MULTITHREADED);142}143144// S_FALSE means success, but someone else already initialized.145// You still need to call RoUninitialize in this case!146if (hr == S_FALSE) {147return S_OK;148}149150return hr;151} else {152return E_NOINTERFACE;153}154}155156void WIN_RoUninitialize(void)157{158typedef void(WINAPI * RoUninitialize_t)(void);159RoUninitialize_t RoUninitializeFunc = (RoUninitialize_t)WIN_LoadComBaseFunction("RoUninitialize");160if (RoUninitializeFunc) {161RoUninitializeFunc();162}163}164165#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)166static BOOL IsWindowsVersionOrGreater(WORD wMajorVersion, WORD wMinorVersion, WORD wServicePackMajor)167{168OSVERSIONINFOEXW osvi;169DWORDLONG const dwlConditionMask = VerSetConditionMask(170VerSetConditionMask(171VerSetConditionMask(1720, VER_MAJORVERSION, VER_GREATER_EQUAL),173VER_MINORVERSION, VER_GREATER_EQUAL),174VER_SERVICEPACKMAJOR, VER_GREATER_EQUAL);175176SDL_zero(osvi);177osvi.dwOSVersionInfoSize = sizeof(osvi);178osvi.dwMajorVersion = wMajorVersion;179osvi.dwMinorVersion = wMinorVersion;180osvi.wServicePackMajor = wServicePackMajor;181182return VerifyVersionInfoW(&osvi, VER_MAJORVERSION | VER_MINORVERSION | VER_SERVICEPACKMAJOR, dwlConditionMask) != FALSE;183}184#endif185186// apply some static variables so we only call into the Win32 API once per process for each check.187#if defined(SDL_PLATFORM_XBOXONE) || defined(SDL_PLATFORM_XBOXSERIES)188#define CHECKWINVER(notdesktop_platform_result, test) return (notdesktop_platform_result);189#else190#define CHECKWINVER(notdesktop_platform_result, test) \191static bool checked = false; \192static BOOL result = FALSE; \193if (!checked) { \194result = (test); \195checked = true; \196} \197return result;198#endif199200// this is the oldest thing we run on (and we may lose support for this in SDL3 at any time!),201// so there's no "OrGreater" as that would always be TRUE. The other functions are here to202// ask "can we support a specific feature?" but this function is here to ask "do we need to do203// something different for an OS version we probably should abandon?" :)204BOOL WIN_IsWindowsXP(void)205{206CHECKWINVER(FALSE, !WIN_IsWindowsVistaOrGreater() && IsWindowsVersionOrGreater(HIBYTE(_WIN32_WINNT_WINXP), LOBYTE(_WIN32_WINNT_WINXP), 0));207}208209BOOL WIN_IsWindowsVistaOrGreater(void)210{211CHECKWINVER(TRUE, IsWindowsVersionOrGreater(HIBYTE(_WIN32_WINNT_VISTA), LOBYTE(_WIN32_WINNT_VISTA), 0));212}213214BOOL WIN_IsWindows7OrGreater(void)215{216CHECKWINVER(TRUE, IsWindowsVersionOrGreater(HIBYTE(_WIN32_WINNT_WIN7), LOBYTE(_WIN32_WINNT_WIN7), 0));217}218219BOOL WIN_IsWindows8OrGreater(void)220{221CHECKWINVER(TRUE, IsWindowsVersionOrGreater(HIBYTE(_WIN32_WINNT_WIN8), LOBYTE(_WIN32_WINNT_WIN8), 0));222}223224#undef CHECKWINVER225226227/*228WAVExxxCAPS gives you 31 bytes for the device name, and just truncates if it's229longer. However, since WinXP, you can use the WAVExxxCAPS2 structure, which230will give you a name GUID. The full name is in the Windows Registry under231that GUID, located here: HKLM\System\CurrentControlSet\Control\MediaCategories232233Note that drivers can report GUID_NULL for the name GUID, in which case,234Windows makes a best effort to fill in those 31 bytes in the usual place.235This info summarized from MSDN:236237http://web.archive.org/web/20131027093034/http://msdn.microsoft.com/en-us/library/windows/hardware/ff536382(v=vs.85).aspx238239Always look this up in the registry if possible, because the strings are240different! At least on Win10, I see "Yeti Stereo Microphone" in the241Registry, and a unhelpful "Microphone(Yeti Stereo Microph" in winmm. Sigh.242243(Also, DirectSound shouldn't be limited to 32 chars, but its device enum244has the same problem.)245246WASAPI doesn't need this. This is just for DirectSound/WinMM.247*/248char *WIN_LookupAudioDeviceName(const WCHAR *name, const GUID *guid)249{250#if defined(SDL_PLATFORM_XBOXONE) || defined(SDL_PLATFORM_XBOXSERIES)251return WIN_StringToUTF8W(name); // No registry access on Xbox, go with what we've got.252#else253static const GUID nullguid = { 0 };254const unsigned char *ptr;255char keystr[128];256WCHAR *strw = NULL;257bool rc;258HKEY hkey;259DWORD len = 0;260char *result = NULL;261262if (WIN_IsEqualGUID(guid, &nullguid)) {263return WIN_StringToUTF8(name); // No GUID, go with what we've got.264}265266ptr = (const unsigned char *)guid;267(void)SDL_snprintf(keystr, sizeof(keystr),268"System\\CurrentControlSet\\Control\\MediaCategories\\{%02X%02X%02X%02X-%02X%02X-%02X%02X-%02X%02X-%02X%02X%02X%02X%02X%02X}",269ptr[3], ptr[2], ptr[1], ptr[0], ptr[5], ptr[4], ptr[7], ptr[6],270ptr[8], ptr[9], ptr[10], ptr[11], ptr[12], ptr[13], ptr[14], ptr[15]);271272strw = WIN_UTF8ToString(keystr);273rc = (RegOpenKeyExW(HKEY_LOCAL_MACHINE, strw, 0, KEY_QUERY_VALUE, &hkey) == ERROR_SUCCESS);274SDL_free(strw);275if (!rc) {276return WIN_StringToUTF8(name); // oh well.277}278279rc = (RegQueryValueExW(hkey, L"Name", NULL, NULL, NULL, &len) == ERROR_SUCCESS);280if (!rc) {281RegCloseKey(hkey);282return WIN_StringToUTF8(name); // oh well.283}284285strw = (WCHAR *)SDL_malloc(len + sizeof(WCHAR));286if (!strw) {287RegCloseKey(hkey);288return WIN_StringToUTF8(name); // oh well.289}290291rc = (RegQueryValueExW(hkey, L"Name", NULL, NULL, (LPBYTE)strw, &len) == ERROR_SUCCESS);292RegCloseKey(hkey);293if (!rc) {294SDL_free(strw);295return WIN_StringToUTF8(name); // oh well.296}297298strw[len / 2] = 0; // make sure it's null-terminated.299300result = WIN_StringToUTF8(strw);301SDL_free(strw);302return result ? result : WIN_StringToUTF8(name);303#endif304}305306BOOL WIN_IsEqualGUID(const GUID *a, const GUID *b)307{308return (SDL_memcmp(a, b, sizeof(*a)) == 0);309}310311BOOL WIN_IsEqualIID(REFIID a, REFIID b)312{313return (SDL_memcmp(a, b, sizeof(*a)) == 0);314}315316void WIN_RECTToRect(const RECT *winrect, SDL_Rect *sdlrect)317{318sdlrect->x = winrect->left;319sdlrect->w = (winrect->right - winrect->left) + 1;320sdlrect->y = winrect->top;321sdlrect->h = (winrect->bottom - winrect->top) + 1;322}323324void WIN_RectToRECT(const SDL_Rect *sdlrect, RECT *winrect)325{326winrect->left = sdlrect->x;327winrect->right = sdlrect->x + sdlrect->w - 1;328winrect->top = sdlrect->y;329winrect->bottom = sdlrect->y + sdlrect->h - 1;330}331332bool WIN_WindowRectValid(const RECT *rect)333{334// A window can be resized to zero height, but not zero width335return (rect->right > 0);336}337338// Some GUIDs we need to know without linking to libraries that aren't available before Vista.339/* *INDENT-OFF* */ // clang-format off340static const GUID SDL_KSDATAFORMAT_SUBTYPE_PCM = { 0x00000001, 0x0000, 0x0010,{ 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 } };341static const GUID SDL_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT = { 0x00000003, 0x0000, 0x0010,{ 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 } };342/* *INDENT-ON* */ // clang-format on343344SDL_AudioFormat SDL_WaveFormatExToSDLFormat(WAVEFORMATEX *waveformat)345{346if ((waveformat->wFormatTag == WAVE_FORMAT_IEEE_FLOAT) && (waveformat->wBitsPerSample == 32)) {347return SDL_AUDIO_F32;348} else if ((waveformat->wFormatTag == WAVE_FORMAT_PCM) && (waveformat->wBitsPerSample == 16)) {349return SDL_AUDIO_S16;350} else if ((waveformat->wFormatTag == WAVE_FORMAT_PCM) && (waveformat->wBitsPerSample == 32)) {351return SDL_AUDIO_S32;352} else if (waveformat->wFormatTag == WAVE_FORMAT_EXTENSIBLE) {353const WAVEFORMATEXTENSIBLE *ext = (const WAVEFORMATEXTENSIBLE *)waveformat;354if ((SDL_memcmp(&ext->SubFormat, &SDL_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, sizeof(GUID)) == 0) && (waveformat->wBitsPerSample == 32)) {355return SDL_AUDIO_F32;356} else if ((SDL_memcmp(&ext->SubFormat, &SDL_KSDATAFORMAT_SUBTYPE_PCM, sizeof(GUID)) == 0) && (waveformat->wBitsPerSample == 16)) {357return SDL_AUDIO_S16;358} else if ((SDL_memcmp(&ext->SubFormat, &SDL_KSDATAFORMAT_SUBTYPE_PCM, sizeof(GUID)) == 0) && (waveformat->wBitsPerSample == 32)) {359return SDL_AUDIO_S32;360}361}362return SDL_AUDIO_UNKNOWN;363}364365366int WIN_WideCharToMultiByte(UINT CodePage, DWORD dwFlags, LPCWCH lpWideCharStr, int cchWideChar, LPSTR lpMultiByteStr, int cbMultiByte, LPCCH lpDefaultChar, LPBOOL lpUsedDefaultChar)367{368if (WIN_IsWindowsXP()) {369dwFlags &= ~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.370}371return WideCharToMultiByte(CodePage, dwFlags, lpWideCharStr, cchWideChar, lpMultiByteStr, cbMultiByte, lpDefaultChar, lpUsedDefaultChar);372}373374#endif // defined(SDL_PLATFORM_WINDOWS)375376377