From 5a079a2d114f96d4847d1ee305d5b7c16eeec50e Mon Sep 17 00:00:00 2001 From: 3gg <3gg@shellblade.net> Date: Sat, 27 Dec 2025 12:03:39 -0800 Subject: Initial commit --- contrib/SDL-3.2.8/src/video/windows/SDL_msctf.h | 246 ++ .../src/video/windows/SDL_surface_utils.c | 97 + .../src/video/windows/SDL_surface_utils.h | 38 + .../src/video/windows/SDL_windowsclipboard.c | 442 ++++ .../src/video/windows/SDL_windowsclipboard.h | 34 + .../src/video/windows/SDL_windowsevents.c | 2734 ++++++++++++++++++++ .../src/video/windows/SDL_windowsevents.h | 39 + .../src/video/windows/SDL_windowsframebuffer.c | 132 + .../src/video/windows/SDL_windowsframebuffer.h | 25 + .../src/video/windows/SDL_windowsgameinput.c | 612 +++++ .../src/video/windows/SDL_windowsgameinput.h | 29 + .../src/video/windows/SDL_windowskeyboard.c | 1145 ++++++++ .../src/video/windows/SDL_windowskeyboard.h | 40 + .../src/video/windows/SDL_windowsmessagebox.c | 1086 ++++++++ .../src/video/windows/SDL_windowsmessagebox.h | 27 + .../SDL-3.2.8/src/video/windows/SDL_windowsmodes.c | 927 +++++++ .../SDL-3.2.8/src/video/windows/SDL_windowsmodes.h | 55 + .../SDL-3.2.8/src/video/windows/SDL_windowsmouse.c | 745 ++++++ .../SDL-3.2.8/src/video/windows/SDL_windowsmouse.h | 34 + .../src/video/windows/SDL_windowsopengl.c | 906 +++++++ .../src/video/windows/SDL_windowsopengl.h | 178 ++ .../src/video/windows/SDL_windowsopengles.c | 142 + .../src/video/windows/SDL_windowsopengles.h | 48 + .../src/video/windows/SDL_windowsrawinput.c | 262 ++ .../src/video/windows/SDL_windowsrawinput.h | 30 + .../SDL-3.2.8/src/video/windows/SDL_windowsshape.c | 125 + .../SDL-3.2.8/src/video/windows/SDL_windowsshape.h | 28 + .../SDL-3.2.8/src/video/windows/SDL_windowsvideo.c | 773 ++++++ .../SDL-3.2.8/src/video/windows/SDL_windowsvideo.h | 507 ++++ .../src/video/windows/SDL_windowsvulkan.c | 195 ++ .../src/video/windows/SDL_windowsvulkan.h | 55 + .../src/video/windows/SDL_windowswindow.c | 2435 +++++++++++++++++ .../src/video/windows/SDL_windowswindow.h | 152 ++ contrib/SDL-3.2.8/src/video/windows/wmmsg.h | 1050 ++++++++ 34 files changed, 15373 insertions(+) create mode 100644 contrib/SDL-3.2.8/src/video/windows/SDL_msctf.h create mode 100644 contrib/SDL-3.2.8/src/video/windows/SDL_surface_utils.c create mode 100644 contrib/SDL-3.2.8/src/video/windows/SDL_surface_utils.h create mode 100644 contrib/SDL-3.2.8/src/video/windows/SDL_windowsclipboard.c create mode 100644 contrib/SDL-3.2.8/src/video/windows/SDL_windowsclipboard.h create mode 100644 contrib/SDL-3.2.8/src/video/windows/SDL_windowsevents.c create mode 100644 contrib/SDL-3.2.8/src/video/windows/SDL_windowsevents.h create mode 100644 contrib/SDL-3.2.8/src/video/windows/SDL_windowsframebuffer.c create mode 100644 contrib/SDL-3.2.8/src/video/windows/SDL_windowsframebuffer.h create mode 100644 contrib/SDL-3.2.8/src/video/windows/SDL_windowsgameinput.c create mode 100644 contrib/SDL-3.2.8/src/video/windows/SDL_windowsgameinput.h create mode 100644 contrib/SDL-3.2.8/src/video/windows/SDL_windowskeyboard.c create mode 100644 contrib/SDL-3.2.8/src/video/windows/SDL_windowskeyboard.h create mode 100644 contrib/SDL-3.2.8/src/video/windows/SDL_windowsmessagebox.c create mode 100644 contrib/SDL-3.2.8/src/video/windows/SDL_windowsmessagebox.h create mode 100644 contrib/SDL-3.2.8/src/video/windows/SDL_windowsmodes.c create mode 100644 contrib/SDL-3.2.8/src/video/windows/SDL_windowsmodes.h create mode 100644 contrib/SDL-3.2.8/src/video/windows/SDL_windowsmouse.c create mode 100644 contrib/SDL-3.2.8/src/video/windows/SDL_windowsmouse.h create mode 100644 contrib/SDL-3.2.8/src/video/windows/SDL_windowsopengl.c create mode 100644 contrib/SDL-3.2.8/src/video/windows/SDL_windowsopengl.h create mode 100644 contrib/SDL-3.2.8/src/video/windows/SDL_windowsopengles.c create mode 100644 contrib/SDL-3.2.8/src/video/windows/SDL_windowsopengles.h create mode 100644 contrib/SDL-3.2.8/src/video/windows/SDL_windowsrawinput.c create mode 100644 contrib/SDL-3.2.8/src/video/windows/SDL_windowsrawinput.h create mode 100644 contrib/SDL-3.2.8/src/video/windows/SDL_windowsshape.c create mode 100644 contrib/SDL-3.2.8/src/video/windows/SDL_windowsshape.h create mode 100644 contrib/SDL-3.2.8/src/video/windows/SDL_windowsvideo.c create mode 100644 contrib/SDL-3.2.8/src/video/windows/SDL_windowsvideo.h create mode 100644 contrib/SDL-3.2.8/src/video/windows/SDL_windowsvulkan.c create mode 100644 contrib/SDL-3.2.8/src/video/windows/SDL_windowsvulkan.h create mode 100644 contrib/SDL-3.2.8/src/video/windows/SDL_windowswindow.c create mode 100644 contrib/SDL-3.2.8/src/video/windows/SDL_windowswindow.h create mode 100644 contrib/SDL-3.2.8/src/video/windows/wmmsg.h (limited to 'contrib/SDL-3.2.8/src/video/windows') diff --git a/contrib/SDL-3.2.8/src/video/windows/SDL_msctf.h b/contrib/SDL-3.2.8/src/video/windows/SDL_msctf.h new file mode 100644 index 0000000..0c9ed01 --- /dev/null +++ b/contrib/SDL-3.2.8/src/video/windows/SDL_msctf.h @@ -0,0 +1,246 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +#ifndef SDL_msctf_h_ +#define SDL_msctf_h_ + +#include + +#define TF_INVALID_COOKIE (0xffffffff) +#define TF_IPSINK_FLAG_ACTIVE 0x0001 +#define TF_TMAE_UIELEMENTENABLEDONLY 0x00000004 + +/* *INDENT-OFF* */ // clang-format off + +typedef struct ITfThreadMgr ITfThreadMgr; +typedef struct ITfDocumentMgr ITfDocumentMgr; +typedef struct ITfClientId ITfClientId; + +typedef struct IEnumTfDocumentMgrs IEnumTfDocumentMgrs; +typedef struct IEnumTfFunctionProviders IEnumTfFunctionProviders; +typedef struct ITfFunctionProvider ITfFunctionProvider; +typedef struct ITfCompartmentMgr ITfCompartmentMgr; +typedef struct ITfContext ITfContext; +typedef struct IEnumTfContexts IEnumTfContexts; +typedef struct ITfUIElementSink ITfUIElementSink; +typedef struct ITfUIElement ITfUIElement; +typedef struct ITfUIElementMgr ITfUIElementMgr; +typedef struct IEnumTfUIElements IEnumTfUIElements; +typedef struct ITfThreadMgrEx ITfThreadMgrEx; +typedef struct ITfCandidateListUIElement ITfCandidateListUIElement; +typedef struct ITfReadingInformationUIElement ITfReadingInformationUIElement; +typedef struct ITfInputProcessorProfileActivationSink ITfInputProcessorProfileActivationSink; +typedef struct ITfSource ITfSource; + +typedef DWORD TfClientId; +typedef DWORD TfEditCookie; + +typedef struct ITfThreadMgrVtbl +{ + HRESULT (STDMETHODCALLTYPE *QueryInterface)(ITfThreadMgr *, REFIID, void **); + ULONG (STDMETHODCALLTYPE *AddRef)(ITfThreadMgr *); + ULONG (STDMETHODCALLTYPE *Release)(ITfThreadMgr *); + HRESULT (STDMETHODCALLTYPE *Activate)(ITfThreadMgr *, TfClientId *); + HRESULT (STDMETHODCALLTYPE *Deactivate)(ITfThreadMgr *); + HRESULT (STDMETHODCALLTYPE *CreateDocumentMgr)(ITfThreadMgr *); + HRESULT (STDMETHODCALLTYPE *EnumDocumentMgrs)(ITfThreadMgr *, IEnumTfDocumentMgrs **); + HRESULT (STDMETHODCALLTYPE *GetFocus)(ITfThreadMgr *, ITfDocumentMgr **); + HRESULT (STDMETHODCALLTYPE *SetFocus)(ITfThreadMgr *, ITfDocumentMgr *); + HRESULT (STDMETHODCALLTYPE *AssociateFocus)(ITfThreadMgr *, HWND, ITfDocumentMgr *, ITfDocumentMgr **); + HRESULT (STDMETHODCALLTYPE *IsThreadFocus)(ITfThreadMgr *, BOOL *); + HRESULT (STDMETHODCALLTYPE *GetFunctionProvider)(ITfThreadMgr *, REFCLSID, ITfFunctionProvider **); + HRESULT (STDMETHODCALLTYPE *EnumFunctionProviders)(ITfThreadMgr *, IEnumTfFunctionProviders **); + HRESULT (STDMETHODCALLTYPE *GetGlobalCompartment)(ITfThreadMgr *, ITfCompartmentMgr **); +} ITfThreadMgrVtbl; + +struct ITfThreadMgr +{ + const struct ITfThreadMgrVtbl *lpVtbl; +}; + +typedef struct ITfThreadMgrExVtbl +{ + HRESULT (STDMETHODCALLTYPE *QueryInterface)(ITfThreadMgrEx *, REFIID, void **); + ULONG (STDMETHODCALLTYPE *AddRef)(ITfThreadMgrEx *); + ULONG (STDMETHODCALLTYPE *Release)(ITfThreadMgrEx *); + HRESULT (STDMETHODCALLTYPE *Activate)(ITfThreadMgrEx *, TfClientId *); + HRESULT (STDMETHODCALLTYPE *Deactivate)(ITfThreadMgrEx *); + HRESULT (STDMETHODCALLTYPE *CreateDocumentMgr)(ITfThreadMgrEx *, ITfDocumentMgr **); + HRESULT (STDMETHODCALLTYPE *EnumDocumentMgrs)(ITfThreadMgrEx *, IEnumTfDocumentMgrs **); + HRESULT (STDMETHODCALLTYPE *GetFocus)(ITfThreadMgrEx *, ITfDocumentMgr **); + HRESULT (STDMETHODCALLTYPE *SetFocus)(ITfThreadMgrEx *, ITfDocumentMgr *); + HRESULT (STDMETHODCALLTYPE *AssociateFocus)(ITfThreadMgrEx *, ITfDocumentMgr *, ITfDocumentMgr **); + HRESULT (STDMETHODCALLTYPE *IsThreadFocus)(ITfThreadMgrEx *, BOOL *); + HRESULT (STDMETHODCALLTYPE *GetFunctionProvider)(ITfThreadMgrEx *, REFCLSID, ITfFunctionProvider **); + HRESULT (STDMETHODCALLTYPE *EnumFunctionProviders)(ITfThreadMgrEx *, IEnumTfFunctionProviders **); + HRESULT (STDMETHODCALLTYPE *GetGlobalCompartment)(ITfThreadMgrEx *, ITfCompartmentMgr **); + HRESULT (STDMETHODCALLTYPE *ActivateEx)(ITfThreadMgrEx *, TfClientId *, DWORD); + HRESULT (STDMETHODCALLTYPE *GetActiveFlags)(ITfThreadMgrEx *, DWORD *); +} ITfThreadMgrExVtbl; + +struct ITfThreadMgrEx +{ + const struct ITfThreadMgrExVtbl *lpVtbl; +}; + +typedef struct ITfDocumentMgrVtbl +{ + HRESULT (STDMETHODCALLTYPE *QueryInterface)(ITfDocumentMgr *, REFIID, void **); + ULONG (STDMETHODCALLTYPE *AddRef)(ITfDocumentMgr *); + ULONG (STDMETHODCALLTYPE *Release)(ITfDocumentMgr *); + HRESULT (STDMETHODCALLTYPE *CreateContext)(ITfDocumentMgr *, TfClientId, DWORD, IUnknown *, ITfContext **, TfEditCookie *); + HRESULT (STDMETHODCALLTYPE *Push)(ITfDocumentMgr *, ITfContext *); + HRESULT (STDMETHODCALLTYPE *Pop)(ITfDocumentMgr *); + HRESULT (STDMETHODCALLTYPE *GetTop)(ITfDocumentMgr *, ITfContext **); + HRESULT (STDMETHODCALLTYPE *GetBase)(ITfDocumentMgr *, ITfContext **); + HRESULT (STDMETHODCALLTYPE *EnumContexts)(ITfDocumentMgr *, IEnumTfContexts **); +} ITfDocumentMgrVtbl; + +struct ITfDocumentMgr +{ + const struct ITfDocumentMgrVtbl *lpVtbl; +}; + +typedef struct ITfUIElementSinkVtbl +{ + HRESULT (STDMETHODCALLTYPE *QueryInterface)(ITfUIElementSink *, REFIID, void **); + ULONG (STDMETHODCALLTYPE *AddRef)(ITfUIElementSink *); + ULONG (STDMETHODCALLTYPE *Release)(ITfUIElementSink *); + HRESULT (STDMETHODCALLTYPE *BeginUIElement)(ITfUIElementSink *, DWORD, BOOL *); + HRESULT (STDMETHODCALLTYPE *UpdateUIElement)(ITfUIElementSink *, DWORD); + HRESULT (STDMETHODCALLTYPE *EndUIElement)(ITfUIElementSink *, DWORD); +} ITfUIElementSinkVtbl; + +struct ITfUIElementSink +{ + const struct ITfUIElementSinkVtbl *lpVtbl; +}; + +typedef struct ITfUIElementMgrVtbl +{ + HRESULT (STDMETHODCALLTYPE *QueryInterface)(ITfUIElementMgr *, REFIID, void **); + ULONG (STDMETHODCALLTYPE *AddRef)(ITfUIElementMgr *); + ULONG (STDMETHODCALLTYPE *Release)(ITfUIElementMgr *); + HRESULT (STDMETHODCALLTYPE *BeginUIElement)(ITfUIElementMgr *, ITfUIElement *, BOOL *, DWORD *); + HRESULT (STDMETHODCALLTYPE *UpdateUIElement)(ITfUIElementMgr *, DWORD); + HRESULT (STDMETHODCALLTYPE *EndUIElement)(ITfUIElementMgr *, DWORD); + HRESULT (STDMETHODCALLTYPE *GetUIElement)(ITfUIElementMgr *, DWORD, ITfUIElement **); + HRESULT (STDMETHODCALLTYPE *EnumUIElements)(ITfUIElementMgr *, IEnumTfUIElements **); +} ITfUIElementMgrVtbl; + +struct ITfUIElementMgr +{ + const struct ITfUIElementMgrVtbl *lpVtbl; +}; + +typedef struct ITfCandidateListUIElementVtbl +{ + HRESULT (STDMETHODCALLTYPE *QueryInterface)(ITfCandidateListUIElement *, REFIID, void **); + ULONG (STDMETHODCALLTYPE *AddRef)(ITfCandidateListUIElement *); + ULONG (STDMETHODCALLTYPE *Release)(ITfCandidateListUIElement *); + HRESULT (STDMETHODCALLTYPE *GetDescription)(ITfCandidateListUIElement *, BSTR *); + HRESULT (STDMETHODCALLTYPE *GetGUID)(ITfCandidateListUIElement *, GUID *); + HRESULT (STDMETHODCALLTYPE *Show)(ITfCandidateListUIElement *, BOOL); + HRESULT (STDMETHODCALLTYPE *IsShown)(ITfCandidateListUIElement *, BOOL *); + HRESULT (STDMETHODCALLTYPE *GetUpdatedFlags)(ITfCandidateListUIElement *, DWORD *); + HRESULT (STDMETHODCALLTYPE *GetDocumentMgr)(ITfCandidateListUIElement *, ITfDocumentMgr **); + HRESULT (STDMETHODCALLTYPE *GetCount)(ITfCandidateListUIElement *, UINT *); + HRESULT (STDMETHODCALLTYPE *GetSelection)(ITfCandidateListUIElement *, UINT *); + HRESULT (STDMETHODCALLTYPE *GetString)(ITfCandidateListUIElement *, UINT, BSTR *); + HRESULT (STDMETHODCALLTYPE *GetPageIndex)(ITfCandidateListUIElement *, UINT *, UINT, UINT *); + HRESULT (STDMETHODCALLTYPE *SetPageIndex)(ITfCandidateListUIElement *, UINT *, UINT); + HRESULT (STDMETHODCALLTYPE *GetCurrentPage)(ITfCandidateListUIElement *, UINT *); +} ITfCandidateListUIElementVtbl; + +struct ITfCandidateListUIElement +{ + const struct ITfCandidateListUIElementVtbl *lpVtbl; +}; + +typedef struct ITfReadingInformationUIElementVtbl +{ + HRESULT (STDMETHODCALLTYPE *QueryInterface)(ITfReadingInformationUIElement *, REFIID, void **); + ULONG (STDMETHODCALLTYPE *AddRef)(ITfReadingInformationUIElement *); + ULONG (STDMETHODCALLTYPE *Release)(ITfReadingInformationUIElement *); + HRESULT (STDMETHODCALLTYPE *GetDescription)(ITfReadingInformationUIElement *, BSTR *); + HRESULT (STDMETHODCALLTYPE *GetGUID)(ITfReadingInformationUIElement *, GUID *); + HRESULT (STDMETHODCALLTYPE *Show)(ITfReadingInformationUIElement *, BOOL); + HRESULT (STDMETHODCALLTYPE *IsShown)(ITfReadingInformationUIElement *, BOOL *); + HRESULT (STDMETHODCALLTYPE *GetUpdatedFlags)(ITfReadingInformationUIElement *, DWORD *); + HRESULT (STDMETHODCALLTYPE *GetContext)(ITfReadingInformationUIElement *, ITfContext **); + HRESULT (STDMETHODCALLTYPE *GetString)(ITfReadingInformationUIElement *, BSTR *); + HRESULT (STDMETHODCALLTYPE *GetMaxReadingStringLength)(ITfReadingInformationUIElement *, UINT *); + HRESULT (STDMETHODCALLTYPE *GetErrorIndex)(ITfReadingInformationUIElement *, UINT *); + HRESULT (STDMETHODCALLTYPE *IsVerticalOrderPreferred)(ITfReadingInformationUIElement *, BOOL *); +} ITfReadingInformationUIElementVtbl; + +struct ITfReadingInformationUIElement +{ + const struct ITfReadingInformationUIElementVtbl *lpVtbl; +}; + +typedef struct ITfUIElementVtbl +{ + HRESULT (STDMETHODCALLTYPE *QueryInterface)(ITfUIElement *, REFIID, void **); + ULONG (STDMETHODCALLTYPE *AddRef)(ITfUIElement *); + ULONG (STDMETHODCALLTYPE *Release)(ITfUIElement *); + HRESULT (STDMETHODCALLTYPE *GetDescription)(ITfUIElement *, BSTR *); + HRESULT (STDMETHODCALLTYPE *GetGUID)(ITfUIElement *, GUID *); + HRESULT (STDMETHODCALLTYPE *Show)(ITfUIElement *, BOOL); + HRESULT (STDMETHODCALLTYPE *IsShown)(ITfUIElement *, BOOL *); +} ITfUIElementVtbl; + +struct ITfUIElement +{ + const struct ITfUIElementVtbl *lpVtbl; +}; + +typedef struct ITfInputProcessorProfileActivationSinkVtbl +{ + HRESULT (STDMETHODCALLTYPE *QueryInterface)(ITfInputProcessorProfileActivationSink *, REFIID, void **); + ULONG (STDMETHODCALLTYPE *AddRef)(ITfInputProcessorProfileActivationSink *); + ULONG (STDMETHODCALLTYPE *Release)(ITfInputProcessorProfileActivationSink *); + HRESULT (STDMETHODCALLTYPE *OnActivated)(ITfInputProcessorProfileActivationSink *, DWORD, LANGID, REFCLSID, REFGUID, REFGUID, HKL, DWORD); + +} ITfInputProcessorProfileActivationSinkVtbl; + +struct ITfInputProcessorProfileActivationSink +{ + const struct ITfInputProcessorProfileActivationSinkVtbl *lpVtbl; +}; + +typedef struct ITfSourceVtbl +{ + HRESULT (STDMETHODCALLTYPE *QueryInterface)(ITfSource *, REFIID, void **); + ULONG (STDMETHODCALLTYPE *AddRef)(ITfSource *); + ULONG (STDMETHODCALLTYPE *Release)(ITfSource *); + HRESULT (STDMETHODCALLTYPE *AdviseSink)(ITfSource *, REFIID, IUnknown *, DWORD *); + HRESULT (STDMETHODCALLTYPE *UnadviseSink)(ITfSource *, DWORD); +} ITfSourceVtbl; + +struct ITfSource +{ + const struct ITfSourceVtbl *lpVtbl; +}; + +/* *INDENT-ON* */ // clang-format on + +#endif // SDL_msctf_h_ diff --git a/contrib/SDL-3.2.8/src/video/windows/SDL_surface_utils.c b/contrib/SDL-3.2.8/src/video/windows/SDL_surface_utils.c new file mode 100644 index 0000000..7f9245c --- /dev/null +++ b/contrib/SDL-3.2.8/src/video/windows/SDL_surface_utils.c @@ -0,0 +1,97 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include "SDL_internal.h" + +#include "SDL_surface_utils.h" + +#include "../SDL_surface_c.h" + +#if !(defined(SDL_PLATFORM_XBOXONE) || defined(SDL_PLATFORM_XBOXSERIES)) +HICON CreateIconFromSurface(SDL_Surface *surface) +{ + SDL_Surface *s = SDL_ConvertSurface(surface, SDL_PIXELFORMAT_ARGB8888); + if (!s) { + return NULL; + } + + /* The dimensions will be needed after s is freed */ + const int width = s->w; + const int height = s->h; + + BITMAPINFO bmpInfo; + SDL_zero(bmpInfo); + bmpInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + bmpInfo.bmiHeader.biWidth = width; + bmpInfo.bmiHeader.biHeight = -height; /* Top-down bitmap */ + bmpInfo.bmiHeader.biPlanes = 1; + bmpInfo.bmiHeader.biBitCount = 32; + bmpInfo.bmiHeader.biCompression = BI_RGB; + + HDC hdc = GetDC(NULL); + void* pBits = NULL; + HBITMAP hBitmap = CreateDIBSection(hdc, &bmpInfo, DIB_RGB_COLORS, &pBits, NULL, 0); + if (!hBitmap) { + ReleaseDC(NULL, hdc); + SDL_DestroySurface(s); + return NULL; + } + + SDL_memcpy(pBits, s->pixels, width * height * 4); + + SDL_DestroySurface(s); + + HBITMAP hMask = CreateBitmap(width, height, 1, 1, NULL); + if (!hMask) { + DeleteObject(hBitmap); + ReleaseDC(NULL, hdc); + return NULL; + } + + HDC hdcMem = CreateCompatibleDC(hdc); + HGDIOBJ oldBitmap = SelectObject(hdcMem, hMask); + + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + BYTE* pixel = (BYTE*)pBits + (y * width + x) * 4; + BYTE alpha = pixel[3]; + COLORREF maskColor = (alpha == 0) ? RGB(0, 0, 0) : RGB(255, 255, 255); + SetPixel(hdcMem, x, y, maskColor); + } + } + + ICONINFO iconInfo; + iconInfo.fIcon = TRUE; + iconInfo.xHotspot = 0; + iconInfo.yHotspot = 0; + iconInfo.hbmMask = hMask; + iconInfo.hbmColor = hBitmap; + + HICON hIcon = CreateIconIndirect(&iconInfo); + + SelectObject(hdcMem, oldBitmap); + DeleteDC(hdcMem); + DeleteObject(hBitmap); + DeleteObject(hMask); + ReleaseDC(NULL, hdc); + + return hIcon; +} +#endif diff --git a/contrib/SDL-3.2.8/src/video/windows/SDL_surface_utils.h b/contrib/SDL-3.2.8/src/video/windows/SDL_surface_utils.h new file mode 100644 index 0000000..a793cdc --- /dev/null +++ b/contrib/SDL-3.2.8/src/video/windows/SDL_surface_utils.h @@ -0,0 +1,38 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include "SDL_internal.h" + +#ifndef SDL_surface_utils_h_ +#define SDL_surface_utils_h_ + +#include "../../core/windows/SDL_windows.h" + +#ifdef __cplusplus +extern "C" { +#endif + +extern HICON CreateIconFromSurface(SDL_Surface *surface); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/contrib/SDL-3.2.8/src/video/windows/SDL_windowsclipboard.c b/contrib/SDL-3.2.8/src/video/windows/SDL_windowsclipboard.c new file mode 100644 index 0000000..39f86ef --- /dev/null +++ b/contrib/SDL-3.2.8/src/video/windows/SDL_windowsclipboard.c @@ -0,0 +1,442 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include "SDL_internal.h" + +#if defined(SDL_VIDEO_DRIVER_WINDOWS) && !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) + +#include "SDL_windowsvideo.h" +#include "SDL_windowswindow.h" +#include "../SDL_clipboard_c.h" +#include "../../events/SDL_events_c.h" +#include "../../events/SDL_clipboardevents_c.h" + +#ifdef UNICODE +#define TEXT_FORMAT CF_UNICODETEXT +#else +#define TEXT_FORMAT CF_TEXT +#endif + +#define IMAGE_FORMAT CF_DIB +#define IMAGE_MIME_TYPE "image/bmp" +#define BFT_BITMAP 0x4d42 // 'BM' + +// Assume we can directly read and write BMP fields without byte swapping +SDL_COMPILE_TIME_ASSERT(verify_byte_order, SDL_BYTEORDER == SDL_LIL_ENDIAN); + +static BOOL WIN_OpenClipboard(SDL_VideoDevice *_this) +{ + // Retry to open the clipboard in case another application has it open + const int MAX_ATTEMPTS = 3; + int attempt; + HWND hwnd = NULL; + + if (_this->windows) { + hwnd = _this->windows->internal->hwnd; + } + for (attempt = 0; attempt < MAX_ATTEMPTS; ++attempt) { + if (OpenClipboard(hwnd)) { + return TRUE; + } + SDL_Delay(10); + } + return FALSE; +} + +static void WIN_CloseClipboard(void) +{ + CloseClipboard(); +} + +static HANDLE WIN_ConvertBMPtoDIB(const void *bmp, size_t bmp_size) +{ + HANDLE hMem = NULL; + + if (bmp && bmp_size > sizeof(BITMAPFILEHEADER) && ((BITMAPFILEHEADER *)bmp)->bfType == BFT_BITMAP) { + BITMAPFILEHEADER *pbfh = (BITMAPFILEHEADER *)bmp; + BITMAPINFOHEADER *pbih = (BITMAPINFOHEADER *)((Uint8 *)bmp + sizeof(BITMAPFILEHEADER)); + size_t bih_size = pbih->biSize + pbih->biClrUsed * sizeof(RGBQUAD); + size_t pixels_size = pbih->biSizeImage; + + if (pbfh->bfOffBits >= (sizeof(BITMAPFILEHEADER) + bih_size) && + (pbfh->bfOffBits + pixels_size) <= bmp_size) { + const Uint8 *pixels = (const Uint8 *)bmp + pbfh->bfOffBits; + size_t dib_size = bih_size + pixels_size; + hMem = GlobalAlloc(GMEM_MOVEABLE, dib_size); + if (hMem) { + LPVOID dst = GlobalLock(hMem); + if (dst) { + SDL_memcpy(dst, pbih, bih_size); + SDL_memcpy((Uint8 *)dst + bih_size, pixels, pixels_size); + GlobalUnlock(hMem); + } else { + WIN_SetError("GlobalLock()"); + GlobalFree(hMem); + hMem = NULL; + } + } else { + SDL_OutOfMemory(); + } + } else { + SDL_SetError("Invalid BMP data"); + } + } else { + SDL_SetError("Invalid BMP data"); + } + return hMem; +} + +static void *WIN_ConvertDIBtoBMP(HANDLE hMem, size_t *size) +{ + void *bmp = NULL; + size_t mem_size = GlobalSize(hMem); + + if (mem_size > sizeof(BITMAPINFOHEADER)) { + LPVOID dib = GlobalLock(hMem); + if (dib) { + BITMAPINFOHEADER *pbih = (BITMAPINFOHEADER *)dib; + + // https://learn.microsoft.com/en-us/windows/win32/api/wingdi/ns-wingdi-bitmapinfoheader#color-tables + size_t color_table_size; + switch (pbih->biCompression) { + case BI_RGB: + if (pbih->biBitCount <= 8) { + color_table_size = sizeof(RGBQUAD) * (pbih->biClrUsed == 0 ? 1 << pbih->biBitCount : pbih->biClrUsed); + } else { + color_table_size = 0; + } + break; + case BI_BITFIELDS: + color_table_size = 3 * sizeof(DWORD); + break; + case 6 /* BI_ALPHABITFIELDS */: + // https://learn.microsoft.com/en-us/previous-versions/windows/embedded/aa452885(v=msdn.10) + color_table_size = 4 * sizeof(DWORD); + break; + default: // FOURCC + color_table_size = sizeof(RGBQUAD) * pbih->biClrUsed; + } + + size_t bih_size = pbih->biSize + color_table_size; + size_t dib_size = bih_size + pbih->biSizeImage; + if (dib_size <= mem_size) { + size_t bmp_size = sizeof(BITMAPFILEHEADER) + mem_size; + bmp = SDL_malloc(bmp_size); + if (bmp) { + BITMAPFILEHEADER *pbfh = (BITMAPFILEHEADER *)bmp; + pbfh->bfType = BFT_BITMAP; + pbfh->bfSize = (DWORD)bmp_size; + pbfh->bfReserved1 = 0; + pbfh->bfReserved2 = 0; + pbfh->bfOffBits = (DWORD)(sizeof(BITMAPFILEHEADER) + pbih->biSize + color_table_size); + SDL_memcpy((Uint8 *)bmp + sizeof(BITMAPFILEHEADER), dib, dib_size); + *size = bmp_size; + } + } else { + SDL_SetError("Invalid BMP data"); + } + GlobalUnlock(hMem); + } else { + WIN_SetError("GlobalLock()"); + } + } else { + SDL_SetError("Invalid BMP data"); + } + return bmp; +} + +static bool WIN_SetClipboardImage(SDL_VideoDevice *_this) +{ + HANDLE hMem; + size_t clipboard_data_size; + const void *clipboard_data; + bool result = true; + + clipboard_data = _this->clipboard_callback(_this->clipboard_userdata, IMAGE_MIME_TYPE, &clipboard_data_size); + hMem = WIN_ConvertBMPtoDIB(clipboard_data, clipboard_data_size); + if (hMem) { + // Save the image to the clipboard + if (!SetClipboardData(IMAGE_FORMAT, hMem)) { + result = WIN_SetError("Couldn't set clipboard data"); + } + } else { + // WIN_ConvertBMPtoDIB() set the error + result = false; + } + return result; +} + +static bool WIN_SetClipboardText(SDL_VideoDevice *_this, const char *mime_type) +{ + HANDLE hMem; + size_t clipboard_data_size; + const void *clipboard_data; + bool result = true; + + clipboard_data = _this->clipboard_callback(_this->clipboard_userdata, mime_type, &clipboard_data_size); + if (clipboard_data && clipboard_data_size > 0) { + SIZE_T i, size; + LPTSTR tstr = (WCHAR *)SDL_iconv_string("UTF-16LE", "UTF-8", (const char *)clipboard_data, clipboard_data_size); + if (!tstr) { + return SDL_SetError("Couldn't convert text from UTF-8"); + } + + // Find out the size of the data + for (size = 0, i = 0; tstr[i]; ++i, ++size) { + if (tstr[i] == '\n' && (i == 0 || tstr[i - 1] != '\r')) { + // We're going to insert a carriage return + ++size; + } + } + size = (size + 1) * sizeof(*tstr); + + // Save the data to the clipboard + hMem = GlobalAlloc(GMEM_MOVEABLE, size); + if (hMem) { + LPTSTR dst = (LPTSTR)GlobalLock(hMem); + if (dst) { + // Copy the text over, adding carriage returns as necessary + for (i = 0; tstr[i]; ++i) { + if (tstr[i] == '\n' && (i == 0 || tstr[i - 1] != '\r')) { + *dst++ = '\r'; + } + *dst++ = tstr[i]; + } + *dst = 0; + GlobalUnlock(hMem); + } + + if (!SetClipboardData(TEXT_FORMAT, hMem)) { + result = WIN_SetError("Couldn't set clipboard data"); + } + } else { + result = SDL_OutOfMemory(); + } + SDL_free(tstr); + } + return result; +} + +bool WIN_SetClipboardData(SDL_VideoDevice *_this) +{ + SDL_VideoData *data = _this->internal; + size_t i; + bool result = true; + + /* I investigated delayed clipboard rendering, and at least with text and image + * formats you have to use an output window, not SDL_HelperWindow, and the system + * requests them being rendered immediately, so there isn't any benefit. + */ + + if (WIN_OpenClipboard(_this)) { + EmptyClipboard(); + + // Set the clipboard text + for (i = 0; i < _this->num_clipboard_mime_types; ++i) { + const char *mime_type = _this->clipboard_mime_types[i]; + + if (SDL_IsTextMimeType(mime_type)) { + if (!WIN_SetClipboardText(_this, mime_type)) { + result = false; + } + // Only set the first clipboard text + break; + } + } + + // Set the clipboard image + for (i = 0; i < _this->num_clipboard_mime_types; ++i) { + const char *mime_type = _this->clipboard_mime_types[i]; + + if (SDL_strcmp(mime_type, IMAGE_MIME_TYPE) == 0) { + if (!WIN_SetClipboardImage(_this)) { + result = false; + } + break; + } + } + + data->clipboard_count = GetClipboardSequenceNumber(); + WIN_CloseClipboard(); + } else { + result = WIN_SetError("Couldn't open clipboard"); + } + return result; +} + +void *WIN_GetClipboardData(SDL_VideoDevice *_this, const char *mime_type, size_t *size) +{ + void *data = NULL; + + if (SDL_IsTextMimeType(mime_type)) { + char *text = NULL; + + if (IsClipboardFormatAvailable(TEXT_FORMAT)) { + if (WIN_OpenClipboard(_this)) { + HANDLE hMem; + LPTSTR tstr; + + hMem = GetClipboardData(TEXT_FORMAT); + if (hMem) { + tstr = (LPTSTR)GlobalLock(hMem); + if (tstr) { + text = WIN_StringToUTF8(tstr); + GlobalUnlock(hMem); + } else { + WIN_SetError("Couldn't lock clipboard data"); + } + } else { + WIN_SetError("Couldn't get clipboard data"); + } + WIN_CloseClipboard(); + } + } + if (!text) { + text = SDL_strdup(""); + } + data = text; + *size = SDL_strlen(text); + + } else if (SDL_strcmp(mime_type, IMAGE_MIME_TYPE) == 0) { + if (IsClipboardFormatAvailable(IMAGE_FORMAT)) { + if (WIN_OpenClipboard(_this)) { + HANDLE hMem; + + hMem = GetClipboardData(IMAGE_FORMAT); + if (hMem) { + data = WIN_ConvertDIBtoBMP(hMem, size); + } else { + WIN_SetError("Couldn't get clipboard data"); + } + WIN_CloseClipboard(); + } + } + } else { + data = SDL_GetInternalClipboardData(_this, mime_type, size); + } + return data; +} + +bool WIN_HasClipboardData(SDL_VideoDevice *_this, const char *mime_type) +{ + if (SDL_IsTextMimeType(mime_type)) { + if (IsClipboardFormatAvailable(TEXT_FORMAT)) { + return true; + } + } else if (SDL_strcmp(mime_type, IMAGE_MIME_TYPE) == 0) { + if (IsClipboardFormatAvailable(IMAGE_FORMAT)) { + return true; + } + } else { + if (SDL_HasInternalClipboardData(_this, mime_type)) { + return true; + } + } + return false; +} + +static int GetClipboardFormatMimeType(UINT format, char *name) +{ + static struct + { + UINT format; + const char *mime_type; + } mime_types[] = { + { TEXT_FORMAT, "text/plain;charset=utf-8" }, + { IMAGE_FORMAT, IMAGE_MIME_TYPE }, + }; + + for (int i = 0; i < SDL_arraysize(mime_types); ++i) { + if (format == mime_types[i].format) { + size_t len = SDL_strlen(mime_types[i].mime_type) + 1; + if (name) { + SDL_memcpy(name, mime_types[i].mime_type, len); + } + return (int)len; + } + } + return 0; +} + +static char **GetMimeTypes(int *pnformats) +{ + char **new_mime_types = NULL; + + *pnformats = 0; + + if (WIN_OpenClipboard(SDL_GetVideoDevice())) { + int nformats = 0; + UINT format = 0; + int formatsSz = 0; + for ( ; ; ) { + format = EnumClipboardFormats(format); + if (!format) { + break; + } + + int len = GetClipboardFormatMimeType(format, NULL); + if (len > 0) { + ++nformats; + formatsSz += len; + } + } + + new_mime_types = SDL_AllocateTemporaryMemory((nformats + 1) * sizeof(char *) + formatsSz); + if (new_mime_types) { + format = 0; + char *strPtr = (char *)(new_mime_types + nformats + 1); + int i = 0; + for ( ; ; ) { + format = EnumClipboardFormats(format); + if (!format) { + break; + } + + int len = GetClipboardFormatMimeType(format, strPtr); + if (len > 0) { + new_mime_types[i++] = strPtr; + strPtr += len; + } + } + + new_mime_types[nformats] = NULL; + *pnformats = nformats; + } + WIN_CloseClipboard(); + } + return new_mime_types; +} + +void WIN_CheckClipboardUpdate(struct SDL_VideoData *data) +{ + DWORD count = GetClipboardSequenceNumber(); + if (count != data->clipboard_count) { + if (count) { + int nformats = 0; + char **new_mime_types = GetMimeTypes(&nformats); + if (new_mime_types) { + SDL_SendClipboardUpdate(false, new_mime_types, nformats); + } + } + data->clipboard_count = count; + } +} + +#endif // SDL_VIDEO_DRIVER_WINDOWS diff --git a/contrib/SDL-3.2.8/src/video/windows/SDL_windowsclipboard.h b/contrib/SDL-3.2.8/src/video/windows/SDL_windowsclipboard.h new file mode 100644 index 0000000..4b14ffe --- /dev/null +++ b/contrib/SDL-3.2.8/src/video/windows/SDL_windowsclipboard.h @@ -0,0 +1,34 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include "SDL_internal.h" + +#ifndef SDL_windowsclipboard_h_ +#define SDL_windowsclipboard_h_ + +// Forward declaration +struct SDL_VideoData; + +extern bool WIN_SetClipboardData(SDL_VideoDevice *_this); +extern void *WIN_GetClipboardData(SDL_VideoDevice *_this, const char *mime_type, size_t *size); +extern bool WIN_HasClipboardData(SDL_VideoDevice *_this, const char *mime_type); +extern void WIN_CheckClipboardUpdate(struct SDL_VideoData *data); + +#endif // SDL_windowsclipboard_h_ diff --git a/contrib/SDL-3.2.8/src/video/windows/SDL_windowsevents.c b/contrib/SDL-3.2.8/src/video/windows/SDL_windowsevents.c new file mode 100644 index 0000000..f271db8 --- /dev/null +++ b/contrib/SDL-3.2.8/src/video/windows/SDL_windowsevents.c @@ -0,0 +1,2734 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include "SDL_internal.h" + +#ifdef SDL_VIDEO_DRIVER_WINDOWS + +#include "SDL_windowsvideo.h" +#include "../../events/SDL_events_c.h" +#include "../../events/SDL_touch_c.h" +#include "../../events/scancodes_windows.h" +#include "../../main/SDL_main_callbacks.h" +#include "../../core/windows/SDL_hid.h" + +// Dropfile support +#include + +// Device names +#include + +// For GET_X_LPARAM, GET_Y_LPARAM. +#include + +// For WM_TABLET_QUERYSYSTEMGESTURESTATUS et. al. +#ifdef HAVE_TPCSHRD_H +#include +#endif // HAVE_TPCSHRD_H + +#if 0 +#define WMMSG_DEBUG +#endif +#ifdef WMMSG_DEBUG +#include +#include "wmmsg.h" +#endif + +#ifdef SDL_PLATFORM_GDK +#include "../../core/gdk/SDL_gdk.h" +#endif + +// #define HIGHDPI_DEBUG + +// Make sure XBUTTON stuff is defined that isn't in older Platform SDKs... +#ifndef WM_XBUTTONDOWN +#define WM_XBUTTONDOWN 0x020B +#endif +#ifndef WM_XBUTTONUP +#define WM_XBUTTONUP 0x020C +#endif +#ifndef GET_XBUTTON_WPARAM +#define GET_XBUTTON_WPARAM(w) (HIWORD(w)) +#endif +#ifndef WM_INPUT +#define WM_INPUT 0x00ff +#endif +#ifndef WM_TOUCH +#define WM_TOUCH 0x0240 +#endif +#ifndef WM_MOUSEHWHEEL +#define WM_MOUSEHWHEEL 0x020E +#endif +#ifndef RI_MOUSE_HWHEEL +#define RI_MOUSE_HWHEEL 0x0800 +#endif +#ifndef WM_POINTERUPDATE +#define WM_POINTERUPDATE 0x0245 +#endif +#ifndef WM_POINTERDOWN +#define WM_POINTERDOWN 0x0246 +#endif +#ifndef WM_POINTERUP +#define WM_POINTERUP 0x0247 +#endif +#ifndef WM_POINTERENTER +#define WM_POINTERENTER 0x0249 +#endif +#ifndef WM_POINTERLEAVE +#define WM_POINTERLEAVE 0x024A +#endif +#ifndef WM_POINTERCAPTURECHANGED +#define WM_POINTERCAPTURECHANGED 0x024C +#endif +#ifndef WM_UNICHAR +#define WM_UNICHAR 0x0109 +#endif +#ifndef WM_DPICHANGED +#define WM_DPICHANGED 0x02E0 +#endif +#ifndef WM_GETDPISCALEDSIZE +#define WM_GETDPISCALEDSIZE 0x02E4 +#endif +#ifndef TOUCHEVENTF_PEN +#define TOUCHEVENTF_PEN 0x0040 +#endif + +#ifndef MAPVK_VK_TO_VSC_EX +#define MAPVK_VK_TO_VSC_EX 4 +#endif + +#ifndef WC_ERR_INVALID_CHARS +#define WC_ERR_INVALID_CHARS 0x00000080 +#endif + +#ifndef IS_HIGH_SURROGATE +#define IS_HIGH_SURROGATE(x) (((x) >= 0xd800) && ((x) <= 0xdbff)) +#endif + +#ifndef USER_TIMER_MINIMUM +#define USER_TIMER_MINIMUM 0x0000000A +#endif + +// Used to compare Windows message timestamps +#define SDL_TICKS_PASSED(A, B) ((Sint32)((B) - (A)) <= 0) + +#ifdef _WIN64 +typedef Uint64 QWORD; // Needed for NEXTRAWINPUTBLOCK() +#endif + +static bool SDL_processing_messages; +static DWORD message_tick; +static Uint64 timestamp_offset; + +static void WIN_SetMessageTick(DWORD tick) +{ + message_tick = tick; +} + +static Uint64 WIN_GetEventTimestamp(void) +{ + const Uint64 TIMESTAMP_WRAP_OFFSET = SDL_MS_TO_NS(0x100000000LL); + Uint64 timestamp, now; + + if (!SDL_processing_messages) { + // message_tick isn't valid, just use the current time + return 0; + } + + now = SDL_GetTicksNS(); + timestamp = SDL_MS_TO_NS(message_tick); + timestamp += timestamp_offset; + if (!timestamp_offset) { + // Initializing timestamp offset + //SDL_Log("Initializing timestamp offset"); + timestamp_offset = (now - timestamp); + timestamp = now; + } else if ((Sint64)(now - timestamp - TIMESTAMP_WRAP_OFFSET) >= 0) { + // The windows message tick wrapped + //SDL_Log("Adjusting timestamp offset for wrapping tick"); + timestamp_offset += TIMESTAMP_WRAP_OFFSET; + timestamp += TIMESTAMP_WRAP_OFFSET; + } else if (timestamp > now) { + // We got a newer timestamp, but it can't be newer than now, so adjust our offset + //SDL_Log("Adjusting timestamp offset, %.2f ms newer", (double)(timestamp - now) / SDL_NS_PER_MS); + timestamp_offset -= (timestamp - now); + timestamp = now; + } + return timestamp; +} + +// A message hook called before TranslateMessage() +static SDL_WindowsMessageHook g_WindowsMessageHook = NULL; +static void *g_WindowsMessageHookData = NULL; + +void SDL_SetWindowsMessageHook(SDL_WindowsMessageHook callback, void *userdata) +{ + g_WindowsMessageHook = callback; + g_WindowsMessageHookData = userdata; +} + +static SDL_Scancode WindowsScanCodeToSDLScanCode(LPARAM lParam, WPARAM wParam, Uint16 *rawcode, bool *virtual_key) +{ + SDL_Scancode code; + Uint8 index; + Uint16 keyFlags = HIWORD(lParam); + Uint16 scanCode = LOBYTE(keyFlags); + + /* On-Screen Keyboard can send wrong scan codes with high-order bit set (key break code). + * Strip high-order bit. */ + scanCode &= ~0x80; + + *virtual_key = (scanCode == 0); + + if (scanCode != 0) { + if ((keyFlags & KF_EXTENDED) == KF_EXTENDED) { + scanCode = MAKEWORD(scanCode, 0xe0); + } else if (scanCode == 0x45) { + // Pause + scanCode = 0xe046; + } + } else { + Uint16 vkCode = LOWORD(wParam); + +#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) + /* Windows may not report scan codes for some buttons (multimedia buttons etc). + * Get scan code from the VK code.*/ + scanCode = LOWORD(MapVirtualKey(vkCode, WIN_IsWindowsXP() ? MAPVK_VK_TO_VSC : MAPVK_VK_TO_VSC_EX)); +#endif // !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) + + /* Pause/Break key have a special scan code with 0xe1 prefix. + * Use Pause scan code that is used in Win32. */ + if (scanCode == 0xe11d) { + scanCode = 0xe046; + } + } + + // Pack scan code into one byte to make the index. + index = LOBYTE(scanCode) | (HIBYTE(scanCode) ? 0x80 : 0x00); + code = windows_scancode_table[index]; + *rawcode = scanCode; + + return code; +} + +#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) +static bool WIN_ShouldIgnoreFocusClick(SDL_WindowData *data) +{ + return !SDL_WINDOW_IS_POPUP(data->window) && + !SDL_GetHintBoolean(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, false); +} + +static void WIN_CheckWParamMouseButton(Uint64 timestamp, bool bwParamMousePressed, Uint32 mouseFlags, bool bSwapButtons, SDL_WindowData *data, Uint8 button, SDL_MouseID mouseID) +{ + if (bSwapButtons) { + if (button == SDL_BUTTON_LEFT) { + button = SDL_BUTTON_RIGHT; + } else if (button == SDL_BUTTON_RIGHT) { + button = SDL_BUTTON_LEFT; + } + } + + if (data->focus_click_pending & SDL_BUTTON_MASK(button)) { + // Ignore the button click for activation + if (!bwParamMousePressed) { + data->focus_click_pending &= ~SDL_BUTTON_MASK(button); + WIN_UpdateClipCursor(data->window); + } + return; + } + + if (bwParamMousePressed && !(mouseFlags & SDL_BUTTON_MASK(button))) { + SDL_SendMouseButton(timestamp, data->window, mouseID, button, true); + } else if (!bwParamMousePressed && (mouseFlags & SDL_BUTTON_MASK(button))) { + SDL_SendMouseButton(timestamp, data->window, mouseID, button, false); + } +} + +/* + * Some windows systems fail to send a WM_LBUTTONDOWN sometimes, but each mouse move contains the current button state also + * so this function reconciles our view of the world with the current buttons reported by windows + */ +static void WIN_CheckWParamMouseButtons(Uint64 timestamp, WPARAM wParam, SDL_WindowData *data, SDL_MouseID mouseID) +{ + if (wParam != data->mouse_button_flags) { + SDL_MouseButtonFlags mouseFlags = SDL_GetMouseState(NULL, NULL); + + // WM_LBUTTONDOWN and friends handle button swapping for us. No need to check SM_SWAPBUTTON here. + WIN_CheckWParamMouseButton(timestamp, (wParam & MK_LBUTTON), mouseFlags, false, data, SDL_BUTTON_LEFT, mouseID); + WIN_CheckWParamMouseButton(timestamp, (wParam & MK_MBUTTON), mouseFlags, false, data, SDL_BUTTON_MIDDLE, mouseID); + WIN_CheckWParamMouseButton(timestamp, (wParam & MK_RBUTTON), mouseFlags, false, data, SDL_BUTTON_RIGHT, mouseID); + WIN_CheckWParamMouseButton(timestamp, (wParam & MK_XBUTTON1), mouseFlags, false, data, SDL_BUTTON_X1, mouseID); + WIN_CheckWParamMouseButton(timestamp, (wParam & MK_XBUTTON2), mouseFlags, false, data, SDL_BUTTON_X2, mouseID); + + data->mouse_button_flags = wParam; + } +} + +static void WIN_CheckAsyncMouseRelease(Uint64 timestamp, SDL_WindowData *data) +{ + SDL_MouseID mouseID = SDL_GLOBAL_MOUSE_ID; + Uint32 mouseFlags; + SHORT keyState; + bool swapButtons; + + /* mouse buttons may have changed state here, we need to resync them, + but we will get a WM_MOUSEMOVE right away which will fix things up if in non raw mode also + */ + mouseFlags = SDL_GetMouseState(NULL, NULL); + swapButtons = GetSystemMetrics(SM_SWAPBUTTON) != 0; + + keyState = GetAsyncKeyState(VK_LBUTTON); + if (!(keyState & 0x8000)) { + WIN_CheckWParamMouseButton(timestamp, false, mouseFlags, swapButtons, data, SDL_BUTTON_LEFT, mouseID); + } + keyState = GetAsyncKeyState(VK_RBUTTON); + if (!(keyState & 0x8000)) { + WIN_CheckWParamMouseButton(timestamp, false, mouseFlags, swapButtons, data, SDL_BUTTON_RIGHT, mouseID); + } + keyState = GetAsyncKeyState(VK_MBUTTON); + if (!(keyState & 0x8000)) { + WIN_CheckWParamMouseButton(timestamp, false, mouseFlags, swapButtons, data, SDL_BUTTON_MIDDLE, mouseID); + } + keyState = GetAsyncKeyState(VK_XBUTTON1); + if (!(keyState & 0x8000)) { + WIN_CheckWParamMouseButton(timestamp, false, mouseFlags, swapButtons, data, SDL_BUTTON_X1, mouseID); + } + keyState = GetAsyncKeyState(VK_XBUTTON2); + if (!(keyState & 0x8000)) { + WIN_CheckWParamMouseButton(timestamp, false, mouseFlags, swapButtons, data, SDL_BUTTON_X2, mouseID); + } + data->mouse_button_flags = (WPARAM)-1; +} + +static void WIN_UpdateMouseCapture(void) +{ + SDL_Window *focusWindow = SDL_GetKeyboardFocus(); + + if (focusWindow && (focusWindow->flags & SDL_WINDOW_MOUSE_CAPTURE)) { + SDL_WindowData *data = focusWindow->internal; + + if (!data->mouse_tracked) { + POINT cursorPos; + + if (GetCursorPos(&cursorPos) && ScreenToClient(data->hwnd, &cursorPos)) { + bool swapButtons = GetSystemMetrics(SM_SWAPBUTTON) != 0; + SDL_MouseID mouseID = SDL_GLOBAL_MOUSE_ID; + + SDL_SendMouseMotion(WIN_GetEventTimestamp(), data->window, mouseID, false, (float)cursorPos.x, (float)cursorPos.y); + SDL_SendMouseButton(WIN_GetEventTimestamp(), data->window, mouseID, + !swapButtons ? SDL_BUTTON_LEFT : SDL_BUTTON_RIGHT, + (GetAsyncKeyState(VK_LBUTTON) & 0x8000) != 0); + SDL_SendMouseButton(WIN_GetEventTimestamp(), data->window, mouseID, + !swapButtons ? SDL_BUTTON_RIGHT : SDL_BUTTON_LEFT, + (GetAsyncKeyState(VK_RBUTTON) & 0x8000) != 0); + SDL_SendMouseButton(WIN_GetEventTimestamp(), data->window, mouseID, + SDL_BUTTON_MIDDLE, + (GetAsyncKeyState(VK_MBUTTON) & 0x8000) != 0); + SDL_SendMouseButton(WIN_GetEventTimestamp(), data->window, mouseID, + SDL_BUTTON_X1, + (GetAsyncKeyState(VK_XBUTTON1) & 0x8000) != 0); + SDL_SendMouseButton(WIN_GetEventTimestamp(), data->window, mouseID, + SDL_BUTTON_X2, + (GetAsyncKeyState(VK_XBUTTON2) & 0x8000) != 0); + } + } + } +} + +static void WIN_UpdateFocus(SDL_Window *window, bool expect_focus) +{ + SDL_WindowData *data = window->internal; + HWND hwnd = data->hwnd; + bool had_focus = (SDL_GetKeyboardFocus() == window); + bool has_focus = (GetForegroundWindow() == hwnd); + + if (had_focus == has_focus || has_focus != expect_focus) { + return; + } + + if (has_focus) { + POINT cursorPos; + + if (WIN_ShouldIgnoreFocusClick(data) && !(window->flags & SDL_WINDOW_MOUSE_CAPTURE)) { + bool swapButtons = GetSystemMetrics(SM_SWAPBUTTON) != 0; + if (GetAsyncKeyState(VK_LBUTTON)) { + data->focus_click_pending |= !swapButtons ? SDL_BUTTON_LMASK : SDL_BUTTON_RMASK; + } + if (GetAsyncKeyState(VK_RBUTTON)) { + data->focus_click_pending |= !swapButtons ? SDL_BUTTON_RMASK : SDL_BUTTON_LMASK; + } + if (GetAsyncKeyState(VK_MBUTTON)) { + data->focus_click_pending |= SDL_BUTTON_MMASK; + } + if (GetAsyncKeyState(VK_XBUTTON1)) { + data->focus_click_pending |= SDL_BUTTON_X1MASK; + } + if (GetAsyncKeyState(VK_XBUTTON2)) { + data->focus_click_pending |= SDL_BUTTON_X2MASK; + } + } + + SDL_SetKeyboardFocus(data->keyboard_focus ? data->keyboard_focus : window); + + // In relative mode we are guaranteed to have mouse focus if we have keyboard focus + if (!SDL_GetMouse()->relative_mode) { + GetCursorPos(&cursorPos); + ScreenToClient(hwnd, &cursorPos); + SDL_SendMouseMotion(WIN_GetEventTimestamp(), window, SDL_GLOBAL_MOUSE_ID, false, (float)cursorPos.x, (float)cursorPos.y); + } + + WIN_CheckAsyncMouseRelease(WIN_GetEventTimestamp(), data); + WIN_UpdateClipCursor(window); + + /* + * FIXME: Update keyboard state + */ + WIN_CheckClipboardUpdate(data->videodata); + + SDL_ToggleModState(SDL_KMOD_CAPS, (GetKeyState(VK_CAPITAL) & 0x0001) ? true : false); + SDL_ToggleModState(SDL_KMOD_NUM, (GetKeyState(VK_NUMLOCK) & 0x0001) ? true : false); + SDL_ToggleModState(SDL_KMOD_SCROLL, (GetKeyState(VK_SCROLL) & 0x0001) ? true : false); + + WIN_UpdateWindowICCProfile(data->window, true); + } else { + data->in_window_deactivation = true; + + SDL_SetKeyboardFocus(NULL); + // In relative mode we are guaranteed to not have mouse focus if we don't have keyboard focus + if (SDL_GetMouse()->relative_mode) { + SDL_SetMouseFocus(NULL); + } + WIN_ResetDeadKeys(); + + WIN_UnclipCursorForWindow(window); + + data->in_window_deactivation = false; + } +} +#endif // !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) + +static bool ShouldGenerateWindowCloseOnAltF4(void) +{ + return SDL_GetHintBoolean(SDL_HINT_WINDOWS_CLOSE_ON_ALT_F4, true); +} + +static bool ShouldClearWindowOnEraseBackground(SDL_WindowData *data) +{ + switch (data->hint_erase_background_mode) { + case SDL_ERASEBACKGROUNDMODE_NEVER: + return false; + case SDL_ERASEBACKGROUNDMODE_INITIAL: + return !data->videodata->cleared; + case SDL_ERASEBACKGROUNDMODE_ALWAYS: + return true; + default: + // Unexpected value, fallback to default behaviour + return !data->videodata->cleared; + } +} + +#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) +// We want to generate mouse events from mouse and pen, and touch events from touchscreens +#define MI_WP_SIGNATURE 0xFF515700 +#define MI_WP_SIGNATURE_MASK 0xFFFFFF00 +#define IsTouchEvent(dw) ((dw)&MI_WP_SIGNATURE_MASK) == MI_WP_SIGNATURE + +typedef enum +{ + SDL_MOUSE_EVENT_SOURCE_UNKNOWN, + SDL_MOUSE_EVENT_SOURCE_MOUSE, + SDL_MOUSE_EVENT_SOURCE_TOUCH, + SDL_MOUSE_EVENT_SOURCE_PEN, +} SDL_MOUSE_EVENT_SOURCE; + +static SDL_MOUSE_EVENT_SOURCE GetMouseMessageSource(ULONG extrainfo) +{ + // Mouse data (ignoring synthetic mouse events generated for touchscreens) + /* Versions below Vista will set the low 7 bits to the Mouse ID and don't use bit 7: + Check bits 8-31 for the signature (which will indicate a Tablet PC Pen or Touch Device). + Only check bit 7 when Vista and up(Cleared=Pen, Set=Touch(which we need to filter out)), + when the signature is set. The Mouse ID will be zero for an actual mouse. */ + if (IsTouchEvent(extrainfo)) { + if (extrainfo & 0x80) { + return SDL_MOUSE_EVENT_SOURCE_TOUCH; + } else { + return SDL_MOUSE_EVENT_SOURCE_PEN; + } + } + /* Sometimes WM_INPUT events won't have the correct touch signature, + so we have to rely purely on the touch bit being set. */ + if (SDL_TouchDevicesAvailable() && extrainfo & 0x80) { + return SDL_MOUSE_EVENT_SOURCE_TOUCH; + } + return SDL_MOUSE_EVENT_SOURCE_MOUSE; +} +#endif // !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) + +static SDL_WindowData *WIN_GetWindowDataFromHWND(HWND hwnd) +{ + SDL_VideoDevice *_this = SDL_GetVideoDevice(); + SDL_Window *window; + + if (_this) { + for (window = _this->windows; window; window = window->next) { + SDL_WindowData *data = window->internal; + if (data && data->hwnd == hwnd) { + return data; + } + } + } + return NULL; +} + +#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) +LRESULT CALLBACK +WIN_KeyboardHookProc(int nCode, WPARAM wParam, LPARAM lParam) +{ + KBDLLHOOKSTRUCT *hookData = (KBDLLHOOKSTRUCT *)lParam; + SDL_VideoData *data = SDL_GetVideoDevice()->internal; + SDL_Scancode scanCode; + + if (nCode < 0 || nCode != HC_ACTION) { + return CallNextHookEx(NULL, nCode, wParam, lParam); + } + if (hookData->scanCode == 0x21d) { + // Skip fake LCtrl when RAlt is pressed + return 1; + } + + switch (hookData->vkCode) { + case VK_LWIN: + scanCode = SDL_SCANCODE_LGUI; + break; + case VK_RWIN: + scanCode = SDL_SCANCODE_RGUI; + break; + case VK_LMENU: + scanCode = SDL_SCANCODE_LALT; + break; + case VK_RMENU: + scanCode = SDL_SCANCODE_RALT; + break; + case VK_LCONTROL: + scanCode = SDL_SCANCODE_LCTRL; + break; + case VK_RCONTROL: + scanCode = SDL_SCANCODE_RCTRL; + break; + + // These are required to intercept Alt+Tab and Alt+Esc on Windows 7 + case VK_TAB: + scanCode = SDL_SCANCODE_TAB; + break; + case VK_ESCAPE: + scanCode = SDL_SCANCODE_ESCAPE; + break; + + default: + return CallNextHookEx(NULL, nCode, wParam, lParam); + } + + if (wParam == WM_KEYDOWN || wParam == WM_SYSKEYDOWN) { + if (!data->raw_keyboard_enabled) { + SDL_SendKeyboardKey(0, SDL_GLOBAL_KEYBOARD_ID, hookData->scanCode, scanCode, true); + } + } else { + if (!data->raw_keyboard_enabled) { + SDL_SendKeyboardKey(0, SDL_GLOBAL_KEYBOARD_ID, hookData->scanCode, scanCode, false); + } + + /* If the key was down prior to our hook being installed, allow the + key up message to pass normally the first time. This ensures other + windows have a consistent view of the key state, and avoids keys + being stuck down in those windows if they are down when the grab + happens and raised while grabbed. */ + if (hookData->vkCode <= 0xFF && data->pre_hook_key_state[hookData->vkCode]) { + data->pre_hook_key_state[hookData->vkCode] = 0; + return CallNextHookEx(NULL, nCode, wParam, lParam); + } + } + + return 1; +} + +static bool WIN_SwapButtons(HANDLE hDevice) +{ + if (hDevice == NULL) { + // Touchpad, already has buttons swapped + return false; + } + return GetSystemMetrics(SM_SWAPBUTTON) != 0; +} + +static void WIN_HandleRawMouseInput(Uint64 timestamp, SDL_VideoData *data, HANDLE hDevice, RAWMOUSE *rawmouse) +{ + static struct { + USHORT usButtonFlags; + Uint8 button; + bool down; + } raw_buttons[] = { + { RI_MOUSE_LEFT_BUTTON_DOWN, SDL_BUTTON_LEFT, true }, + { RI_MOUSE_LEFT_BUTTON_UP, SDL_BUTTON_LEFT, false }, + { RI_MOUSE_RIGHT_BUTTON_DOWN, SDL_BUTTON_RIGHT, true }, + { RI_MOUSE_RIGHT_BUTTON_UP, SDL_BUTTON_RIGHT, false }, + { RI_MOUSE_MIDDLE_BUTTON_DOWN, SDL_BUTTON_MIDDLE, true }, + { RI_MOUSE_MIDDLE_BUTTON_UP, SDL_BUTTON_MIDDLE, false }, + { RI_MOUSE_BUTTON_4_DOWN, SDL_BUTTON_X1, true }, + { RI_MOUSE_BUTTON_4_UP, SDL_BUTTON_X1, false }, + { RI_MOUSE_BUTTON_5_DOWN, SDL_BUTTON_X2, true }, + { RI_MOUSE_BUTTON_5_UP, SDL_BUTTON_X2, false } + }; + + int dx = (int)rawmouse->lLastX; + int dy = (int)rawmouse->lLastY; + bool haveMotion = (dx || dy); + bool haveButton = (rawmouse->usButtonFlags != 0); + bool isAbsolute = ((rawmouse->usFlags & MOUSE_MOVE_ABSOLUTE) != 0); + SDL_MouseID mouseID = (SDL_MouseID)(uintptr_t)hDevice; + + // Check whether relative mode should also receive events from the rawinput stream + if (!data->raw_mouse_enabled) { + return; + } + + // Relative mouse motion is delivered to the window with keyboard focus + SDL_Window *window = SDL_GetKeyboardFocus(); + if (!window) { + return; + } + + if (GetMouseMessageSource(rawmouse->ulExtraInformation) != SDL_MOUSE_EVENT_SOURCE_MOUSE) { + return; + } + + SDL_WindowData *windowdata = window->internal; + + if (haveMotion) { + if (!isAbsolute) { + SDL_SendMouseMotion(timestamp, window, mouseID, true, (float)dx, (float)dy); + } else { + /* This is absolute motion, either using a tablet or mouse over RDP + + Notes on how RDP appears to work, as of Windows 10 2004: + - SetCursorPos() calls are cached, with multiple calls coalesced into a single call that's sent to the RDP client. If the last call to SetCursorPos() has the same value as the last one that was sent to the client, it appears to be ignored and not sent. This means that we need to jitter the SetCursorPos() position slightly in order for the recentering to work correctly. + - User mouse motion is coalesced with SetCursorPos(), so the WM_INPUT positions we see will not necessarily match the position we requested with SetCursorPos(). + - SetCursorPos() outside of the bounds of the focus window appears not to do anything. + - SetCursorPos() while the cursor is NULL doesn't do anything + + We handle this by creating a safe area within the application window, and when the mouse leaves that safe area, we warp back to the opposite side. Any single motion > 50% of the safe area is assumed to be a warp and ignored. + */ + bool remote_desktop = (GetSystemMetrics(SM_REMOTESESSION) == TRUE); + bool virtual_desktop = ((rawmouse->usFlags & MOUSE_VIRTUAL_DESKTOP) != 0); + bool raw_coordinates = ((rawmouse->usFlags & 0x40) != 0); + int w = GetSystemMetrics(virtual_desktop ? SM_CXVIRTUALSCREEN : SM_CXSCREEN); + int h = GetSystemMetrics(virtual_desktop ? SM_CYVIRTUALSCREEN : SM_CYSCREEN); + int x = raw_coordinates ? dx : (int)(((float)dx / 65535.0f) * w); + int y = raw_coordinates ? dy : (int)(((float)dy / 65535.0f) * h); + int relX, relY; + + /* Calculate relative motion */ + if (data->last_raw_mouse_position.x == 0 && data->last_raw_mouse_position.y == 0) { + data->last_raw_mouse_position.x = x; + data->last_raw_mouse_position.y = y; + } + relX = x - data->last_raw_mouse_position.x; + relY = y - data->last_raw_mouse_position.y; + + if (remote_desktop) { + if (!windowdata->in_title_click && !windowdata->focus_click_pending) { + static int wobble; + float floatX = (float)x / w; + float floatY = (float)y / h; + + /* See if the mouse is at the edge of the screen, or in the RDP title bar area */ + if (floatX <= 0.01f || floatX >= 0.99f || floatY <= 0.01f || floatY >= 0.99f || y < 32) { + /* Wobble the cursor position so it's not ignored if the last warp didn't have any effect */ + RECT rect = windowdata->cursor_clipped_rect; + int warpX = rect.left + ((rect.right - rect.left) / 2) + wobble; + int warpY = rect.top + ((rect.bottom - rect.top) / 2); + + WIN_SetCursorPos(warpX, warpY); + + ++wobble; + if (wobble > 1) { + wobble = -1; + } + } else { + /* Send relative motion if we didn't warp last frame (had good position data) + We also sometimes get large deltas due to coalesced mouse motion and warping, + so ignore those. + */ + const int MAX_RELATIVE_MOTION = (h / 6); + if (SDL_abs(relX) < MAX_RELATIVE_MOTION && + SDL_abs(relY) < MAX_RELATIVE_MOTION) { + SDL_SendMouseMotion(timestamp, window, mouseID, true, (float)relX, (float)relY); + } + } + } + } else { + const int MAXIMUM_TABLET_RELATIVE_MOTION = 32; + if (SDL_abs(relX) > MAXIMUM_TABLET_RELATIVE_MOTION || + SDL_abs(relY) > MAXIMUM_TABLET_RELATIVE_MOTION) { + /* Ignore this motion, probably a pen lift and drop */ + } else { + SDL_SendMouseMotion(timestamp, window, mouseID, true, (float)relX, (float)relY); + } + } + + data->last_raw_mouse_position.x = x; + data->last_raw_mouse_position.y = y; + } + } + + if (haveButton) { + for (int i = 0; i < SDL_arraysize(raw_buttons); ++i) { + if (rawmouse->usButtonFlags & raw_buttons[i].usButtonFlags) { + Uint8 button = raw_buttons[i].button; + bool down = raw_buttons[i].down; + + if (button == SDL_BUTTON_LEFT) { + if (WIN_SwapButtons(hDevice)) { + button = SDL_BUTTON_RIGHT; + } + } else if (button == SDL_BUTTON_RIGHT) { + if (WIN_SwapButtons(hDevice)) { + button = SDL_BUTTON_LEFT; + } + } + + if (windowdata->focus_click_pending & SDL_BUTTON_MASK(button)) { + // Ignore the button click for activation + if (!down) { + windowdata->focus_click_pending &= ~SDL_BUTTON_MASK(button); + WIN_UpdateClipCursor(window); + } + continue; + } + + SDL_SendMouseButton(timestamp, window, mouseID, button, down); + } + } + + if (rawmouse->usButtonFlags & RI_MOUSE_WHEEL) { + SHORT amount = (SHORT)rawmouse->usButtonData; + float fAmount = (float)amount / WHEEL_DELTA; + SDL_SendMouseWheel(WIN_GetEventTimestamp(), window, mouseID, 0.0f, fAmount, SDL_MOUSEWHEEL_NORMAL); + } else if (rawmouse->usButtonFlags & RI_MOUSE_HWHEEL) { + SHORT amount = (SHORT)rawmouse->usButtonData; + float fAmount = (float)amount / WHEEL_DELTA; + SDL_SendMouseWheel(WIN_GetEventTimestamp(), window, mouseID, fAmount, 0.0f, SDL_MOUSEWHEEL_NORMAL); + } + } +} + +static void WIN_HandleRawKeyboardInput(Uint64 timestamp, SDL_VideoData *data, HANDLE hDevice, RAWKEYBOARD *rawkeyboard) +{ + SDL_KeyboardID keyboardID = (SDL_KeyboardID)(uintptr_t)hDevice; + + if (!data->raw_keyboard_enabled) { + return; + } + + if (rawkeyboard->Flags & RI_KEY_E1) { + // First key in a Ctrl+{key} sequence + data->pending_E1_key_sequence = true; + return; + } + + if ((rawkeyboard->Flags & RI_KEY_E0) && rawkeyboard->MakeCode == 0x2A) { + // 0xE02A make code prefix, ignored + return; + } + +#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) + if (!rawkeyboard->MakeCode) { + rawkeyboard->MakeCode = LOWORD(MapVirtualKey(rawkeyboard->VKey, WIN_IsWindowsXP() ? MAPVK_VK_TO_VSC : MAPVK_VK_TO_VSC_EX)); + } +#endif + if (!rawkeyboard->MakeCode) { + return; + } + + bool down = !(rawkeyboard->Flags & RI_KEY_BREAK); + SDL_Scancode code; + USHORT rawcode = rawkeyboard->MakeCode; + if (data->pending_E1_key_sequence) { + rawcode |= 0xE100; + if (rawkeyboard->MakeCode == 0x45) { + // Ctrl+NumLock == Pause + code = SDL_SCANCODE_PAUSE; + } else { + // Ctrl+ScrollLock == Break (no SDL scancode?) + code = SDL_SCANCODE_UNKNOWN; + } + data->pending_E1_key_sequence = false; + } else { + // The code is in the lower 7 bits, the high bit is set for the E0 prefix + Uint8 index = (Uint8)rawkeyboard->MakeCode; + if (rawkeyboard->Flags & RI_KEY_E0) { + rawcode |= 0xE000; + index |= 0x80; + } + code = windows_scancode_table[index]; + } + + if (down) { + SDL_Window *focus = SDL_GetKeyboardFocus(); + if (!focus || focus->text_input_active) { + return; + } + } + + SDL_SendKeyboardKey(timestamp, keyboardID, rawcode, code, down); +} + +void WIN_PollRawInput(SDL_VideoDevice *_this, Uint64 poll_start) +{ + SDL_VideoData *data = _this->internal; + UINT size, i, count, total = 0; + RAWINPUT *input; + Uint64 poll_finish; + + if (data->rawinput_offset == 0) { + BOOL isWow64; + + data->rawinput_offset = sizeof(RAWINPUTHEADER); + if (IsWow64Process(GetCurrentProcess(), &isWow64) && isWow64) { + // We're going to get 64-bit data, so use the 64-bit RAWINPUTHEADER size + data->rawinput_offset += 8; + } + } + + // Get all available events + input = (RAWINPUT *)data->rawinput; + for (;;) { + size = data->rawinput_size - (UINT)((BYTE *)input - data->rawinput); + count = GetRawInputBuffer(input, &size, sizeof(RAWINPUTHEADER)); + poll_finish = SDL_GetTicksNS(); + if (count == 0 || count == (UINT)-1) { + if (!data->rawinput || (count == (UINT)-1 && GetLastError() == ERROR_INSUFFICIENT_BUFFER)) { + const UINT RAWINPUT_BUFFER_SIZE_INCREMENT = 96; // 2 64-bit raw mouse packets + BYTE *rawinput = (BYTE *)SDL_realloc(data->rawinput, data->rawinput_size + RAWINPUT_BUFFER_SIZE_INCREMENT); + if (!rawinput) { + break; + } + input = (RAWINPUT *)(rawinput + ((BYTE *)input - data->rawinput)); + data->rawinput = rawinput; + data->rawinput_size += RAWINPUT_BUFFER_SIZE_INCREMENT; + } else { + break; + } + } else { + total += count; + + // Advance input to the end of the buffer + while (count--) { + input = NEXTRAWINPUTBLOCK(input); + } + } + } + + if (total > 0) { + Uint64 delta = poll_finish - poll_start; + UINT mouse_total = 0; + for (i = 0, input = (RAWINPUT *)data->rawinput; i < total; ++i, input = NEXTRAWINPUTBLOCK(input)) { + if (input->header.dwType == RIM_TYPEMOUSE) { + mouse_total += 1; + } + } + int mouse_index = 0; + for (i = 0, input = (RAWINPUT *)data->rawinput; i < total; ++i, input = NEXTRAWINPUTBLOCK(input)) { + if (input->header.dwType == RIM_TYPEMOUSE) { + mouse_index += 1; // increment first so that it starts at one + RAWMOUSE *rawmouse = (RAWMOUSE *)((BYTE *)input + data->rawinput_offset); + Uint64 time = poll_finish - (delta * (mouse_total - mouse_index)) / mouse_total; + WIN_HandleRawMouseInput(time, data, input->header.hDevice, rawmouse); + } else if (input->header.dwType == RIM_TYPEKEYBOARD) { + RAWKEYBOARD *rawkeyboard = (RAWKEYBOARD *)((BYTE *)input + data->rawinput_offset); + WIN_HandleRawKeyboardInput(poll_finish, data, input->header.hDevice, rawkeyboard); + } + } + } + data->last_rawinput_poll = poll_finish; +} + +#endif // !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) + +static void AddDeviceID(Uint32 deviceID, Uint32 **list, int *count) +{ + int new_count = (*count + 1); + Uint32 *new_list = (Uint32 *)SDL_realloc(*list, new_count * sizeof(*new_list)); + if (!new_list) { + // Oh well, we'll drop this one + return; + } + new_list[new_count - 1] = deviceID; + + *count = new_count; + *list = new_list; +} + +static bool HasDeviceID(Uint32 deviceID, const Uint32 *list, int count) +{ + for (int i = 0; i < count; ++i) { + if (deviceID == list[i]) { + return true; + } + } + return false; +} + +#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) +static char *GetDeviceName(HANDLE hDevice, HDEVINFO devinfo, const char *instance, const char *default_name, bool hid_loaded) +{ + char *vendor_name = NULL; + char *product_name = NULL; + char *name = NULL; + + // These are 126 for USB, but can be longer for Bluetooth devices + WCHAR vend[256], prod[256]; + vend[0] = 0; + prod[0] = 0; + + + HIDD_ATTRIBUTES attr; + attr.VendorID = 0; + attr.ProductID = 0; + attr.Size = sizeof(attr); + + if (hid_loaded) { + char devName[MAX_PATH + 1]; + UINT cap = sizeof(devName) - 1; + UINT len = GetRawInputDeviceInfoA(hDevice, RIDI_DEVICENAME, devName, &cap); + if (len != (UINT)-1) { + devName[len] = '\0'; + + // important: for devices with exclusive access mode as per + // https://learn.microsoft.com/en-us/windows-hardware/drivers/hid/top-level-collections-opened-by-windows-for-system-use + // they can only be opened with a desired access of none instead of generic read. + HANDLE hFile = CreateFileA(devName, 0, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL); + if (hFile != INVALID_HANDLE_VALUE) { + SDL_HidD_GetAttributes(hFile, &attr); + SDL_HidD_GetManufacturerString(hFile, vend, sizeof(vend)); + SDL_HidD_GetProductString(hFile, prod, sizeof(prod)); + CloseHandle(hFile); + } + } + } + + if (vend[0]) { + vendor_name = WIN_StringToUTF8W(vend); + } + + if (prod[0]) { + product_name = WIN_StringToUTF8W(prod); + } else { + SP_DEVINFO_DATA data; + SDL_zero(data); + data.cbSize = sizeof(data); + for (DWORD i = 0;; ++i) { + if (!SetupDiEnumDeviceInfo(devinfo, i, &data)) { + if (GetLastError() == ERROR_NO_MORE_ITEMS) { + break; + } else { + continue; + } + } + + char DeviceInstanceId[64]; + if (!SetupDiGetDeviceInstanceIdA(devinfo, &data, DeviceInstanceId, sizeof(DeviceInstanceId), NULL)) + continue; + + if (SDL_strcasecmp(instance, DeviceInstanceId) == 0) { + DWORD size = 0; + if (SetupDiGetDeviceRegistryPropertyW(devinfo, &data, SPDRP_DEVICEDESC, NULL, (PBYTE)prod, sizeof(prod), &size)) { + // Make sure the device description is null terminated + size /= sizeof(*prod); + if (size >= SDL_arraysize(prod)) { + // Truncated description... + size = (SDL_arraysize(prod) - 1); + } + prod[size] = 0; + + if (attr.VendorID || attr.ProductID) { + SDL_asprintf(&product_name, "%S (0x%.4x/0x%.4x)", prod, attr.VendorID, attr.ProductID); + } else { + product_name = WIN_StringToUTF8W(prod); + } + } + break; + } + } + } + + if (!product_name && (attr.VendorID || attr.ProductID)) { + SDL_asprintf(&product_name, "%s (0x%.4x/0x%.4x)", default_name, attr.VendorID, attr.ProductID); + } + name = SDL_CreateDeviceName(attr.VendorID, attr.ProductID, vendor_name, product_name, default_name); + SDL_free(vendor_name); + SDL_free(product_name); + + return name; +} + +void WIN_CheckKeyboardAndMouseHotplug(SDL_VideoDevice *_this, bool initial_check) +{ + PRAWINPUTDEVICELIST raw_devices = NULL; + UINT raw_device_count = 0; + int old_keyboard_count = 0; + SDL_KeyboardID *old_keyboards = NULL; + int new_keyboard_count = 0; + SDL_KeyboardID *new_keyboards = NULL; + int old_mouse_count = 0; + SDL_MouseID *old_mice = NULL; + int new_mouse_count = 0; + SDL_MouseID *new_mice = NULL; + bool send_event = !initial_check; + + // Check to see if anything has changed + static Uint64 s_last_device_change; + Uint64 last_device_change = WIN_GetLastDeviceNotification(); + if (!initial_check && last_device_change == s_last_device_change) { + return; + } + s_last_device_change = last_device_change; + + if ((GetRawInputDeviceList(NULL, &raw_device_count, sizeof(RAWINPUTDEVICELIST)) == -1) || (!raw_device_count)) { + return; // oh well. + } + + raw_devices = (PRAWINPUTDEVICELIST)SDL_malloc(sizeof(RAWINPUTDEVICELIST) * raw_device_count); + if (!raw_devices) { + return; // oh well. + } + + raw_device_count = GetRawInputDeviceList(raw_devices, &raw_device_count, sizeof(RAWINPUTDEVICELIST)); + if (raw_device_count == (UINT)-1) { + SDL_free(raw_devices); + raw_devices = NULL; + return; // oh well. + } + + HDEVINFO devinfo = SetupDiGetClassDevsA(NULL, NULL, NULL, (DIGCF_ALLCLASSES | DIGCF_PRESENT)); + + old_keyboards = SDL_GetKeyboards(&old_keyboard_count); + old_mice = SDL_GetMice(&old_mouse_count); + + bool hid_loaded = WIN_LoadHIDDLL(); + for (UINT i = 0; i < raw_device_count; i++) { + RID_DEVICE_INFO rdi; + char devName[MAX_PATH] = { 0 }; + UINT rdiSize = sizeof(rdi); + UINT nameSize = SDL_arraysize(devName); + int vendor = 0, product = 0; + DWORD dwType = raw_devices[i].dwType; + char *instance, *ptr, *name; + + if (dwType != RIM_TYPEKEYBOARD && dwType != RIM_TYPEMOUSE) { + continue; + } + + rdi.cbSize = sizeof(rdi); + if (GetRawInputDeviceInfoA(raw_devices[i].hDevice, RIDI_DEVICEINFO, &rdi, &rdiSize) == ((UINT)-1) || + GetRawInputDeviceInfoA(raw_devices[i].hDevice, RIDI_DEVICENAME, devName, &nameSize) == ((UINT)-1)) { + continue; + } + + // Extract the device instance + instance = devName; + while (*instance == '\\' || *instance == '?') { + ++instance; + } + for (ptr = instance; *ptr; ++ptr) { + if (*ptr == '#') { + *ptr = '\\'; + } + if (*ptr == '{') { + if (ptr > instance && ptr[-1] == '\\') { + --ptr; + } + break; + } + } + *ptr = '\0'; + + SDL_sscanf(instance, "HID\\VID_%X&PID_%X&", &vendor, &product); + + switch (dwType) { + case RIM_TYPEKEYBOARD: + if (SDL_IsKeyboard((Uint16)vendor, (Uint16)product, rdi.keyboard.dwNumberOfKeysTotal)) { + SDL_KeyboardID keyboardID = (Uint32)(uintptr_t)raw_devices[i].hDevice; + AddDeviceID(keyboardID, &new_keyboards, &new_keyboard_count); + if (!HasDeviceID(keyboardID, old_keyboards, old_keyboard_count)) { + name = GetDeviceName(raw_devices[i].hDevice, devinfo, instance, "Keyboard", hid_loaded); + SDL_AddKeyboard(keyboardID, name, send_event); + SDL_free(name); + } + } + break; + case RIM_TYPEMOUSE: + if (SDL_IsMouse((Uint16)vendor, (Uint16)product)) { + SDL_MouseID mouseID = (Uint32)(uintptr_t)raw_devices[i].hDevice; + AddDeviceID(mouseID, &new_mice, &new_mouse_count); + if (!HasDeviceID(mouseID, old_mice, old_mouse_count)) { + name = GetDeviceName(raw_devices[i].hDevice, devinfo, instance, "Mouse", hid_loaded); + SDL_AddMouse(mouseID, name, send_event); + SDL_free(name); + } + } + break; + default: + break; + } + } + if (hid_loaded) { + WIN_UnloadHIDDLL(); + } + + for (int i = old_keyboard_count; i--;) { + if (!HasDeviceID(old_keyboards[i], new_keyboards, new_keyboard_count)) { + SDL_RemoveKeyboard(old_keyboards[i], send_event); + } + } + + for (int i = old_mouse_count; i--;) { + if (!HasDeviceID(old_mice[i], new_mice, new_mouse_count)) { + SDL_RemoveMouse(old_mice[i], send_event); + } + } + + SDL_free(old_keyboards); + SDL_free(new_keyboards); + SDL_free(old_mice); + SDL_free(new_mice); + + SetupDiDestroyDeviceInfoList(devinfo); + + SDL_free(raw_devices); +} +#endif // !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) + +// Return true if spurious LCtrl is pressed +// LCtrl is sent when RAltGR is pressed +static bool SkipAltGrLeftControl(WPARAM wParam, LPARAM lParam) +{ + if (wParam != VK_CONTROL) { + return false; + } + + // Is this an extended key (i.e. right key)? + if (lParam & 0x01000000) { + return false; + } + +#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) + // Here is a trick: "Alt Gr" sends LCTRL, then RALT. We only + // want the RALT message, so we try to see if the next message + // is a RALT message. In that case, this is a false LCTRL! + MSG next_msg; + DWORD msg_time = GetMessageTime(); + if (PeekMessage(&next_msg, NULL, 0, 0, PM_NOREMOVE)) { + if (next_msg.message == WM_KEYDOWN || + next_msg.message == WM_SYSKEYDOWN) { + if (next_msg.wParam == VK_MENU && (next_msg.lParam & 0x01000000) && next_msg.time == msg_time) { + // Next message is a RALT down message, which means that this is NOT a proper LCTRL message! + return true; + } + } + } +#endif // !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) + + return false; +} + +static bool DispatchModalLoopMessageHook(HWND *hwnd, UINT *msg, WPARAM *wParam, LPARAM *lParam) +{ + MSG dummy; + + SDL_zero(dummy); + dummy.hwnd = *hwnd; + dummy.message = *msg; + dummy.wParam = *wParam; + dummy.lParam = *lParam; + if (g_WindowsMessageHook(g_WindowsMessageHookData, &dummy)) { + // Can't modify the hwnd, but everything else is fair game + *msg = dummy.message; + *wParam = dummy.wParam; + *lParam = dummy.lParam; + return true; + } + return false; +} + +LRESULT CALLBACK WIN_WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + SDL_WindowData *data; + LRESULT returnCode = -1; + + // Get the window data for the window + data = WIN_GetWindowDataFromHWND(hwnd); +#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) + if (!data) { + // Fallback + data = (SDL_WindowData *)GetProp(hwnd, TEXT("SDL_WindowData")); + } +#endif + if (!data) { + return CallWindowProc(DefWindowProc, hwnd, msg, wParam, lParam); + } + +#ifdef WMMSG_DEBUG + { + char message[1024]; + if (msg > MAX_WMMSG) { + SDL_snprintf(message, sizeof(message), "Received windows message: %p UNKNOWN (%d) -- 0x%x, 0x%x\r\n", hwnd, msg, wParam, lParam); + } else { + SDL_snprintf(message, sizeof(message), "Received windows message: %p %s -- 0x%x, 0x%x\r\n", hwnd, wmtab[msg], wParam, lParam); + } + OutputDebugStringA(message); + } +#endif // WMMSG_DEBUG + + + if (g_WindowsMessageHook && data->in_modal_loop) { + // Synthesize a message for window hooks so they can modify the message if desired + if (!DispatchModalLoopMessageHook(&hwnd, &msg, &wParam, &lParam)) { + return 0; + } + } + +#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) + if (WIN_HandleIMEMessage(hwnd, msg, wParam, &lParam, data->videodata)) { + return 0; + } +#endif + + switch (msg) { + + case WM_SHOWWINDOW: + { + if (wParam) { + SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_SHOWN, 0, 0); + } else { + SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_HIDDEN, 0, 0); + } + } break; + +#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) + case WM_NCACTIVATE: + { + // Don't immediately clip the cursor in case we're clicking minimize/maximize buttons + // This is the only place that this flag is set. This causes all subsequent calls to + // WIN_UpdateClipCursor for this window to be no-ops in this frame's message-pumping. + // This flag is unset at the end of message pumping each frame for every window, and + // should never be carried over between frames. + data->skip_update_clipcursor = true; + + /* Update the focus here, since it's possible to get WM_ACTIVATE and WM_SETFOCUS without + actually being the foreground window, but this appears to get called in all cases where + the global foreground window changes to and from this window. */ + WIN_UpdateFocus(data->window, !!wParam); + } break; + + case WM_ACTIVATE: + { + // Update the focus in case we changed focus to a child window and then away from the application + WIN_UpdateFocus(data->window, !!LOWORD(wParam)); + } break; + + case WM_MOUSEACTIVATE: + { + if (SDL_WINDOW_IS_POPUP(data->window)) { + return MA_NOACTIVATE; + } + + // Check parents to see if they are in relative mouse mode and focused + SDL_Window *parent = data->window->parent; + while (parent) { + if ((parent->flags & SDL_WINDOW_INPUT_FOCUS) && + (parent->flags & SDL_WINDOW_MOUSE_RELATIVE_MODE)) { + return MA_NOACTIVATE; + } + parent = parent->parent; + } + } break; + + case WM_SETFOCUS: + { + // Update the focus in case it's changing between top-level windows in the same application + WIN_UpdateFocus(data->window, true); + } break; + + case WM_KILLFOCUS: + case WM_ENTERIDLE: + { + // Update the focus in case it's changing between top-level windows in the same application + WIN_UpdateFocus(data->window, false); + } break; + + case WM_POINTERENTER: + { + if (!data->videodata->GetPointerType) { + break; // Not on Windows8 or later? We shouldn't get this event, but just in case... + } + + const UINT32 pointerid = GET_POINTERID_WPARAM(wParam); + void *hpointer = (void *) (size_t) pointerid; + POINTER_INPUT_TYPE pointer_type = PT_POINTER; + if (!data->videodata->GetPointerType(pointerid, &pointer_type)) { + break; // oh well. + } else if (pointer_type != PT_PEN) { + break; // we only care about pens here. + } else if (SDL_FindPenByHandle(hpointer)) { + break; // we already have this one, don't readd it. + } + + // one can use GetPointerPenInfo() to get the current state of the pen, and check POINTER_PEN_INFO::penMask, + // but the docs aren't clear if these masks are _always_ set for pens with specific features, or if they + // could be unset at this moment because Windows is still deciding what capabilities the pen has, and/or + // doesn't yet have valid data for them. As such, just say everything that the interface supports is + // available...we don't expose this information through the public API at the moment anyhow. + SDL_PenInfo info; + SDL_zero(info); + info.capabilities = SDL_PEN_CAPABILITY_PRESSURE | SDL_PEN_CAPABILITY_XTILT | SDL_PEN_CAPABILITY_YTILT | SDL_PEN_CAPABILITY_DISTANCE | SDL_PEN_CAPABILITY_ROTATION | SDL_PEN_CAPABILITY_ERASER; + info.max_tilt = 90.0f; + info.num_buttons = 1; + info.subtype = SDL_PEN_TYPE_PENCIL; + SDL_AddPenDevice(0, NULL, &info, hpointer); + returnCode = 0; + } break; + + case WM_POINTERCAPTURECHANGED: + case WM_POINTERLEAVE: + { + const UINT32 pointerid = GET_POINTERID_WPARAM(wParam); + void *hpointer = (void *) (size_t) pointerid; + const SDL_PenID pen = SDL_FindPenByHandle(hpointer); + if (pen == 0) { + break; // not a pen, or not a pen we already knew about. + } + + // if this just left the _window_, we don't care. If this is no longer visible to the tablet, time to remove it! + if ((msg == WM_POINTERCAPTURECHANGED) || !IS_POINTER_INCONTACT_WPARAM(wParam)) { + SDL_RemovePenDevice(WIN_GetEventTimestamp(), pen); + } + returnCode = 0; + } break; + + case WM_POINTERUPDATE: { + POINTER_INPUT_TYPE pointer_type = PT_POINTER; + if (!data->videodata->GetPointerType || !data->videodata->GetPointerType(GET_POINTERID_WPARAM(wParam), &pointer_type)) { + break; // oh well. + } + + if (pointer_type == PT_MOUSE) { + data->last_pointer_update = lParam; + returnCode = 0; + break; + } + } + SDL_FALLTHROUGH; + + case WM_POINTERDOWN: + case WM_POINTERUP: { + POINTER_PEN_INFO pen_info; + const UINT32 pointerid = GET_POINTERID_WPARAM(wParam); + void *hpointer = (void *) (size_t) pointerid; + const SDL_PenID pen = SDL_FindPenByHandle(hpointer); + if (pen == 0) { + break; // not a pen, or not a pen we already knew about. + } else if (!data->videodata->GetPointerPenInfo || !data->videodata->GetPointerPenInfo(pointerid, &pen_info)) { + break; // oh well. + } + + const Uint64 timestamp = WIN_GetEventTimestamp(); + SDL_Window *window = data->window; + + // if lifting off, do it first, so any motion changes don't cause app issues. + if (msg == WM_POINTERUP) { + SDL_SendPenTouch(timestamp, pen, window, (pen_info.penFlags & PEN_FLAG_INVERTED) != 0, false); + } + + POINT position; + position.x = (LONG) GET_X_LPARAM(lParam); + position.y = (LONG) GET_Y_LPARAM(lParam); + ScreenToClient(data->hwnd, &position); + + SDL_SendPenMotion(timestamp, pen, window, (float) position.x, (float) position.y); + SDL_SendPenButton(timestamp, pen, window, 1, (pen_info.penFlags & PEN_FLAG_BARREL) != 0); + SDL_SendPenButton(timestamp, pen, window, 2, (pen_info.penFlags & PEN_FLAG_ERASER) != 0); + + if (pen_info.penMask & PEN_MASK_PRESSURE) { + SDL_SendPenAxis(timestamp, pen, window, SDL_PEN_AXIS_PRESSURE, ((float) pen_info.pressure) / 1024.0f); // pen_info.pressure is in the range 0..1024. + } + + if (pen_info.penMask & PEN_MASK_ROTATION) { + SDL_SendPenAxis(timestamp, pen, window, SDL_PEN_AXIS_ROTATION, ((float) pen_info.rotation)); // it's already in the range of 0 to 359. + } + + if (pen_info.penMask & PEN_MASK_TILT_X) { + SDL_SendPenAxis(timestamp, pen, window, SDL_PEN_AXIS_XTILT, ((float) pen_info.tiltX)); // it's already in the range of -90 to 90.. + } + + if (pen_info.penMask & PEN_MASK_TILT_Y) { + SDL_SendPenAxis(timestamp, pen, window, SDL_PEN_AXIS_YTILT, ((float) pen_info.tiltY)); // it's already in the range of -90 to 90.. + } + + // if setting down, do it last, so the pen is positioned correctly from the first contact. + if (msg == WM_POINTERDOWN) { + SDL_SendPenTouch(timestamp, pen, window, (pen_info.penFlags & PEN_FLAG_INVERTED) != 0, true); + } + + returnCode = 0; + } break; + + case WM_MOUSEMOVE: + { + SDL_Window *window = data->window; + + if (window->flags & SDL_WINDOW_INPUT_FOCUS) { + bool wish_clip_cursor = ( + window->flags & (SDL_WINDOW_MOUSE_RELATIVE_MODE | SDL_WINDOW_MOUSE_GRABBED) || + (window->mouse_rect.w > 0 && window->mouse_rect.h > 0) + ); + if (wish_clip_cursor) { + data->skip_update_clipcursor = false; + WIN_UpdateClipCursor(window); + } + } + + if (!data->mouse_tracked) { + TRACKMOUSEEVENT trackMouseEvent; + + trackMouseEvent.cbSize = sizeof(TRACKMOUSEEVENT); + trackMouseEvent.dwFlags = TME_LEAVE; + trackMouseEvent.hwndTrack = data->hwnd; + + if (TrackMouseEvent(&trackMouseEvent)) { + data->mouse_tracked = true; + } + + WIN_CheckAsyncMouseRelease(WIN_GetEventTimestamp(), data); + } + + if (!data->videodata->raw_mouse_enabled) { + // Only generate mouse events for real mouse + if (GetMouseMessageSource((ULONG)GetMessageExtraInfo()) == SDL_MOUSE_EVENT_SOURCE_MOUSE && + lParam != data->last_pointer_update) { + SDL_SendMouseMotion(WIN_GetEventTimestamp(), window, SDL_GLOBAL_MOUSE_ID, false, (float)GET_X_LPARAM(lParam), (float)GET_Y_LPARAM(lParam)); + } + } + } break; + + case WM_LBUTTONUP: + case WM_RBUTTONUP: + case WM_MBUTTONUP: + case WM_XBUTTONUP: + case WM_LBUTTONDOWN: + case WM_LBUTTONDBLCLK: + case WM_RBUTTONDOWN: + case WM_RBUTTONDBLCLK: + case WM_MBUTTONDOWN: + case WM_MBUTTONDBLCLK: + case WM_XBUTTONDOWN: + case WM_XBUTTONDBLCLK: + { + /* SDL_Mouse *mouse = SDL_GetMouse(); */ + if (!data->videodata->raw_mouse_enabled) { + if (GetMouseMessageSource((ULONG)GetMessageExtraInfo()) == SDL_MOUSE_EVENT_SOURCE_MOUSE && + lParam != data->last_pointer_update) { + WIN_CheckWParamMouseButtons(WIN_GetEventTimestamp(), wParam, data, SDL_GLOBAL_MOUSE_ID); + } + } + } break; + +#if 0 // We handle raw input all at once instead of using a syscall for each mouse event + case WM_INPUT: + { + HRAWINPUT hRawInput = (HRAWINPUT)lParam; + RAWINPUT inp; + UINT size = sizeof(inp); + + // Relative mouse motion is delivered to the window with keyboard focus + if (data->window != SDL_GetKeyboardFocus()) { + break; + } + + GetRawInputData(hRawInput, RID_INPUT, &inp, &size, sizeof(RAWINPUTHEADER)); + if (inp.header.dwType == RIM_TYPEMOUSE) { + WIN_HandleRawMouseInput(WIN_GetEventTimestamp(), data, inp.header.hDevice, &inp.data.mouse); + } else if (inp.header.dwType == RIM_TYPEKEYBOARD) { + WIN_HandleRawKeyboardInput(WIN_GetEventTimestamp(), data, inp.header.hDevice, &inp.data.keyboard); + } + } break; +#endif + + case WM_MOUSEWHEEL: + case WM_MOUSEHWHEEL: + { + if (!data->videodata->raw_mouse_enabled) { + short amount = GET_WHEEL_DELTA_WPARAM(wParam); + float fAmount = (float)amount / WHEEL_DELTA; + if (msg == WM_MOUSEWHEEL) { + SDL_SendMouseWheel(WIN_GetEventTimestamp(), data->window, SDL_GLOBAL_MOUSE_ID, 0.0f, fAmount, SDL_MOUSEWHEEL_NORMAL); + } else { + SDL_SendMouseWheel(WIN_GetEventTimestamp(), data->window, SDL_GLOBAL_MOUSE_ID, fAmount, 0.0f, SDL_MOUSEWHEEL_NORMAL); + } + } + } break; + + case WM_MOUSELEAVE: + if (!(data->window->flags & SDL_WINDOW_MOUSE_CAPTURE)) { + if (SDL_GetMouseFocus() == data->window && !SDL_GetMouse()->relative_mode && !IsIconic(hwnd)) { + SDL_Mouse *mouse; + POINT cursorPos; + GetCursorPos(&cursorPos); + ScreenToClient(hwnd, &cursorPos); + mouse = SDL_GetMouse(); + if (!mouse->was_touch_mouse_events) { // we're not a touch handler causing a mouse leave? + SDL_SendMouseMotion(WIN_GetEventTimestamp(), data->window, SDL_GLOBAL_MOUSE_ID, false, (float)cursorPos.x, (float)cursorPos.y); + } else { // touch handling? + mouse->was_touch_mouse_events = false; // not anymore + if (mouse->touch_mouse_events) { // convert touch to mouse events + SDL_SendMouseMotion(WIN_GetEventTimestamp(), data->window, SDL_TOUCH_MOUSEID, false, (float)cursorPos.x, (float)cursorPos.y); + } else { // normal handling + SDL_SendMouseMotion(WIN_GetEventTimestamp(), data->window, SDL_GLOBAL_MOUSE_ID, false, (float)cursorPos.x, (float)cursorPos.y); + } + } + } + + if (!SDL_GetMouse()->relative_mode) { + // When WM_MOUSELEAVE is fired we can be assured that the cursor has left the window + SDL_SetMouseFocus(NULL); + } + } + + // Once we get WM_MOUSELEAVE we're guaranteed that the window is no longer tracked + data->mouse_tracked = false; + + returnCode = 0; + break; +#endif // !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) + + case WM_KEYDOWN: + case WM_SYSKEYDOWN: + { + if (SkipAltGrLeftControl(wParam, lParam)) { + returnCode = 0; + break; + } + + bool virtual_key = false; + Uint16 rawcode = 0; + SDL_Scancode code = WindowsScanCodeToSDLScanCode(lParam, wParam, &rawcode, &virtual_key); + + // Detect relevant keyboard shortcuts + if (code == SDL_SCANCODE_F4 && (SDL_GetModState() & SDL_KMOD_ALT)) { + // ALT+F4: Close window + if (ShouldGenerateWindowCloseOnAltF4()) { + SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_CLOSE_REQUESTED, 0, 0); + } + } + + if (virtual_key || !data->videodata->raw_keyboard_enabled || data->window->text_input_active) { + SDL_SendKeyboardKey(WIN_GetEventTimestamp(), SDL_GLOBAL_KEYBOARD_ID, rawcode, code, true); + } + } + + returnCode = 0; + break; + + case WM_SYSKEYUP: + case WM_KEYUP: + { + if (SkipAltGrLeftControl(wParam, lParam)) { + returnCode = 0; + break; + } + + bool virtual_key = false; + Uint16 rawcode = 0; + SDL_Scancode code = WindowsScanCodeToSDLScanCode(lParam, wParam, &rawcode, &virtual_key); + const bool *keyboardState = SDL_GetKeyboardState(NULL); + + if (virtual_key || !data->videodata->raw_keyboard_enabled || data->window->text_input_active) { + if (code == SDL_SCANCODE_PRINTSCREEN && !keyboardState[code]) { + SDL_SendKeyboardKey(WIN_GetEventTimestamp(), SDL_GLOBAL_KEYBOARD_ID, rawcode, code, true); + } + SDL_SendKeyboardKey(WIN_GetEventTimestamp(), SDL_GLOBAL_KEYBOARD_ID, rawcode, code, false); + } + } + returnCode = 0; + break; + + case WM_UNICHAR: + if (wParam == UNICODE_NOCHAR) { + returnCode = 1; + } else { + if (SDL_TextInputActive(data->window)) { + char text[5]; + char *end = SDL_UCS4ToUTF8((Uint32)wParam, text); + *end = '\0'; + SDL_SendKeyboardText(text); + } + returnCode = 0; + } + break; + + case WM_CHAR: + if (SDL_TextInputActive(data->window)) { + /* Characters outside Unicode Basic Multilingual Plane (BMP) + * are coded as so called "surrogate pair" in two separate UTF-16 character events. + * Cache high surrogate until next character event. */ + if (IS_HIGH_SURROGATE(wParam)) { + data->high_surrogate = (WCHAR)wParam; + } else { + WCHAR utf16[3]; + + utf16[0] = data->high_surrogate ? data->high_surrogate : (WCHAR)wParam; + utf16[1] = data->high_surrogate ? (WCHAR)wParam : L'\0'; + utf16[2] = L'\0'; + + char utf8[5]; + int result = WIN_WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, utf16, -1, utf8, sizeof(utf8), NULL, NULL); + if (result > 0) { + SDL_SendKeyboardText(utf8); + } + data->high_surrogate = L'\0'; + } + } else { + data->high_surrogate = L'\0'; + } + + returnCode = 0; + break; + +#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) +#ifdef WM_INPUTLANGCHANGE + case WM_INPUTLANGCHANGE: + { + WIN_UpdateKeymap(true); + } + returnCode = 1; + break; +#endif // WM_INPUTLANGCHANGE + + case WM_NCLBUTTONDOWN: + { + data->in_title_click = true; + + // Fix for 500ms hang after user clicks on the title bar, but before moving mouse + // Reference: https://gamedev.net/forums/topic/672094-keeping-things-moving-during-win32-moveresize-events/5254386/ + if (SendMessage(hwnd, WM_NCHITTEST, wParam, lParam) == HTCAPTION) { + POINT cursorPos; + GetCursorPos(&cursorPos); + ScreenToClient(hwnd, &cursorPos); + PostMessage(hwnd, WM_MOUSEMOVE, 0, cursorPos.x | cursorPos.y << 16); + } + } break; + + case WM_CAPTURECHANGED: + { + data->in_title_click = false; + + // The mouse may have been released during a modal loop + WIN_CheckAsyncMouseRelease(WIN_GetEventTimestamp(), data); + } break; + +#ifdef WM_GETMINMAXINFO + case WM_GETMINMAXINFO: + { + MINMAXINFO *info; + RECT size; + int x, y; + int w, h; + int min_w, min_h; + int max_w, max_h; + BOOL constrain_max_size; + + // If this is an expected size change, allow it + if (data->expected_resize) { + break; + } + + // Get the current position of our window + GetWindowRect(hwnd, &size); + x = size.left; + y = size.top; + + // Calculate current size of our window + SDL_GetWindowSize(data->window, &w, &h); + SDL_GetWindowMinimumSize(data->window, &min_w, &min_h); + SDL_GetWindowMaximumSize(data->window, &max_w, &max_h); + + /* Store in min_w and min_h difference between current size and minimal + size so we don't need to call AdjustWindowRectEx twice */ + min_w -= w; + min_h -= h; + if (max_w && max_h) { + max_w -= w; + max_h -= h; + constrain_max_size = TRUE; + } else { + constrain_max_size = FALSE; + } + + if (!(SDL_GetWindowFlags(data->window) & SDL_WINDOW_BORDERLESS) && !SDL_WINDOW_IS_POPUP(data->window)) { + size.top = 0; + size.left = 0; + size.bottom = h; + size.right = w; + WIN_AdjustWindowRectForHWND(hwnd, &size, 0); + w = size.right - size.left; + h = size.bottom - size.top; +#ifdef HIGHDPI_DEBUG + SDL_Log("WM_GETMINMAXINFO: max window size: %dx%d using dpi: %u", w, h, dpi); +#endif + } + + // Fix our size to the current size + info = (MINMAXINFO *)lParam; + if (SDL_GetWindowFlags(data->window) & SDL_WINDOW_RESIZABLE) { + if (SDL_GetWindowFlags(data->window) & SDL_WINDOW_BORDERLESS) { + int screenW = GetSystemMetrics(SM_CXSCREEN); + int screenH = GetSystemMetrics(SM_CYSCREEN); + info->ptMaxSize.x = SDL_max(w, screenW); + info->ptMaxSize.y = SDL_max(h, screenH); + info->ptMaxPosition.x = SDL_min(0, ((screenW - w) / 2)); + info->ptMaxPosition.y = SDL_min(0, ((screenH - h) / 2)); + } + info->ptMinTrackSize.x = (LONG)w + min_w; + info->ptMinTrackSize.y = (LONG)h + min_h; + if (constrain_max_size) { + info->ptMaxTrackSize.x = (LONG)w + max_w; + info->ptMaxTrackSize.y = (LONG)h + max_h; + } + } else { + info->ptMaxSize.x = w; + info->ptMaxSize.y = h; + info->ptMaxPosition.x = x; + info->ptMaxPosition.y = y; + info->ptMinTrackSize.x = w; + info->ptMinTrackSize.y = h; + info->ptMaxTrackSize.x = w; + info->ptMaxTrackSize.y = h; + } + } + returnCode = 0; + break; +#endif // WM_GETMINMAXINFO + + case WM_WINDOWPOSCHANGING: + + if (data->expected_resize) { + returnCode = 0; + } + break; + + case WM_WINDOWPOSCHANGED: + { + SDL_Window *win; + const SDL_DisplayID original_displayID = data->last_displayID; + const WINDOWPOS *windowpos = (WINDOWPOS *)lParam; + const bool iconic = IsIconic(hwnd); + const bool zoomed = IsZoomed(hwnd); + RECT rect; + int x, y; + int w, h; + + if (windowpos->flags & SWP_SHOWWINDOW) { + SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_SHOWN, 0, 0); + } + + if (iconic) { + SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_MINIMIZED, 0, 0); + } else if (zoomed) { + if (data->window->flags & SDL_WINDOW_MINIMIZED) { + // If going from minimized to maximized, send the restored event first. + SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_RESTORED, 0, 0); + } + SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_MAXIMIZED, 0, 0); + data->force_ws_maximizebox = true; + } else if (data->window->flags & (SDL_WINDOW_MAXIMIZED | SDL_WINDOW_MINIMIZED)) { + SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_RESTORED, 0, 0); + + /* If resizable was forced on for the maximized window, clear the style flags now, + * but not if the window is fullscreen, as this needs to be preserved in that case. + */ + if (!(data->window->flags & SDL_WINDOW_FULLSCREEN)) { + data->force_ws_maximizebox = false; + WIN_SetWindowResizable(SDL_GetVideoDevice(), data->window, !!(data->window->flags & SDL_WINDOW_RESIZABLE)); + } + } + + if (windowpos->flags & SWP_HIDEWINDOW) { + SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_HIDDEN, 0, 0); + } + + // When the window is minimized it's resized to the dock icon size, ignore this + if (iconic) { + break; + } + + if (data->initializing) { + break; + } + + if (!data->disable_move_size_events) { + if (GetClientRect(hwnd, &rect) && !WIN_IsRectEmpty(&rect)) { + ClientToScreen(hwnd, (LPPOINT) &rect); + ClientToScreen(hwnd, (LPPOINT) &rect + 1); + + x = rect.left; + y = rect.top; + + SDL_GlobalToRelativeForWindow(data->window, x, y, &x, &y); + SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_MOVED, x, y); + } + + // Moving the window from one display to another can change the size of the window (in the handling of SDL_EVENT_WINDOW_MOVED), so we need to re-query the bounds + if (GetClientRect(hwnd, &rect) && !WIN_IsRectEmpty(&rect)) { + w = rect.right; + h = rect.bottom; + + SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_RESIZED, w, h); + } + } + + WIN_UpdateClipCursor(data->window); + + // Update the window display position + data->last_displayID = SDL_GetDisplayForWindow(data->window); + + if (data->last_displayID != original_displayID) { + // Display changed, check ICC profile + WIN_UpdateWindowICCProfile(data->window, true); + } + + // Update the position of any child windows + for (win = data->window->first_child; win; win = win->next_sibling) { + // Don't update hidden child popup windows, their relative position doesn't change + if (SDL_WINDOW_IS_POPUP(win) && !(win->flags & SDL_WINDOW_HIDDEN)) { + WIN_SetWindowPositionInternal(win, SWP_NOCOPYBITS | SWP_NOACTIVATE, SDL_WINDOWRECT_CURRENT); + } + } + + // Forces a WM_PAINT event + InvalidateRect(hwnd, NULL, FALSE); + + } break; + + case WM_ENTERSIZEMOVE: + case WM_ENTERMENULOOP: + { + if (g_WindowsMessageHook) { + if (!DispatchModalLoopMessageHook(&hwnd, &msg, &wParam, &lParam)) { + return 0; + } + } + + ++data->in_modal_loop; + if (data->in_modal_loop == 1) { + data->initial_size_rect.left = data->window->x; + data->initial_size_rect.right = data->window->x + data->window->w; + data->initial_size_rect.top = data->window->y; + data->initial_size_rect.bottom = data->window->y + data->window->h; + + SetTimer(hwnd, (UINT_PTR)SDL_IterateMainCallbacks, USER_TIMER_MINIMUM, NULL); + } + } break; + + case WM_TIMER: + { + if (wParam == (UINT_PTR)SDL_IterateMainCallbacks) { + SDL_OnWindowLiveResizeUpdate(data->window); + return 0; + } + } break; + + case WM_EXITSIZEMOVE: + case WM_EXITMENULOOP: + { + --data->in_modal_loop; + if (data->in_modal_loop == 0) { + KillTimer(hwnd, (UINT_PTR)SDL_IterateMainCallbacks); + } + } break; + + case WM_SIZING: + { + WPARAM edge = wParam; + RECT* dragRect = (RECT*)lParam; + RECT clientDragRect = *dragRect; + bool lock_aspect_ratio = (data->window->max_aspect == data->window->min_aspect) ? true : false; + RECT rc; + LONG w, h; + float new_aspect; + + // if aspect ratio constraints are not enabled then skip this message + if (data->window->min_aspect <= 0 && data->window->max_aspect <= 0) { + break; + } + + // unadjust the dragRect from the window rect to the client rect + SetRectEmpty(&rc); + if (!AdjustWindowRectEx(&rc, GetWindowStyle(hwnd), GetMenu(hwnd) != NULL, GetWindowExStyle(hwnd))) { + break; + } + + clientDragRect.left -= rc.left; + clientDragRect.top -= rc.top; + clientDragRect.right -= rc.right; + clientDragRect.bottom -= rc.bottom; + + w = clientDragRect.right - clientDragRect.left; + h = clientDragRect.bottom - clientDragRect.top; + new_aspect = w / (float)h; + + // handle the special case in which the min ar and max ar are the same so the window can size symmetrically + if (lock_aspect_ratio) { + switch (edge) { + case WMSZ_LEFT: + case WMSZ_RIGHT: + h = (int)SDL_roundf(w / data->window->max_aspect); + break; + default: + // resizing via corners or top or bottom + w = (int)SDL_roundf(h * data->window->max_aspect); + break; + } + } else { + switch (edge) { + case WMSZ_LEFT: + case WMSZ_RIGHT: + if (data->window->max_aspect > 0.0f && new_aspect > data->window->max_aspect) { + w = (int)SDL_roundf(h * data->window->max_aspect); + } else if (data->window->min_aspect > 0.0f && new_aspect < data->window->min_aspect) { + w = (int)SDL_roundf(h * data->window->min_aspect); + } + break; + case WMSZ_TOP: + case WMSZ_BOTTOM: + if (data->window->min_aspect > 0.0f && new_aspect < data->window->min_aspect) { + h = (int)SDL_roundf(w / data->window->min_aspect); + } else if (data->window->max_aspect > 0.0f && new_aspect > data->window->max_aspect) { + h = (int)SDL_roundf(w / data->window->max_aspect); + } + break; + + default: + // resizing via corners + if (data->window->max_aspect > 0.0f && new_aspect > data->window->max_aspect) { + w = (int)SDL_roundf(h * data->window->max_aspect); + } else if (data->window->min_aspect > 0.0f && new_aspect < data->window->min_aspect) { + h = (int)SDL_roundf(w / data->window->min_aspect); + } + break; + } + } + + switch (edge) { + case WMSZ_LEFT: + clientDragRect.left = clientDragRect.right - w; + if (lock_aspect_ratio) { + clientDragRect.top = (data->initial_size_rect.bottom + data->initial_size_rect.top - h) / 2; + } + clientDragRect.bottom = h + clientDragRect.top; + break; + case WMSZ_BOTTOMLEFT: + clientDragRect.left = clientDragRect.right - w; + clientDragRect.bottom = h + clientDragRect.top; + break; + case WMSZ_RIGHT: + clientDragRect.right = w + clientDragRect.left; + if (lock_aspect_ratio) { + clientDragRect.top = (data->initial_size_rect.bottom + data->initial_size_rect.top - h) / 2; + } + clientDragRect.bottom = h + clientDragRect.top; + break; + case WMSZ_TOPRIGHT: + clientDragRect.right = w + clientDragRect.left; + clientDragRect.top = clientDragRect.bottom - h; + break; + case WMSZ_TOP: + if (lock_aspect_ratio) { + clientDragRect.left = (data->initial_size_rect.right + data->initial_size_rect.left - w) / 2; + } + clientDragRect.right = w + clientDragRect.left; + clientDragRect.top = clientDragRect.bottom - h; + break; + case WMSZ_TOPLEFT: + clientDragRect.left = clientDragRect.right - w; + clientDragRect.top = clientDragRect.bottom - h; + break; + case WMSZ_BOTTOM: + if (lock_aspect_ratio) { + clientDragRect.left = (data->initial_size_rect.right + data->initial_size_rect.left - w) / 2; + } + clientDragRect.right = w + clientDragRect.left; + clientDragRect.bottom = h + clientDragRect.top; + break; + case WMSZ_BOTTOMRIGHT: + clientDragRect.right = w + clientDragRect.left; + clientDragRect.bottom = h + clientDragRect.top; + break; + } + + // convert the client rect to a window rect + if (!AdjustWindowRectEx(&clientDragRect, GetWindowStyle(hwnd), GetMenu(hwnd) != NULL, GetWindowExStyle(hwnd))) { + break; + } + + *dragRect = clientDragRect; + } + break; + + case WM_SETCURSOR: + { + Uint16 hittest; + + hittest = LOWORD(lParam); + if (hittest == HTCLIENT) { + SetCursor(SDL_cursor); + returnCode = TRUE; + } else if (!g_WindowFrameUsableWhileCursorHidden && !SDL_cursor) { + SetCursor(NULL); + returnCode = TRUE; + } + } break; + + // We were occluded, refresh our display + case WM_PAINT: + { + RECT rect; + if (GetUpdateRect(hwnd, &rect, FALSE)) { + const LONG style = GetWindowLong(hwnd, GWL_EXSTYLE); + + /* Composited windows will continue to receive WM_PAINT messages for update + regions until the window is actually painted through Begin/EndPaint */ + if (style & WS_EX_COMPOSITED) { + PAINTSTRUCT ps; + BeginPaint(hwnd, &ps); + EndPaint(hwnd, &ps); + } + + ValidateRect(hwnd, NULL); + SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_EXPOSED, 0, 0); + } + } + returnCode = 0; + break; + + // We'll do our own drawing, prevent flicker + case WM_ERASEBKGND: + if (ShouldClearWindowOnEraseBackground(data)) { + RECT client_rect; + HBRUSH brush; + data->videodata->cleared = true; + GetClientRect(hwnd, &client_rect); + brush = CreateSolidBrush(0); + FillRect(GetDC(hwnd), &client_rect, brush); + DeleteObject(brush); + } + return 1; + + case WM_SYSCOMMAND: + { + if (!g_WindowsEnableMenuMnemonics) { + if ((wParam & 0xFFF0) == SC_KEYMENU) { + return 0; + } + } + +#if defined(SC_SCREENSAVE) || defined(SC_MONITORPOWER) + // Don't start the screensaver or blank the monitor in fullscreen apps + if ((wParam & 0xFFF0) == SC_SCREENSAVE || + (wParam & 0xFFF0) == SC_MONITORPOWER) { + if (SDL_GetVideoDevice()->suspend_screensaver) { + return 0; + } + } +#endif // System has screensaver support + } break; + + case WM_CLOSE: + { + SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_CLOSE_REQUESTED, 0, 0); + } + returnCode = 0; + break; + + case WM_TOUCH: + if (data->videodata->GetTouchInputInfo && data->videodata->CloseTouchInputHandle) { + UINT i, num_inputs = LOWORD(wParam); + bool isstack; + PTOUCHINPUT inputs = SDL_small_alloc(TOUCHINPUT, num_inputs, &isstack); + if (inputs && data->videodata->GetTouchInputInfo((HTOUCHINPUT)lParam, num_inputs, inputs, sizeof(TOUCHINPUT))) { + RECT rect; + float x, y; + + if (!GetClientRect(hwnd, &rect) || WIN_IsRectEmpty(&rect)) { + if (inputs) { + SDL_small_free(inputs, isstack); + } + break; + } + ClientToScreen(hwnd, (LPPOINT)&rect); + ClientToScreen(hwnd, (LPPOINT)&rect + 1); + rect.top *= 100; + rect.left *= 100; + rect.bottom *= 100; + rect.right *= 100; + + for (i = 0; i < num_inputs; ++i) { + PTOUCHINPUT input = &inputs[i]; + const int w = (rect.right - rect.left); + const int h = (rect.bottom - rect.top); + + const SDL_TouchID touchId = (SDL_TouchID)((uintptr_t)input->hSource); + const SDL_FingerID fingerId = (input->dwID + 1); + + /* TODO: Can we use GetRawInputDeviceInfo and HID info to + determine if this is a direct or indirect touch device? + */ + if (SDL_AddTouch(touchId, SDL_TOUCH_DEVICE_DIRECT, (input->dwFlags & TOUCHEVENTF_PEN) == TOUCHEVENTF_PEN ? "pen" : "touch") < 0) { + continue; + } + + // Get the normalized coordinates for the window + if (w <= 1) { + x = 0.5f; + } else { + x = (float)(input->x - rect.left) / (w - 1); + } + if (h <= 1) { + y = 0.5f; + } else { + y = (float)(input->y - rect.top) / (h - 1); + } + + // FIXME: Should we use the input->dwTime field for the tick source of the timestamp? + if (input->dwFlags & TOUCHEVENTF_DOWN) { + SDL_SendTouch(WIN_GetEventTimestamp(), touchId, fingerId, data->window, SDL_EVENT_FINGER_DOWN, x, y, 1.0f); + } + if (input->dwFlags & TOUCHEVENTF_MOVE) { + SDL_SendTouchMotion(WIN_GetEventTimestamp(), touchId, fingerId, data->window, x, y, 1.0f); + } + if (input->dwFlags & TOUCHEVENTF_UP) { + SDL_SendTouch(WIN_GetEventTimestamp(), touchId, fingerId, data->window, SDL_EVENT_FINGER_UP, x, y, 1.0f); + } + } + } + SDL_small_free(inputs, isstack); + + data->videodata->CloseTouchInputHandle((HTOUCHINPUT)lParam); + return 0; + } + break; + +#ifdef HAVE_TPCSHRD_H + + case WM_TABLET_QUERYSYSTEMGESTURESTATUS: + /* See https://msdn.microsoft.com/en-us/library/windows/desktop/bb969148(v=vs.85).aspx . + * If we're handling our own touches, we don't want any gestures. + * Not all of these settings are documented. + * The use of the undocumented ones was suggested by https://github.com/bjarkeck/GCGJ/blob/master/Monogame/Windows/WinFormsGameForm.cs . */ + return TABLET_DISABLE_PRESSANDHOLD | TABLET_DISABLE_PENTAPFEEDBACK | TABLET_DISABLE_PENBARRELFEEDBACK | TABLET_DISABLE_TOUCHUIFORCEON | TABLET_DISABLE_TOUCHUIFORCEOFF | TABLET_DISABLE_TOUCHSWITCH | TABLET_DISABLE_FLICKS | TABLET_DISABLE_SMOOTHSCROLLING | TABLET_DISABLE_FLICKFALLBACKKEYS; // disables press and hold (right-click) gesture + // disables UI feedback on pen up (waves) + // disables UI feedback on pen button down (circle) + // disables pen flicks (back, forward, drag down, drag up) + +#endif // HAVE_TPCSHRD_H + + case WM_DROPFILES: + { + UINT i; + HDROP drop = (HDROP)wParam; + UINT count = DragQueryFile(drop, 0xFFFFFFFF, NULL, 0); + for (i = 0; i < count; ++i) { + UINT size = DragQueryFile(drop, i, NULL, 0) + 1; + LPTSTR buffer = (LPTSTR)SDL_malloc(sizeof(TCHAR) * size); + if (buffer) { + if (DragQueryFile(drop, i, buffer, size)) { + char *file = WIN_StringToUTF8(buffer); + SDL_SendDropFile(data->window, NULL, file); + SDL_free(file); + } + SDL_free(buffer); + } + } + SDL_SendDropComplete(data->window); + DragFinish(drop); + return 0; + } break; + + case WM_DISPLAYCHANGE: + { + // Reacquire displays if any were added or removed + WIN_RefreshDisplays(SDL_GetVideoDevice()); + } break; + + case WM_NCCALCSIZE: + { + SDL_WindowFlags window_flags = SDL_GetWindowFlags(data->window); + if (wParam == TRUE && (window_flags & SDL_WINDOW_BORDERLESS) && !(window_flags & SDL_WINDOW_FULLSCREEN)) { + // When borderless, need to tell windows that the size of the non-client area is 0 + NCCALCSIZE_PARAMS *params = (NCCALCSIZE_PARAMS *)lParam; + WINDOWPLACEMENT placement; + if (GetWindowPlacement(hwnd, &placement) && placement.showCmd == SW_MAXIMIZE) { + // Maximized borderless windows should use the monitor work area. + HMONITOR hMonitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONULL); + if (!hMonitor) { + // The returned monitor can be null when restoring from minimized, so use the last coordinates. + const POINT pt = { data->window->windowed.x, data->window->windowed.y }; + hMonitor = MonitorFromPoint(pt, MONITOR_DEFAULTTONEAREST); + } + if (hMonitor) { + MONITORINFO info; + SDL_zero(info); + info.cbSize = sizeof(info); + if (GetMonitorInfo(hMonitor, &info)) { + params->rgrc[0] = info.rcWork; + } + } + } else if (!(window_flags & SDL_WINDOW_RESIZABLE) && !data->force_ws_maximizebox) { + int w, h; + if (data->window->last_size_pending) { + w = data->window->pending.w; + h = data->window->pending.h; + } else { + w = data->window->floating.w; + h = data->window->floating.h; + } + params->rgrc[0].right = params->rgrc[0].left + w; + params->rgrc[0].bottom = params->rgrc[0].top + h; + } + return 0; + } + } break; + + case WM_NCHITTEST: + { + SDL_Window *window = data->window; + + if (window->flags & SDL_WINDOW_TOOLTIP) { + return HTTRANSPARENT; + } + + if (window->hit_test) { + POINT winpoint; + winpoint.x = GET_X_LPARAM(lParam); + winpoint.y = GET_Y_LPARAM(lParam); + if (ScreenToClient(hwnd, &winpoint)) { + SDL_Point point; + SDL_HitTestResult rc; + point.x = winpoint.x; + point.y = winpoint.y; + rc = window->hit_test(window, &point, window->hit_test_data); + switch (rc) { +#define POST_HIT_TEST(ret) \ + { \ + SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_HIT_TEST, 0, 0); \ + return ret; \ + } + case SDL_HITTEST_DRAGGABLE: + { + /* If the mouse button state is something other than none or left button down, + * return HTCLIENT, or Windows will eat the button press. + */ + SDL_MouseButtonFlags buttonState = SDL_GetGlobalMouseState(NULL, NULL); + if (buttonState && !(buttonState & SDL_BUTTON_LMASK)) { + // Set focus in case it was lost while previously moving over a draggable area. + SDL_SetMouseFocus(window); + return HTCLIENT; + } + + POST_HIT_TEST(HTCAPTION); + } + case SDL_HITTEST_RESIZE_TOPLEFT: + POST_HIT_TEST(HTTOPLEFT); + case SDL_HITTEST_RESIZE_TOP: + POST_HIT_TEST(HTTOP); + case SDL_HITTEST_RESIZE_TOPRIGHT: + POST_HIT_TEST(HTTOPRIGHT); + case SDL_HITTEST_RESIZE_RIGHT: + POST_HIT_TEST(HTRIGHT); + case SDL_HITTEST_RESIZE_BOTTOMRIGHT: + POST_HIT_TEST(HTBOTTOMRIGHT); + case SDL_HITTEST_RESIZE_BOTTOM: + POST_HIT_TEST(HTBOTTOM); + case SDL_HITTEST_RESIZE_BOTTOMLEFT: + POST_HIT_TEST(HTBOTTOMLEFT); + case SDL_HITTEST_RESIZE_LEFT: + POST_HIT_TEST(HTLEFT); +#undef POST_HIT_TEST + case SDL_HITTEST_NORMAL: + return HTCLIENT; + } + } + // If we didn't return, this will call DefWindowProc below. + } + } break; + + case WM_GETDPISCALEDSIZE: + // Windows 10 Creators Update+ + /* Documented as only being sent to windows that are per-monitor V2 DPI aware. + + Experimentation shows it's only sent during interactive dragging, not in response to + SetWindowPos. */ + if (data->videodata->GetDpiForWindow && data->videodata->AdjustWindowRectExForDpi) { + /* Windows expects applications to scale their window rects linearly + when dragging between monitors with different DPI's. + e.g. a 100x100 window dragged to a 200% scaled monitor + becomes 200x200. + + For SDL, we instead want the client size to scale linearly. + This is not the same as the window rect scaling linearly, + because Windows doesn't scale the non-client area (titlebar etc.) + linearly. So, we need to handle this message to request custom + scaling. */ + + const int nextDPI = (int)wParam; + const int prevDPI = (int)data->videodata->GetDpiForWindow(hwnd); + SIZE *sizeInOut = (SIZE *)lParam; + + int frame_w, frame_h; + int query_client_w_win, query_client_h_win; + +#ifdef HIGHDPI_DEBUG + SDL_Log("WM_GETDPISCALEDSIZE: current DPI: %d potential DPI: %d input size: (%dx%d)", + prevDPI, nextDPI, sizeInOut->cx, sizeInOut->cy); +#endif + + // Subtract the window frame size that would have been used at prevDPI + { + RECT rect = { 0 }; + + if (!(data->window->flags & SDL_WINDOW_BORDERLESS) && !SDL_WINDOW_IS_POPUP(data->window)) { + WIN_AdjustWindowRectForHWND(hwnd, &rect, prevDPI); + } + + frame_w = -rect.left + rect.right; + frame_h = -rect.top + rect.bottom; + + query_client_w_win = sizeInOut->cx - frame_w; + query_client_h_win = sizeInOut->cy - frame_h; + } + + // Add the window frame size that would be used at nextDPI + { + RECT rect = { 0 }; + rect.right = query_client_w_win; + rect.bottom = query_client_h_win; + + if (!(data->window->flags & SDL_WINDOW_BORDERLESS) && !SDL_WINDOW_IS_POPUP(data->window)) { + WIN_AdjustWindowRectForHWND(hwnd, &rect, nextDPI); + } + + // This is supposed to control the suggested rect param of WM_DPICHANGED + sizeInOut->cx = rect.right - rect.left; + sizeInOut->cy = rect.bottom - rect.top; + } + +#ifdef HIGHDPI_DEBUG + SDL_Log("WM_GETDPISCALEDSIZE: output size: (%dx%d)", sizeInOut->cx, sizeInOut->cy); +#endif + return TRUE; + } + break; + + case WM_DPICHANGED: + // Windows 8.1+ + { + const int newDPI = HIWORD(wParam); + RECT *const suggestedRect = (RECT *)lParam; + int w, h; + +#ifdef HIGHDPI_DEBUG + SDL_Log("WM_DPICHANGED: to %d\tsuggested rect: (%d, %d), (%dx%d)", newDPI, + suggestedRect->left, suggestedRect->top, suggestedRect->right - suggestedRect->left, suggestedRect->bottom - suggestedRect->top); +#endif + + if (data->expected_resize) { + /* This DPI change is coming from an explicit SetWindowPos call within SDL. + Assume all call sites are calculating the DPI-aware frame correctly, so + we don't need to do any further adjustment. */ +#ifdef HIGHDPI_DEBUG + SDL_Log("WM_DPICHANGED: Doing nothing, assuming window is already sized correctly"); +#endif + return 0; + } + + // Interactive user-initiated resizing/movement + { + /* Calculate the new frame w/h such that + the client area size is maintained. */ + RECT rect = { 0 }; + rect.right = data->window->w; + rect.bottom = data->window->h; + + if (!(data->window->flags & SDL_WINDOW_BORDERLESS)) { + WIN_AdjustWindowRectForHWND(hwnd, &rect, newDPI); + } + + w = rect.right - rect.left; + h = rect.bottom - rect.top; + } + +#ifdef HIGHDPI_DEBUG + SDL_Log("WM_DPICHANGED: current SDL window size: (%dx%d)\tcalling SetWindowPos: (%d, %d), (%dx%d)", + data->window->w, data->window->h, + suggestedRect->left, suggestedRect->top, w, h); +#endif + + data->expected_resize = true; + SetWindowPos(hwnd, + NULL, + suggestedRect->left, + suggestedRect->top, + w, + h, + SWP_NOZORDER | SWP_NOACTIVATE); + data->expected_resize = false; + return 0; + } + break; + + case WM_SETTINGCHANGE: + if (wParam == 0 && lParam != 0 && SDL_wcscmp((wchar_t *)lParam, L"ImmersiveColorSet") == 0) { + SDL_SetSystemTheme(WIN_GetSystemTheme()); + WIN_UpdateDarkModeForHWND(hwnd); + } + if (wParam == SPI_SETMOUSE || wParam == SPI_SETMOUSESPEED) { + WIN_UpdateMouseSystemScale(); + } + break; + +#endif // !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) + } + + // If there's a window proc, assume it's going to handle messages + if (data->wndproc) { + return CallWindowProc(data->wndproc, hwnd, msg, wParam, lParam); + } else if (returnCode >= 0) { + return returnCode; + } else { + return CallWindowProc(DefWindowProc, hwnd, msg, wParam, lParam); + } +} + +int WIN_WaitEventTimeout(SDL_VideoDevice *_this, Sint64 timeoutNS) +{ + if (g_WindowsEnableMessageLoop) { +#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) + DWORD timeout, ret; + timeout = timeoutNS < 0 ? INFINITE : (DWORD)SDL_NS_TO_MS(timeoutNS); + ret = MsgWaitForMultipleObjects(0, NULL, FALSE, timeout, QS_ALLINPUT); + if (ret == WAIT_OBJECT_0) { + return 1; + } else { + return 0; + } +#else + // MsgWaitForMultipleObjects is desktop-only. + MSG msg; + BOOL message_result; + UINT_PTR timer_id = 0; + if (timeoutNS > 0) { + timer_id = SetTimer(NULL, 0, (UINT)SDL_NS_TO_MS(timeoutNS), NULL); + message_result = GetMessage(&msg, 0, 0, 0); + KillTimer(NULL, timer_id); + } else if (timeoutNS == 0) { + message_result = PeekMessage(&msg, NULL, 0, 0, PM_REMOVE); + } else { + message_result = GetMessage(&msg, 0, 0, 0); + } + if (message_result) { + if (msg.message == WM_TIMER && !msg.hwnd && msg.wParam == timer_id) { + return 0; + } + if (g_WindowsMessageHook) { + if (!g_WindowsMessageHook(g_WindowsMessageHookData, &msg)) { + return 1; + } + } + // Always translate the message in case it's a non-SDL window (e.g. with Qt integration) + TranslateMessage(&msg); + DispatchMessage(&msg); + return 1; + } else { + return 0; + } +#endif // !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) + } else { + // Fail the wait so the caller falls back to polling + return -1; + } +} + +void WIN_SendWakeupEvent(SDL_VideoDevice *_this, SDL_Window *window) +{ + SDL_WindowData *data = window->internal; + PostMessage(data->hwnd, data->videodata->_SDL_WAKEUP, 0, 0); +} + +void WIN_PumpEvents(SDL_VideoDevice *_this) +{ + MSG msg; +#ifdef _MSC_VER // We explicitly want to use GetTickCount(), not GetTickCount64() +#pragma warning(push) +#pragma warning(disable : 28159) +#endif + DWORD end_ticks = GetTickCount() + 1; +#ifdef _MSC_VER +#pragma warning(pop) +#endif + int new_messages = 0; +#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) + const bool *keystate; + SDL_Window *focusWindow; +#endif + + if (_this->internal->gameinput_context) { + WIN_UpdateGameInput(_this); + } + + if (g_WindowsEnableMessageLoop) { + SDL_processing_messages = true; + + while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { + if (g_WindowsMessageHook) { + if (!g_WindowsMessageHook(g_WindowsMessageHookData, &msg)) { + continue; + } + } + +#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) + // Don't dispatch any mouse motion queued prior to or including the last mouse warp + if (msg.message == WM_MOUSEMOVE && SDL_last_warp_time) { + if (!SDL_TICKS_PASSED(msg.time, (SDL_last_warp_time + 1))) { + continue; + } + + // This mouse message happened after the warp + SDL_last_warp_time = 0; + } +#endif // !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) + + WIN_SetMessageTick(msg.time); + + // Always translate the message in case it's a non-SDL window (e.g. with Qt integration) + TranslateMessage(&msg); + DispatchMessage(&msg); + + // Make sure we don't busy loop here forever if there are lots of events coming in + if (SDL_TICKS_PASSED(msg.time, end_ticks)) { + /* We might get a few new messages generated by the Steam overlay or other application hooks + In this case those messages will be processed before any pending input, so we want to continue after those messages. + (thanks to Peter Deayton for his investigation here) + */ + const int MAX_NEW_MESSAGES = 3; + ++new_messages; + if (new_messages > MAX_NEW_MESSAGES) { + break; + } + } + } + + SDL_processing_messages = false; + } + +#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) + /* Windows loses a shift KEYUP event when you have both pressed at once and let go of one. + You won't get a KEYUP until both are released, and that keyup will only be for the second + key you released. Take heroic measures and check the keystate as of the last handled event, + and if we think a key is pressed when Windows doesn't, unstick it in SDL's state. */ + keystate = SDL_GetKeyboardState(NULL); + if (keystate[SDL_SCANCODE_LSHIFT] && !(GetKeyState(VK_LSHIFT) & 0x8000)) { + SDL_SendKeyboardKey(0, SDL_GLOBAL_KEYBOARD_ID, 0, SDL_SCANCODE_LSHIFT, false); + } + if (keystate[SDL_SCANCODE_RSHIFT] && !(GetKeyState(VK_RSHIFT) & 0x8000)) { + SDL_SendKeyboardKey(0, SDL_GLOBAL_KEYBOARD_ID, 0, SDL_SCANCODE_RSHIFT, false); + } + + /* The Windows key state gets lost when using Windows+Space or Windows+G shortcuts and + not grabbing the keyboard. Note: If we *are* grabbing the keyboard, GetKeyState() + will return inaccurate results for VK_LWIN and VK_RWIN but we don't need it anyway. */ + focusWindow = SDL_GetKeyboardFocus(); + if (!focusWindow || !(focusWindow->flags & SDL_WINDOW_KEYBOARD_GRABBED)) { + if (keystate[SDL_SCANCODE_LGUI] && !(GetKeyState(VK_LWIN) & 0x8000)) { + SDL_SendKeyboardKey(0, SDL_GLOBAL_KEYBOARD_ID, 0, SDL_SCANCODE_LGUI, false); + } + if (keystate[SDL_SCANCODE_RGUI] && !(GetKeyState(VK_RWIN) & 0x8000)) { + SDL_SendKeyboardKey(0, SDL_GLOBAL_KEYBOARD_ID, 0, SDL_SCANCODE_RGUI, false); + } + } + + // Update the clipping rect in case someone else has stolen it + if (_this) { + SDL_Window *window = _this->windows; + while (window) { + SDL_WindowData *data = window->internal; + if (data && data->skip_update_clipcursor) { + data->skip_update_clipcursor = false; + WIN_UpdateClipCursor(window); + } + window = window->next; + } + } + + // Update mouse capture + WIN_UpdateMouseCapture(); + + if (!_this->internal->gameinput_context) { + WIN_CheckKeyboardAndMouseHotplug(_this, false); + } + + WIN_UpdateIMECandidates(_this); + +#endif // !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) + +#ifdef SDL_PLATFORM_GDK + GDK_DispatchTaskQueue(); +#endif +} + +static int app_registered = 0; +LPTSTR SDL_Appname = NULL; +Uint32 SDL_Appstyle = 0; +HINSTANCE SDL_Instance = NULL; + +static void WIN_CleanRegisterApp(WNDCLASSEX wcex) +{ +#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) + if (wcex.hIcon) { + DestroyIcon(wcex.hIcon); + } + if (wcex.hIconSm) { + DestroyIcon(wcex.hIconSm); + } +#endif + SDL_free(SDL_Appname); + SDL_Appname = NULL; +} + +#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) +static BOOL CALLBACK WIN_ResourceNameCallback(HMODULE hModule, LPCTSTR lpType, LPTSTR lpName, LONG_PTR lParam) +{ + WNDCLASSEX *wcex = (WNDCLASSEX *)lParam; + + (void)lpType; // We already know that the resource type is RT_GROUP_ICON. + + /* We leave hIconSm as NULL as it will allow Windows to automatically + choose the appropriate small icon size to suit the current DPI. */ + wcex->hIcon = LoadIcon(hModule, lpName); + + // Do not bother enumerating any more. + return FALSE; +} +#endif // !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) + +// Register the class for this application +bool SDL_RegisterApp(const char *name, Uint32 style, void *hInst) +{ + WNDCLASSEX wcex; +#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) + const char *hint; +#endif + + // Only do this once... + if (app_registered) { + ++app_registered; + return true; + } + SDL_assert(!SDL_Appname); + if (!name) { + name = "SDL_app"; +#if defined(CS_BYTEALIGNCLIENT) || defined(CS_OWNDC) + style = (CS_BYTEALIGNCLIENT | CS_OWNDC); +#endif + } + SDL_Appname = WIN_UTF8ToString(name); + SDL_Appstyle = style; + SDL_Instance = hInst ? (HINSTANCE)hInst : GetModuleHandle(NULL); + + // Register the application class + wcex.cbSize = sizeof(WNDCLASSEX); + wcex.hCursor = NULL; + wcex.hIcon = NULL; + wcex.hIconSm = NULL; + wcex.lpszMenuName = NULL; + wcex.lpszClassName = SDL_Appname; + wcex.style = SDL_Appstyle; + wcex.hbrBackground = NULL; + wcex.lpfnWndProc = WIN_WindowProc; + wcex.hInstance = SDL_Instance; + wcex.cbClsExtra = 0; + wcex.cbWndExtra = 0; + +#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) + hint = SDL_GetHint(SDL_HINT_WINDOWS_INTRESOURCE_ICON); + if (hint && *hint) { + wcex.hIcon = LoadIcon(SDL_Instance, MAKEINTRESOURCE(SDL_atoi(hint))); + + hint = SDL_GetHint(SDL_HINT_WINDOWS_INTRESOURCE_ICON_SMALL); + if (hint && *hint) { + wcex.hIconSm = LoadIcon(SDL_Instance, MAKEINTRESOURCE(SDL_atoi(hint))); + } + } else { + // Use the first icon as a default icon, like in the Explorer. + EnumResourceNames(SDL_Instance, RT_GROUP_ICON, WIN_ResourceNameCallback, (LONG_PTR)&wcex); + } +#endif // !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) + + if (!RegisterClassEx(&wcex)) { + WIN_CleanRegisterApp(wcex); + return SDL_SetError("Couldn't register application class"); + } + + app_registered = 1; + return true; +} + +// Unregisters the windowclass registered in SDL_RegisterApp above. +void SDL_UnregisterApp(void) +{ + WNDCLASSEX wcex; + + // SDL_RegisterApp might not have been called before + if (!app_registered) { + return; + } + --app_registered; + if (app_registered == 0) { + // Ensure the icons are initialized. + wcex.hIcon = NULL; + wcex.hIconSm = NULL; + // Check for any registered window classes. +#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) + if (GetClassInfoEx(SDL_Instance, SDL_Appname, &wcex)) { + UnregisterClass(SDL_Appname, SDL_Instance); + } +#endif + WIN_CleanRegisterApp(wcex); + } +} + +#endif // SDL_VIDEO_DRIVER_WINDOWS diff --git a/contrib/SDL-3.2.8/src/video/windows/SDL_windowsevents.h b/contrib/SDL-3.2.8/src/video/windows/SDL_windowsevents.h new file mode 100644 index 0000000..5336013 --- /dev/null +++ b/contrib/SDL-3.2.8/src/video/windows/SDL_windowsevents.h @@ -0,0 +1,39 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include "SDL_internal.h" + +#ifndef SDL_windowsevents_h_ +#define SDL_windowsevents_h_ + +extern LPTSTR SDL_Appname; +extern Uint32 SDL_Appstyle; +extern HINSTANCE SDL_Instance; + +extern LRESULT CALLBACK WIN_KeyboardHookProc(int nCode, WPARAM wParam, LPARAM lParam); +extern LRESULT CALLBACK WIN_WindowProc(HWND hwnd, UINT msg, WPARAM wParam, + LPARAM lParam); +extern void WIN_PollRawInput(SDL_VideoDevice *_this, Uint64 poll_start); +extern void WIN_CheckKeyboardAndMouseHotplug(SDL_VideoDevice *_this, bool initial_check); +extern void WIN_PumpEvents(SDL_VideoDevice *_this); +extern void WIN_SendWakeupEvent(SDL_VideoDevice *_this, SDL_Window *window); +extern int WIN_WaitEventTimeout(SDL_VideoDevice *_this, Sint64 timeoutNS); + +#endif // SDL_windowsevents_h_ diff --git a/contrib/SDL-3.2.8/src/video/windows/SDL_windowsframebuffer.c b/contrib/SDL-3.2.8/src/video/windows/SDL_windowsframebuffer.c new file mode 100644 index 0000000..f2bbd59 --- /dev/null +++ b/contrib/SDL-3.2.8/src/video/windows/SDL_windowsframebuffer.c @@ -0,0 +1,132 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include "SDL_internal.h" + +#if defined(SDL_VIDEO_DRIVER_WINDOWS) && !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) + +#include "SDL_windowsvideo.h" + +bool WIN_CreateWindowFramebuffer(SDL_VideoDevice *_this, SDL_Window *window, SDL_PixelFormat *format, void **pixels, int *pitch) +{ + SDL_WindowData *data = window->internal; + bool isstack; + size_t size; + LPBITMAPINFO info; + HBITMAP hbm; + int w, h; + + SDL_GetWindowSizeInPixels(window, &w, &h); + + // Free the old framebuffer surface + if (data->mdc) { + DeleteDC(data->mdc); + } + if (data->hbm) { + DeleteObject(data->hbm); + } + + // Find out the format of the screen + size = sizeof(BITMAPINFOHEADER) + 256 * sizeof(RGBQUAD); + info = (LPBITMAPINFO)SDL_small_alloc(Uint8, size, &isstack); + if (!info) { + return false; + } + + SDL_memset(info, 0, size); + info->bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + + // The second call to GetDIBits() fills in the bitfields + hbm = CreateCompatibleBitmap(data->hdc, 1, 1); + GetDIBits(data->hdc, hbm, 0, 0, NULL, info, DIB_RGB_COLORS); + GetDIBits(data->hdc, hbm, 0, 0, NULL, info, DIB_RGB_COLORS); + DeleteObject(hbm); + + *format = SDL_PIXELFORMAT_UNKNOWN; + if (info->bmiHeader.biCompression == BI_BITFIELDS) { + int bpp; + Uint32 *masks; + + bpp = info->bmiHeader.biPlanes * info->bmiHeader.biBitCount; + masks = (Uint32 *)((Uint8 *)info + info->bmiHeader.biSize); + *format = SDL_GetPixelFormatForMasks(bpp, masks[0], masks[1], masks[2], 0); + } + if (*format == SDL_PIXELFORMAT_UNKNOWN) { + // We'll use RGB format for now + *format = SDL_PIXELFORMAT_XRGB8888; + + // Create a new one + SDL_memset(info, 0, size); + info->bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + info->bmiHeader.biPlanes = 1; + info->bmiHeader.biBitCount = 32; + info->bmiHeader.biCompression = BI_RGB; + } + + // Fill in the size information + *pitch = (((w * SDL_BYTESPERPIXEL(*format)) + 3) & ~3); + info->bmiHeader.biWidth = w; + info->bmiHeader.biHeight = -h; // negative for topdown bitmap + info->bmiHeader.biSizeImage = (DWORD)h * (*pitch); + + data->mdc = CreateCompatibleDC(data->hdc); + data->hbm = CreateDIBSection(data->hdc, info, DIB_RGB_COLORS, pixels, NULL, 0); + SDL_small_free(info, isstack); + + if (!data->hbm) { + return WIN_SetError("Unable to create DIB"); + } + SelectObject(data->mdc, data->hbm); + + return true; +} + +bool WIN_UpdateWindowFramebuffer(SDL_VideoDevice *_this, SDL_Window *window, const SDL_Rect *rects, int numrects) +{ + SDL_WindowData *data = window->internal; + int i; + + for (i = 0; i < numrects; ++i) { + BitBlt(data->hdc, rects[i].x, rects[i].y, rects[i].w, rects[i].h, + data->mdc, rects[i].x, rects[i].y, SRCCOPY); + } + return true; +} + +void WIN_DestroyWindowFramebuffer(SDL_VideoDevice *_this, SDL_Window *window) +{ + SDL_WindowData *data = window->internal; + + if (!data) { + // The window wasn't fully initialized + return; + } + + if (data->mdc) { + DeleteDC(data->mdc); + data->mdc = NULL; + } + if (data->hbm) { + DeleteObject(data->hbm); + data->hbm = NULL; + } +} + +#endif // SDL_VIDEO_DRIVER_WINDOWS diff --git a/contrib/SDL-3.2.8/src/video/windows/SDL_windowsframebuffer.h b/contrib/SDL-3.2.8/src/video/windows/SDL_windowsframebuffer.h new file mode 100644 index 0000000..bafcfe9 --- /dev/null +++ b/contrib/SDL-3.2.8/src/video/windows/SDL_windowsframebuffer.h @@ -0,0 +1,25 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include "SDL_internal.h" + +extern bool WIN_CreateWindowFramebuffer(SDL_VideoDevice *_this, SDL_Window *window, SDL_PixelFormat *format, void **pixels, int *pitch); +extern bool WIN_UpdateWindowFramebuffer(SDL_VideoDevice *_this, SDL_Window *window, const SDL_Rect *rects, int numrects); +extern void WIN_DestroyWindowFramebuffer(SDL_VideoDevice *_this, SDL_Window *window); diff --git a/contrib/SDL-3.2.8/src/video/windows/SDL_windowsgameinput.c b/contrib/SDL-3.2.8/src/video/windows/SDL_windowsgameinput.c new file mode 100644 index 0000000..183733a --- /dev/null +++ b/contrib/SDL-3.2.8/src/video/windows/SDL_windowsgameinput.c @@ -0,0 +1,612 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include "SDL_internal.h" + +#include "SDL_windowsvideo.h" + +// GameInput currently has a bug with keys stuck on focus change, and crashes on initialization on some systems, so we'll disable it until these issues are fixed. +#undef HAVE_GAMEINPUT_H + +#ifdef HAVE_GAMEINPUT_H + +#include "../../core/windows/SDL_gameinput.h" +#include "../../events/SDL_mouse_c.h" +#include "../../events/SDL_keyboard_c.h" +#include "../../events/scancodes_windows.h" + + +#define MAX_GAMEINPUT_BUTTONS 7 // GameInputMouseWheelTiltRight is the highest button + +static const Uint8 GAMEINPUT_button_map[MAX_GAMEINPUT_BUTTONS] = { + SDL_BUTTON_LEFT, + SDL_BUTTON_RIGHT, + SDL_BUTTON_MIDDLE, + SDL_BUTTON_X1, + SDL_BUTTON_X2, + 6, + 7 +}; + +typedef struct GAMEINPUT_Device +{ + IGameInputDevice *pDevice; + const GameInputDeviceInfo *info; + char *name; + Uint32 instance_id; // generated by SDL + bool registered; + bool delete_requested; + IGameInputReading *last_mouse_reading; + IGameInputReading *last_keyboard_reading; +} GAMEINPUT_Device; + +struct WIN_GameInputData +{ + IGameInput *pGameInput; + GameInputCallbackToken gameinput_callback_token; + int num_devices; + GAMEINPUT_Device **devices; + GameInputKind enabled_input; + SDL_Mutex *lock; + uint64_t timestamp_offset; +}; + +static bool GAMEINPUT_InternalAddOrFind(WIN_GameInputData *data, IGameInputDevice *pDevice) +{ + GAMEINPUT_Device **devicelist = NULL; + GAMEINPUT_Device *device = NULL; + const GameInputDeviceInfo *info; + bool result = false; + + info = IGameInputDevice_GetDeviceInfo(pDevice); + + SDL_LockMutex(data->lock); + { + for (int i = 0; i < data->num_devices; ++i) { + device = data->devices[i]; + if (device && device->pDevice == pDevice) { + // we're already added + device->delete_requested = false; + result = true; + goto done; + } + } + + device = (GAMEINPUT_Device *)SDL_calloc(1, sizeof(*device)); + if (!device) { + goto done; + } + + devicelist = (GAMEINPUT_Device **)SDL_realloc(data->devices, (data->num_devices + 1) * sizeof(*devicelist)); + if (!devicelist) { + SDL_free(device); + goto done; + } + + if (info->deviceStrings) { + // In theory we could get the manufacturer and product strings here, but they're NULL for all the devices I've tested + } + + if (info->displayName) { + // This could give us a product string, but it's NULL for all the devices I've tested + } + + IGameInputDevice_AddRef(pDevice); + device->pDevice = pDevice; + device->instance_id = SDL_GetNextObjectID(); + device->info = info; + + data->devices = devicelist; + data->devices[data->num_devices++] = device; + + result = true; + } +done: + SDL_UnlockMutex(data->lock); + + return result; +} + +static bool GAMEINPUT_InternalRemoveByIndex(WIN_GameInputData *data, int idx) +{ + GAMEINPUT_Device **devicelist = NULL; + GAMEINPUT_Device *device; + bool result = false; + + SDL_LockMutex(data->lock); + { + if (idx < 0 || idx >= data->num_devices) { + result = SDL_SetError("GAMEINPUT_InternalRemoveByIndex argument idx %d is out of range", idx); + goto done; + } + + device = data->devices[idx]; + if (device) { + if (device->registered) { + if (device->info->supportedInput & GameInputKindMouse) { + SDL_RemoveMouse(device->instance_id, true); + } + if (device->info->supportedInput & GameInputKindKeyboard) { + SDL_RemoveKeyboard(device->instance_id, true); + } + if (device->last_mouse_reading) { + IGameInputReading_Release(device->last_mouse_reading); + device->last_mouse_reading = NULL; + } + if (device->last_keyboard_reading) { + IGameInputReading_Release(device->last_keyboard_reading); + device->last_keyboard_reading = NULL; + } + } + IGameInputDevice_Release(device->pDevice); + SDL_free(device->name); + SDL_free(device); + } + data->devices[idx] = NULL; + + if (data->num_devices == 1) { + // last element in the list, free the entire list then + SDL_free(data->devices); + data->devices = NULL; + } else { + if (idx != data->num_devices - 1) { + size_t bytes = sizeof(*devicelist) * (data->num_devices - idx - 1); + SDL_memmove(&data->devices[idx], &data->devices[idx + 1], bytes); + } + } + + // decrement the count and return + --data->num_devices; + result = true; + } +done: + SDL_UnlockMutex(data->lock); + + return result; +} + +static void CALLBACK GAMEINPUT_InternalDeviceCallback( + _In_ GameInputCallbackToken callbackToken, + _In_ void* context, + _In_ IGameInputDevice *pDevice, + _In_ uint64_t timestamp, + _In_ GameInputDeviceStatus currentStatus, + _In_ GameInputDeviceStatus previousStatus) +{ + WIN_GameInputData *data = (WIN_GameInputData *)context; + int idx = 0; + GAMEINPUT_Device *device = NULL; + + if (!pDevice) { + // This should never happen, but ignore it if it does + return; + } + + if (currentStatus & GameInputDeviceConnected) { + GAMEINPUT_InternalAddOrFind(data, pDevice); + } else { + for (idx = 0; idx < data->num_devices; ++idx) { + device = data->devices[idx]; + if (device && device->pDevice == pDevice) { + // will be deleted on the next Detect call + device->delete_requested = true; + break; + } + } + } +} + +bool WIN_InitGameInput(SDL_VideoDevice *_this) +{ + WIN_GameInputData *data; + HRESULT hr; + bool result = false; + + if (_this->internal->gameinput_context) { + return true; + } + + data = (WIN_GameInputData *)SDL_calloc(1, sizeof(*data)); + if (!data) { + goto done; + } + _this->internal->gameinput_context = data; + + data->lock = SDL_CreateMutex(); + if (!data->lock) { + goto done; + } + + if (!SDL_InitGameInput(&data->pGameInput)) { + goto done; + } + + hr = IGameInput_RegisterDeviceCallback(data->pGameInput, + NULL, + (GameInputKindMouse | GameInputKindKeyboard), + GameInputDeviceConnected, + GameInputBlockingEnumeration, + data, + GAMEINPUT_InternalDeviceCallback, + &data->gameinput_callback_token); + if (FAILED(hr)) { + SDL_SetError("IGameInput::RegisterDeviceCallback failure with HRESULT of %08X", hr); + goto done; + } + + // Calculate the relative offset between SDL timestamps and GameInput timestamps + Uint64 now = SDL_GetTicksNS(); + uint64_t timestampUS = IGameInput_GetCurrentTimestamp(data->pGameInput); + data->timestamp_offset = (SDL_NS_TO_US(now) - timestampUS); + + result = true; + +done: + if (!result) { + WIN_QuitGameInput(_this); + } + return result; +} + +static void GAMEINPUT_InitialMouseReading(WIN_GameInputData *data, SDL_Window *window, GAMEINPUT_Device *device, IGameInputReading *reading) +{ + GameInputMouseState state; + if (SUCCEEDED(IGameInputReading_GetMouseState(reading, &state))) { + Uint64 timestamp = SDL_US_TO_NS(IGameInputReading_GetTimestamp(reading) + data->timestamp_offset); + SDL_MouseID mouseID = device->instance_id; + + for (int i = 0; i < MAX_GAMEINPUT_BUTTONS; ++i) { + const GameInputMouseButtons mask = (1 << i); + bool down = ((state.buttons & mask) != 0); + SDL_SendMouseButton(timestamp, window, mouseID, GAMEINPUT_button_map[i], down); + } + } +} + +static void GAMEINPUT_HandleMouseDelta(WIN_GameInputData *data, SDL_Window *window, GAMEINPUT_Device *device, IGameInputReading *last_reading, IGameInputReading *reading) +{ + GameInputMouseState last; + GameInputMouseState state; + if (SUCCEEDED(IGameInputReading_GetMouseState(last_reading, &last)) && + SUCCEEDED(IGameInputReading_GetMouseState(reading, &state))) { + Uint64 timestamp = SDL_US_TO_NS(IGameInputReading_GetTimestamp(reading) + data->timestamp_offset); + SDL_MouseID mouseID = device->instance_id; + + GameInputMouseState delta; + delta.buttons = (state.buttons ^ last.buttons); + delta.positionX = (state.positionX - last.positionX); + delta.positionY = (state.positionY - last.positionY); + delta.wheelX = (state.wheelX - last.wheelX); + delta.wheelY = (state.wheelY - last.wheelY); + + if (delta.positionX || delta.positionY) { + SDL_SendMouseMotion(timestamp, window, mouseID, true, (float)delta.positionX, (float)delta.positionY); + } + if (delta.buttons) { + for (int i = 0; i < MAX_GAMEINPUT_BUTTONS; ++i) { + const GameInputMouseButtons mask = (1 << i); + if (delta.buttons & mask) { + bool down = ((state.buttons & mask) != 0); + SDL_SendMouseButton(timestamp, window, mouseID, GAMEINPUT_button_map[i], down); + } + } + } + if (delta.wheelX || delta.wheelY) { + float fAmountX = (float)delta.wheelX / WHEEL_DELTA; + float fAmountY = (float)delta.wheelY / WHEEL_DELTA; + SDL_SendMouseWheel(timestamp, SDL_GetMouseFocus(), device->instance_id, fAmountX, fAmountY, SDL_MOUSEWHEEL_NORMAL); + } + } +} + +static SDL_Scancode GetScancodeFromKeyState(const GameInputKeyState *state) +{ + Uint8 index = (Uint8)(state->scanCode & 0xFF); + if ((state->scanCode & 0xFF00) == 0xE000) { + index |= 0x80; + } + return windows_scancode_table[index]; +} + +static bool KeysHaveScancode(const GameInputKeyState *keys, uint32_t count, SDL_Scancode scancode) +{ + for (uint32_t i = 0; i < count; ++i) { + if (GetScancodeFromKeyState(&keys[i]) == scancode) { + return true; + } + } + return false; +} + +static void GAMEINPUT_InitialKeyboardReading(WIN_GameInputData *data, SDL_Window *window, GAMEINPUT_Device *device, IGameInputReading *reading) +{ + Uint64 timestamp = SDL_US_TO_NS(IGameInputReading_GetTimestamp(reading) + data->timestamp_offset); + SDL_KeyboardID keyboardID = device->instance_id; + + uint32_t max_keys = device->info->keyboardInfo->maxSimultaneousKeys; + GameInputKeyState *keys = SDL_stack_alloc(GameInputKeyState, max_keys); + if (!keys) { + return; + } + + uint32_t num_keys = IGameInputReading_GetKeyState(reading, max_keys, keys); + if (!num_keys) { + // FIXME: We probably need to track key state by keyboardID + SDL_ResetKeyboard(); + return; + } + + // Go through and send key up events for any key that's not held down + int num_scancodes; + const bool *keyboard_state = SDL_GetKeyboardState(&num_scancodes); + for (int i = 0; i < num_scancodes; ++i) { + if (keyboard_state[i] && !KeysHaveScancode(keys, num_keys, (SDL_Scancode)i)) { + SDL_SendKeyboardKey(timestamp, keyboardID, keys[i].scanCode, (SDL_Scancode)i, false); + } + } + + // Go through and send key down events for any key that's held down + for (uint32_t i = 0; i < num_keys; ++i) { + SDL_SendKeyboardKey(timestamp, keyboardID, keys[i].scanCode, GetScancodeFromKeyState(&keys[i]), true); + } +} + +#ifdef DEBUG_KEYS +static void DumpKeys(const char *prefix, GameInputKeyState *keys, uint32_t count) +{ + SDL_Log("%s", prefix); + for (uint32_t i = 0; i < count; ++i) { + char str[5]; + *SDL_UCS4ToUTF8(keys[i].codePoint, str) = '\0'; + SDL_Log(" Key 0x%.2x (%s)", keys[i].scanCode, str); + } +} +#endif // DEBUG_KEYS + +static void GAMEINPUT_HandleKeyboardDelta(WIN_GameInputData *data, SDL_Window *window, GAMEINPUT_Device *device, IGameInputReading *last_reading, IGameInputReading *reading) +{ + Uint64 timestamp = SDL_US_TO_NS(IGameInputReading_GetTimestamp(reading) + data->timestamp_offset); + SDL_KeyboardID keyboardID = device->instance_id; + + uint32_t max_keys = device->info->keyboardInfo->maxSimultaneousKeys; + GameInputKeyState *last = SDL_stack_alloc(GameInputKeyState, max_keys); + GameInputKeyState *keys = SDL_stack_alloc(GameInputKeyState, max_keys); + if (!last || !keys) { + return; + } + + uint32_t index_last = 0; + uint32_t index_keys = 0; + uint32_t num_last = IGameInputReading_GetKeyState(last_reading, max_keys, last); + uint32_t num_keys = IGameInputReading_GetKeyState(reading, max_keys, keys); +#ifdef DEBUG_KEYS + SDL_Log("Timestamp: %llu", timestamp); + DumpKeys("Last keys:", last, num_last); + DumpKeys("New keys:", keys, num_keys); +#endif + while (index_last < num_last || index_keys < num_keys) { + if (index_last < num_last && index_keys < num_keys) { + if (last[index_last].scanCode == keys[index_keys].scanCode) { + // No change + ++index_last; + ++index_keys; + } else { + // This key was released + SDL_SendKeyboardKey(timestamp, keyboardID, last[index_last].scanCode, GetScancodeFromKeyState(&last[index_last]), false); + ++index_last; + } + } else if (index_last < num_last) { + // This key was released + SDL_SendKeyboardKey(timestamp, keyboardID, last[index_last].scanCode, GetScancodeFromKeyState(&last[index_last]), false); + ++index_last; + } else { + // This key was pressed + SDL_SendKeyboardKey(timestamp, keyboardID, keys[index_keys].scanCode, GetScancodeFromKeyState(&keys[index_keys]), true); + ++index_keys; + } + } +} + +void WIN_UpdateGameInput(SDL_VideoDevice *_this) +{ + WIN_GameInputData *data = _this->internal->gameinput_context; + + SDL_LockMutex(data->lock); + { + // Key events and relative mouse motion both go to the window with keyboard focus + SDL_Window *window = SDL_GetKeyboardFocus(); + + for (int i = 0; i < data->num_devices; ++i) { + GAMEINPUT_Device *device = data->devices[i]; + IGameInputReading *reading; + + if (!device->registered) { + if (device->info->supportedInput & GameInputKindMouse) { + SDL_AddMouse(device->instance_id, device->name, true); + } + if (device->info->supportedInput & GameInputKindKeyboard) { + SDL_AddKeyboard(device->instance_id, device->name, true); + } + device->registered = true; + } + + if (device->delete_requested) { + GAMEINPUT_InternalRemoveByIndex(data, i--); + continue; + } + + if (!(device->info->supportedInput & data->enabled_input)) { + continue; + } + + if (!window) { + continue; + } + + if (data->enabled_input & GameInputKindMouse) { + if (device->last_mouse_reading) { + HRESULT hr; + while (SUCCEEDED(hr = IGameInput_GetNextReading(data->pGameInput, device->last_mouse_reading, GameInputKindMouse, device->pDevice, &reading))) { + GAMEINPUT_HandleMouseDelta(data, window, device, device->last_mouse_reading, reading); + IGameInputReading_Release(device->last_mouse_reading); + device->last_mouse_reading = reading; + } + if (hr != GAMEINPUT_E_READING_NOT_FOUND) { + if (SUCCEEDED(IGameInput_GetCurrentReading(data->pGameInput, GameInputKindMouse, device->pDevice, &reading))) { + GAMEINPUT_HandleMouseDelta(data, window, device, device->last_mouse_reading, reading); + IGameInputReading_Release(device->last_mouse_reading); + device->last_mouse_reading = reading; + } + } + } else { + if (SUCCEEDED(IGameInput_GetCurrentReading(data->pGameInput, GameInputKindMouse, device->pDevice, &reading))) { + GAMEINPUT_InitialMouseReading(data, window, device, reading); + device->last_mouse_reading = reading; + } + } + } + + if (data->enabled_input & GameInputKindKeyboard) { + if (window->text_input_active) { + // Reset raw input while text input is active + if (device->last_keyboard_reading) { + IGameInputReading_Release(device->last_keyboard_reading); + device->last_keyboard_reading = NULL; + } + } else { + if (device->last_keyboard_reading) { + HRESULT hr; + while (SUCCEEDED(hr = IGameInput_GetNextReading(data->pGameInput, device->last_keyboard_reading, GameInputKindKeyboard, device->pDevice, &reading))) { + GAMEINPUT_HandleKeyboardDelta(data, window, device, device->last_keyboard_reading, reading); + IGameInputReading_Release(device->last_keyboard_reading); + device->last_keyboard_reading = reading; + } + if (hr != GAMEINPUT_E_READING_NOT_FOUND) { + if (SUCCEEDED(IGameInput_GetCurrentReading(data->pGameInput, GameInputKindKeyboard, device->pDevice, &reading))) { + GAMEINPUT_HandleKeyboardDelta(data, window, device, device->last_keyboard_reading, reading); + IGameInputReading_Release(device->last_keyboard_reading); + device->last_keyboard_reading = reading; + } + } + } else { + if (SUCCEEDED(IGameInput_GetCurrentReading(data->pGameInput, GameInputKindKeyboard, device->pDevice, &reading))) { + GAMEINPUT_InitialKeyboardReading(data, window, device, reading); + device->last_keyboard_reading = reading; + } + } + } + } + } + } + SDL_UnlockMutex(data->lock); +} + +bool WIN_UpdateGameInputEnabled(SDL_VideoDevice *_this) +{ + WIN_GameInputData *data = _this->internal->gameinput_context; + bool raw_mouse_enabled = _this->internal->raw_mouse_enabled; + bool raw_keyboard_enabled = _this->internal->raw_keyboard_enabled; + + SDL_LockMutex(data->lock); + { + data->enabled_input = (raw_mouse_enabled ? GameInputKindMouse : GameInputKindUnknown) | + (raw_keyboard_enabled ? GameInputKindKeyboard : GameInputKindUnknown); + + // Reset input if not enabled + for (int i = 0; i < data->num_devices; ++i) { + GAMEINPUT_Device *device = data->devices[i]; + + if (device->last_mouse_reading && !raw_mouse_enabled) { + IGameInputReading_Release(device->last_mouse_reading); + device->last_mouse_reading = NULL; + } + + if (device->last_keyboard_reading && !raw_keyboard_enabled) { + IGameInputReading_Release(device->last_keyboard_reading); + device->last_keyboard_reading = NULL; + } + } + } + SDL_UnlockMutex(data->lock); + + return true; +} + +void WIN_QuitGameInput(SDL_VideoDevice *_this) +{ + WIN_GameInputData *data = _this->internal->gameinput_context; + + if (!data) { + return; + } + + if (data->pGameInput) { + // free the callback + if (data->gameinput_callback_token != GAMEINPUT_INVALID_CALLBACK_TOKEN_VALUE) { + IGameInput_UnregisterCallback(data->pGameInput, data->gameinput_callback_token, /*timeoutInUs:*/ 10000); + data->gameinput_callback_token = GAMEINPUT_INVALID_CALLBACK_TOKEN_VALUE; + } + + // free the list + while (data->num_devices > 0) { + GAMEINPUT_InternalRemoveByIndex(data, 0); + } + + IGameInput_Release(data->pGameInput); + data->pGameInput = NULL; + } + + if (data->pGameInput) { + SDL_QuitGameInput(); + data->pGameInput = NULL; + } + + if (data->lock) { + SDL_DestroyMutex(data->lock); + data->lock = NULL; + } + + SDL_free(data); + _this->internal->gameinput_context = NULL; +} + +#else // !HAVE_GAMEINPUT_H + +bool WIN_InitGameInput(SDL_VideoDevice* _this) +{ + return SDL_Unsupported(); +} + +bool WIN_UpdateGameInputEnabled(SDL_VideoDevice *_this) +{ + return SDL_Unsupported(); +} + +void WIN_UpdateGameInput(SDL_VideoDevice* _this) +{ + return; +} + +void WIN_QuitGameInput(SDL_VideoDevice* _this) +{ + return; +} + +#endif // HAVE_GAMEINPUT_H diff --git a/contrib/SDL-3.2.8/src/video/windows/SDL_windowsgameinput.h b/contrib/SDL-3.2.8/src/video/windows/SDL_windowsgameinput.h new file mode 100644 index 0000000..561de9c --- /dev/null +++ b/contrib/SDL-3.2.8/src/video/windows/SDL_windowsgameinput.h @@ -0,0 +1,29 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include "SDL_internal.h" + +typedef struct WIN_GameInputData WIN_GameInputData; + +extern bool WIN_InitGameInput(SDL_VideoDevice *_this); +extern bool WIN_UpdateGameInputEnabled(SDL_VideoDevice *_this); +extern void WIN_UpdateGameInput(SDL_VideoDevice *_this); +extern void WIN_QuitGameInput(SDL_VideoDevice *_this); + diff --git a/contrib/SDL-3.2.8/src/video/windows/SDL_windowskeyboard.c b/contrib/SDL-3.2.8/src/video/windows/SDL_windowskeyboard.c new file mode 100644 index 0000000..75e8ad4 --- /dev/null +++ b/contrib/SDL-3.2.8/src/video/windows/SDL_windowskeyboard.c @@ -0,0 +1,1145 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include "SDL_internal.h" + +#if defined(SDL_VIDEO_DRIVER_WINDOWS) && !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) + +#include "SDL_windowsvideo.h" + +#include "../../events/SDL_keyboard_c.h" +#include "../../events/scancodes_windows.h" + +#include +#include + +#ifndef SDL_DISABLE_WINDOWS_IME +#if 0 +#define SDL_DebugIMELog SDL_Log +#else +#define SDL_DebugIMELog(...) +#endif +static bool IME_Init(SDL_VideoData *videodata, SDL_Window *window); +static void IME_Enable(SDL_VideoData *videodata, HWND hwnd); +static void IME_Disable(SDL_VideoData *videodata, HWND hwnd); +static void IME_SetTextInputArea(SDL_VideoData *videodata, HWND hwnd, const SDL_Rect *rect, int cursor); +static void IME_ClearComposition(SDL_VideoData *videodata); +static void IME_GetCandidateList(SDL_VideoData *videodata, HWND hwnd); +static void IME_Quit(SDL_VideoData *videodata); +#else +static void IME_SetTextInputArea(SDL_VideoData *videodata, HWND hwnd, const SDL_Rect *rect, int cursor); +#endif // !SDL_DISABLE_WINDOWS_IME + +#ifndef MAPVK_VK_TO_VSC +#define MAPVK_VK_TO_VSC 0 +#endif +#ifndef MAPVK_VSC_TO_VK +#define MAPVK_VSC_TO_VK 1 +#endif + +// Alphabetic scancodes for PC keyboards +void WIN_InitKeyboard(SDL_VideoDevice *_this) +{ +#ifndef SDL_DISABLE_WINDOWS_IME + SDL_VideoData *data = _this->internal; + + data->ime_candlistindexbase = 1; + data->ime_composition_length = 32 * sizeof(WCHAR); + data->ime_composition = (WCHAR *)SDL_calloc(data->ime_composition_length, sizeof(WCHAR)); +#endif // !SDL_DISABLE_WINDOWS_IME + + WIN_UpdateKeymap(false); + + SDL_SetScancodeName(SDL_SCANCODE_APPLICATION, "Menu"); + SDL_SetScancodeName(SDL_SCANCODE_LGUI, "Left Windows"); + SDL_SetScancodeName(SDL_SCANCODE_RGUI, "Right Windows"); + + // Are system caps/num/scroll lock active? Set our state to match. + SDL_ToggleModState(SDL_KMOD_CAPS, (GetKeyState(VK_CAPITAL) & 0x0001) ? true : false); + SDL_ToggleModState(SDL_KMOD_NUM, (GetKeyState(VK_NUMLOCK) & 0x0001) ? true : false); + SDL_ToggleModState(SDL_KMOD_SCROLL, (GetKeyState(VK_SCROLL) & 0x0001) ? true : false); +} + +void WIN_UpdateKeymap(bool send_event) +{ + SDL_Scancode scancode; + SDL_Keymap *keymap; + BYTE keyboardState[256] = { 0 }; + WCHAR buffer[16]; + SDL_Keymod mods[] = { + SDL_KMOD_NONE, + SDL_KMOD_SHIFT, + SDL_KMOD_CAPS, + (SDL_KMOD_SHIFT | SDL_KMOD_CAPS), + SDL_KMOD_MODE, + (SDL_KMOD_MODE | SDL_KMOD_SHIFT), + (SDL_KMOD_MODE | SDL_KMOD_CAPS), + (SDL_KMOD_MODE | SDL_KMOD_SHIFT | SDL_KMOD_CAPS) + }; + + WIN_ResetDeadKeys(); + + keymap = SDL_CreateKeymap(); + + for (int m = 0; m < SDL_arraysize(mods); ++m) { + for (int i = 0; i < SDL_arraysize(windows_scancode_table); i++) { + int vk, sc, result; + Uint32 *ch = 0; + + // Make sure this scancode is a valid character scancode + scancode = windows_scancode_table[i]; + if (scancode == SDL_SCANCODE_UNKNOWN || + scancode == SDL_SCANCODE_DELETE || + (SDL_GetKeymapKeycode(NULL, scancode, SDL_KMOD_NONE) & SDLK_SCANCODE_MASK)) { + + // The Colemak mapping swaps Backspace and CapsLock + if (mods[m] == SDL_KMOD_NONE && + (scancode == SDL_SCANCODE_CAPSLOCK || + scancode == SDL_SCANCODE_BACKSPACE)) { + vk = LOBYTE(MapVirtualKey(i, MAPVK_VSC_TO_VK)); + if (vk == VK_CAPITAL) { + SDL_SetKeymapEntry(keymap, scancode, mods[m], SDLK_CAPSLOCK); + } else if (vk == VK_BACK) { + SDL_SetKeymapEntry(keymap, scancode, mods[m], SDLK_BACKSPACE); + } + } + continue; + } + + // Unpack the single byte index to make the scan code. + sc = MAKEWORD(i & 0x7f, (i & 0x80) ? 0xe0 : 0x00); + vk = LOBYTE(MapVirtualKey(sc, MAPVK_VSC_TO_VK)); + if (!vk) { + continue; + } + + // Update the keyboard state for the modifiers + keyboardState[VK_SHIFT] = (mods[m] & SDL_KMOD_SHIFT) ? 0x80 : 0x00; + keyboardState[VK_CAPITAL] = (mods[m] & SDL_KMOD_CAPS) ? 0x01 : 0x00; + keyboardState[VK_CONTROL] = (mods[m] & SDL_KMOD_MODE) ? 0x80 : 0x00; + keyboardState[VK_MENU] = (mods[m] & SDL_KMOD_MODE) ? 0x80 : 0x00; + + result = ToUnicode(vk, sc, keyboardState, buffer, 16, 0); + buffer[SDL_abs(result)] = 0; + + // Convert UTF-16 to UTF-32 code points + ch = (Uint32 *)SDL_iconv_string("UTF-32LE", "UTF-16LE", (const char *)buffer, (SDL_abs(result) + 1) * sizeof(WCHAR)); + if (ch) { + /* Windows keyboard layouts can emit several UTF-32 code points on a single key press. + * Use since we cannot fit into single SDL_Keycode value in SDL keymap. + * See https://kbdlayout.info/features/ligatures for a list of such keys. */ + SDL_SetKeymapEntry(keymap, scancode, mods[m], ch[1] == 0 ? ch[0] : 0xfffd); + SDL_free(ch); + } else { + // The default keymap doesn't have any SDL_KMOD_MODE entries, so we don't need to override them + if (!(mods[m] & SDL_KMOD_MODE)) { + SDL_SetKeymapEntry(keymap, scancode, mods[m], SDLK_UNKNOWN); + } + } + + if (result < 0) { + WIN_ResetDeadKeys(); + } + } + } + + SDL_SetKeymap(keymap, send_event); +} + +void WIN_QuitKeyboard(SDL_VideoDevice *_this) +{ +#ifndef SDL_DISABLE_WINDOWS_IME + SDL_VideoData *data = _this->internal; + + IME_Quit(data); + + if (data->ime_composition) { + SDL_free(data->ime_composition); + data->ime_composition = NULL; + } +#endif // !SDL_DISABLE_WINDOWS_IME +} + +void WIN_ResetDeadKeys(void) +{ + /* + if a deadkey has been typed, but not the next character (which the deadkey might modify), + this tries to undo the effect pressing the deadkey. + see: http://archives.miloush.net/michkap/archive/2006/09/10/748775.html + */ + BYTE keyboardState[256]; + WCHAR buffer[16]; + int vk, sc, result, i; + + if (!GetKeyboardState(keyboardState)) { + return; + } + + vk = VK_SPACE; + sc = MapVirtualKey(vk, MAPVK_VK_TO_VSC); + if (sc == 0) { + // the keyboard doesn't have this key + return; + } + + for (i = 0; i < 5; i++) { + result = ToUnicode(vk, sc, keyboardState, buffer, 16, 0); + if (result > 0) { + // success + return; + } + } +} + +bool WIN_StartTextInput(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID props) +{ + WIN_ResetDeadKeys(); + +#ifndef SDL_DISABLE_WINDOWS_IME + HWND hwnd = window->internal->hwnd; + SDL_VideoData *videodata = _this->internal; + IME_Init(videodata, window); + IME_Enable(videodata, hwnd); + + WIN_UpdateTextInputArea(_this, window); +#endif // !SDL_DISABLE_WINDOWS_IME + + return true; +} + +bool WIN_StopTextInput(SDL_VideoDevice *_this, SDL_Window *window) +{ + WIN_ResetDeadKeys(); + +#ifndef SDL_DISABLE_WINDOWS_IME + HWND hwnd = window->internal->hwnd; + SDL_VideoData *videodata = _this->internal; + IME_Init(videodata, window); + IME_Disable(videodata, hwnd); +#endif // !SDL_DISABLE_WINDOWS_IME + + return true; +} + +bool WIN_UpdateTextInputArea(SDL_VideoDevice *_this, SDL_Window *window) +{ + SDL_VideoData *videodata = _this->internal; + SDL_WindowData *data = window->internal; + + IME_SetTextInputArea(videodata, data->hwnd, &window->text_input_rect, window->text_input_cursor); + return true; +} + +bool WIN_ClearComposition(SDL_VideoDevice *_this, SDL_Window *window) +{ +#ifndef SDL_DISABLE_WINDOWS_IME + SDL_VideoData *videodata = _this->internal; + + IME_ClearComposition(videodata); +#endif + return true; +} + +#ifdef SDL_DISABLE_WINDOWS_IME + +bool WIN_HandleIMEMessage(HWND hwnd, UINT msg, WPARAM wParam, LPARAM *lParam, SDL_VideoData *videodata) +{ + return false; +} + +void WIN_UpdateIMECandidates(SDL_VideoDevice *_this) +{ + return; +} + +#else + +#define LANG_CHT MAKELANGID(LANG_CHINESE, SUBLANG_CHINESE_TRADITIONAL) +#define LANG_CHS MAKELANGID(LANG_CHINESE, SUBLANG_CHINESE_SIMPLIFIED) + +#define MAKEIMEVERSION(major, minor) ((DWORD)(((BYTE)(major) << 24) | ((BYTE)(minor) << 16))) +#define IMEID_VER(id) ((id)&0xffff0000) +#define IMEID_LANG(id) ((id)&0x0000ffff) + +#define CHT_HKL_DAYI ((HKL)(UINT_PTR)0xE0060404) +#define CHT_HKL_NEW_PHONETIC ((HKL)(UINT_PTR)0xE0080404) +#define CHT_HKL_NEW_CHANG_JIE ((HKL)(UINT_PTR)0xE0090404) +#define CHT_HKL_NEW_QUICK ((HKL)(UINT_PTR)0xE00A0404) +#define CHT_HKL_HK_CANTONESE ((HKL)(UINT_PTR)0xE00B0404) +#define CHT_IMEFILENAME1 "TINTLGNT.IME" +#define CHT_IMEFILENAME2 "CINTLGNT.IME" +#define CHT_IMEFILENAME3 "MSTCIPHA.IME" +#define IMEID_CHT_VER42 (LANG_CHT | MAKEIMEVERSION(4, 2)) +#define IMEID_CHT_VER43 (LANG_CHT | MAKEIMEVERSION(4, 3)) +#define IMEID_CHT_VER44 (LANG_CHT | MAKEIMEVERSION(4, 4)) +#define IMEID_CHT_VER50 (LANG_CHT | MAKEIMEVERSION(5, 0)) +#define IMEID_CHT_VER51 (LANG_CHT | MAKEIMEVERSION(5, 1)) +#define IMEID_CHT_VER52 (LANG_CHT | MAKEIMEVERSION(5, 2)) +#define IMEID_CHT_VER60 (LANG_CHT | MAKEIMEVERSION(6, 0)) +#define IMEID_CHT_VER_VISTA (LANG_CHT | MAKEIMEVERSION(7, 0)) + +#define CHS_HKL ((HKL)(UINT_PTR)0xE00E0804) +#define CHS_IMEFILENAME1 "PINTLGNT.IME" +#define CHS_IMEFILENAME2 "MSSCIPYA.IME" +#define IMEID_CHS_VER41 (LANG_CHS | MAKEIMEVERSION(4, 1)) +#define IMEID_CHS_VER42 (LANG_CHS | MAKEIMEVERSION(4, 2)) +#define IMEID_CHS_VER53 (LANG_CHS | MAKEIMEVERSION(5, 3)) + +#define LANG() LOWORD((videodata->ime_hkl)) +#define PRIMLANG() ((WORD)PRIMARYLANGID(LANG())) +#define SUBLANG() SUBLANGID(LANG()) + +static void IME_UpdateInputLocale(SDL_VideoData *videodata); +static void IME_SetWindow(SDL_VideoData *videodata, SDL_Window *window); +static void IME_SetupAPI(SDL_VideoData *videodata); +static DWORD IME_GetId(SDL_VideoData *videodata, UINT uIndex); +static void IME_SendEditingEvent(SDL_VideoData *videodata); +static void IME_SendClearComposition(SDL_VideoData *videodata); + +static bool IME_Init(SDL_VideoData *videodata, SDL_Window *window) +{ + HWND hwnd = window->internal->hwnd; + + if (videodata->ime_initialized) { + return true; + } + + const char *hint = SDL_GetHint(SDL_HINT_IME_IMPLEMENTED_UI); + if (hint && SDL_strstr(hint, "composition")) { + videodata->ime_internal_composition = true; + } + if (hint && SDL_strstr(hint, "candidates")) { + videodata->ime_internal_candidates = true; + } + + videodata->ime_hwnd_main = hwnd; + videodata->ime_initialized = true; + videodata->ime_himm32 = SDL_LoadObject("imm32.dll"); + if (!videodata->ime_himm32) { + videodata->ime_available = false; + SDL_ClearError(); + return true; + } + /* *INDENT-OFF* */ // clang-format off + videodata->ImmLockIMC = (LPINPUTCONTEXT2 (WINAPI *)(HIMC))SDL_LoadFunction(videodata->ime_himm32, "ImmLockIMC"); + videodata->ImmUnlockIMC = (BOOL (WINAPI *)(HIMC))SDL_LoadFunction(videodata->ime_himm32, "ImmUnlockIMC"); + videodata->ImmLockIMCC = (LPVOID (WINAPI *)(HIMCC))SDL_LoadFunction(videodata->ime_himm32, "ImmLockIMCC"); + videodata->ImmUnlockIMCC = (BOOL (WINAPI *)(HIMCC))SDL_LoadFunction(videodata->ime_himm32, "ImmUnlockIMCC"); + /* *INDENT-ON* */ // clang-format on + + IME_SetWindow(videodata, window); + videodata->ime_himc = ImmGetContext(hwnd); + ImmReleaseContext(hwnd, videodata->ime_himc); + if (!videodata->ime_himc) { + videodata->ime_available = false; + IME_Disable(videodata, hwnd); + return true; + } + videodata->ime_available = true; + IME_UpdateInputLocale(videodata); + IME_SetupAPI(videodata); + IME_UpdateInputLocale(videodata); + IME_Disable(videodata, hwnd); + return true; +} + +static void IME_Enable(SDL_VideoData *videodata, HWND hwnd) +{ + if (!videodata->ime_initialized || !videodata->ime_hwnd_current) { + return; + } + + if (!videodata->ime_available) { + IME_Disable(videodata, hwnd); + return; + } + if (videodata->ime_hwnd_current == videodata->ime_hwnd_main) { + ImmAssociateContext(videodata->ime_hwnd_current, videodata->ime_himc); + } + + videodata->ime_enabled = true; + IME_UpdateInputLocale(videodata); +} + +static void IME_Disable(SDL_VideoData *videodata, HWND hwnd) +{ + if (!videodata->ime_initialized || !videodata->ime_hwnd_current) { + return; + } + + IME_ClearComposition(videodata); + if (videodata->ime_hwnd_current == videodata->ime_hwnd_main) { + ImmAssociateContext(videodata->ime_hwnd_current, (HIMC)0); + } + + videodata->ime_enabled = false; +} + +static void IME_Quit(SDL_VideoData *videodata) +{ + if (!videodata->ime_initialized) { + return; + } + + if (videodata->ime_hwnd_main) { + ImmAssociateContext(videodata->ime_hwnd_main, videodata->ime_himc); + } + + videodata->ime_hwnd_main = 0; + videodata->ime_himc = 0; + if (videodata->ime_himm32) { + SDL_UnloadObject(videodata->ime_himm32); + videodata->ime_himm32 = 0; + } + for (int i = 0; i < videodata->ime_candcount; ++i) { + SDL_free(videodata->ime_candidates[i]); + videodata->ime_candidates[i] = NULL; + } + videodata->ime_initialized = false; +} + +static void IME_GetReadingString(SDL_VideoData *videodata, HWND hwnd) +{ + DWORD id = 0; + HIMC himc = 0; + WCHAR buffer[16]; + WCHAR *s = buffer; + DWORD len = 0; + INT err = 0; + BOOL vertical = FALSE; + UINT maxuilen = 0; + + videodata->ime_readingstring[0] = 0; + + id = IME_GetId(videodata, 0); + if (!id) { + return; + } + + himc = ImmGetContext(hwnd); + if (!himc) { + return; + } + + if (videodata->GetReadingString) { + len = videodata->GetReadingString(himc, 0, 0, &err, &vertical, &maxuilen); + if (len) { + if (len > SDL_arraysize(buffer)) { + len = SDL_arraysize(buffer); + } + + len = videodata->GetReadingString(himc, len, s, &err, &vertical, &maxuilen); + } + SDL_wcslcpy(videodata->ime_readingstring, s, len); + } else { + LPINPUTCONTEXT2 lpimc = videodata->ImmLockIMC(himc); + LPBYTE p = 0; + s = 0; + switch (id) { + case IMEID_CHT_VER42: + case IMEID_CHT_VER43: + case IMEID_CHT_VER44: + p = *(LPBYTE *)((LPBYTE)videodata->ImmLockIMCC(lpimc->hPrivate) + 24); + if (!p) { + break; + } + + len = *(DWORD *)(p + 7 * 4 + 32 * 4); + s = (WCHAR *)(p + 56); + break; + case IMEID_CHT_VER51: + case IMEID_CHT_VER52: + case IMEID_CHS_VER53: + p = *(LPBYTE *)((LPBYTE)videodata->ImmLockIMCC(lpimc->hPrivate) + 4); + if (!p) { + break; + } + + p = *(LPBYTE *)(p + 1 * 4 + 5 * 4); + if (!p) { + break; + } + + len = *(DWORD *)(p + 1 * 4 + (16 * 2 + 2 * 4) + 5 * 4 + 16 * 2); + s = (WCHAR *)(p + 1 * 4 + (16 * 2 + 2 * 4) + 5 * 4); + break; + case IMEID_CHS_VER41: + { + int offset = (IME_GetId(videodata, 1) >= 0x00000002) ? 8 : 7; + p = *(LPBYTE *)((LPBYTE)videodata->ImmLockIMCC(lpimc->hPrivate) + offset * 4); + if (!p) { + break; + } + + len = *(DWORD *)(p + 7 * 4 + 16 * 2 * 4); + s = (WCHAR *)(p + 6 * 4 + 16 * 2 * 1); + } break; + case IMEID_CHS_VER42: + p = *(LPBYTE *)((LPBYTE)videodata->ImmLockIMCC(lpimc->hPrivate) + 1 * 4 + 1 * 4 + 6 * 4); + if (!p) { + break; + } + + len = *(DWORD *)(p + 1 * 4 + (16 * 2 + 2 * 4) + 5 * 4 + 16 * 2); + s = (WCHAR *)(p + 1 * 4 + (16 * 2 + 2 * 4) + 5 * 4); + break; + } + if (s) { + size_t size = SDL_min((size_t)(len + 1), SDL_arraysize(videodata->ime_readingstring)); + SDL_wcslcpy(videodata->ime_readingstring, s, size); + } + + videodata->ImmUnlockIMCC(lpimc->hPrivate); + videodata->ImmUnlockIMC(himc); + } + ImmReleaseContext(hwnd, himc); + IME_SendEditingEvent(videodata); +} + +static void IME_InputLangChanged(SDL_VideoData *videodata) +{ + UINT lang = PRIMLANG(); + IME_UpdateInputLocale(videodata); + + IME_SetupAPI(videodata); + if (lang != PRIMLANG()) { + IME_ClearComposition(videodata); + } +} + +static DWORD IME_GetId(SDL_VideoData *videodata, UINT uIndex) +{ + static HKL hklprev = 0; + static DWORD dwRet[2] = { 0 }; + DWORD dwVerSize = 0; + DWORD dwVerHandle = 0; + LPVOID lpVerBuffer = 0; + LPVOID lpVerData = 0; + UINT cbVerData = 0; + char szTemp[256]; + HKL hkl = 0; + DWORD dwLang = 0; + SDL_assert(uIndex < sizeof(dwRet) / sizeof(dwRet[0])); + + hkl = videodata->ime_hkl; + if (hklprev == hkl) { + return dwRet[uIndex]; + } + hklprev = hkl; + + SDL_assert(uIndex == 0); + dwLang = ((DWORD_PTR)hkl & 0xffff); + // FIXME: What does this do? + if (videodata->ime_internal_candidates && dwLang == LANG_CHT) { + dwRet[0] = IMEID_CHT_VER_VISTA; + dwRet[1] = 0; + return dwRet[0]; + } + if (hkl != CHT_HKL_NEW_PHONETIC && hkl != CHT_HKL_NEW_CHANG_JIE && hkl != CHT_HKL_NEW_QUICK && hkl != CHT_HKL_HK_CANTONESE && hkl != CHS_HKL) { + dwRet[0] = dwRet[1] = 0; + return dwRet[0]; + } + if (!ImmGetIMEFileNameA(hkl, szTemp, sizeof(szTemp) - 1)) { + dwRet[0] = dwRet[1] = 0; + return dwRet[0]; + } + if (!videodata->GetReadingString) { +#define LCID_INVARIANT MAKELCID(MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), SORT_DEFAULT) + if (CompareStringA(LCID_INVARIANT, NORM_IGNORECASE, szTemp, -1, CHT_IMEFILENAME1, -1) != 2 && CompareStringA(LCID_INVARIANT, NORM_IGNORECASE, szTemp, -1, CHT_IMEFILENAME2, -1) != 2 && CompareStringA(LCID_INVARIANT, NORM_IGNORECASE, szTemp, -1, CHT_IMEFILENAME3, -1) != 2 && CompareStringA(LCID_INVARIANT, NORM_IGNORECASE, szTemp, -1, CHS_IMEFILENAME1, -1) != 2 && CompareStringA(LCID_INVARIANT, NORM_IGNORECASE, szTemp, -1, CHS_IMEFILENAME2, -1) != 2) { + dwRet[0] = dwRet[1] = 0; + return dwRet[0]; + } +#undef LCID_INVARIANT + dwVerSize = GetFileVersionInfoSizeA(szTemp, &dwVerHandle); + if (dwVerSize) { + lpVerBuffer = SDL_malloc(dwVerSize); + if (lpVerBuffer) { + if (GetFileVersionInfoA(szTemp, dwVerHandle, dwVerSize, lpVerBuffer)) { + if (VerQueryValueA(lpVerBuffer, "\\", &lpVerData, &cbVerData)) { +#define pVerFixedInfo ((VS_FIXEDFILEINFO FAR *)lpVerData) + DWORD dwVer = pVerFixedInfo->dwFileVersionMS; + dwVer = (dwVer & 0x00ff0000) << 8 | (dwVer & 0x000000ff) << 16; + if ((videodata->GetReadingString) || + ((dwLang == LANG_CHT) && (dwVer == MAKEIMEVERSION(4, 2) || + dwVer == MAKEIMEVERSION(4, 3) || + dwVer == MAKEIMEVERSION(4, 4) || + dwVer == MAKEIMEVERSION(5, 0) || + dwVer == MAKEIMEVERSION(5, 1) || + dwVer == MAKEIMEVERSION(5, 2) || + dwVer == MAKEIMEVERSION(6, 0))) || + ((dwLang == LANG_CHS) && (dwVer == MAKEIMEVERSION(4, 1) || + dwVer == MAKEIMEVERSION(4, 2) || + dwVer == MAKEIMEVERSION(5, 3)))) { + dwRet[0] = dwVer | dwLang; + dwRet[1] = pVerFixedInfo->dwFileVersionLS; + SDL_free(lpVerBuffer); + return dwRet[0]; + } +#undef pVerFixedInfo + } + } + } + SDL_free(lpVerBuffer); + } + } + dwRet[0] = dwRet[1] = 0; + return dwRet[0]; +} + +static void IME_SetupAPI(SDL_VideoData *videodata) +{ + char ime_file[MAX_PATH + 1]; + SDL_SharedObject *hime = 0; + HKL hkl = 0; + videodata->GetReadingString = NULL; + videodata->ShowReadingWindow = NULL; + + hkl = videodata->ime_hkl; + if (!ImmGetIMEFileNameA(hkl, ime_file, sizeof(ime_file) - 1)) { + return; + } + + hime = SDL_LoadObject(ime_file); + if (!hime) { + return; + } + + /* *INDENT-OFF* */ // clang-format off + videodata->GetReadingString = (UINT (WINAPI *)(HIMC, UINT, LPWSTR, PINT, BOOL*, PUINT)) + SDL_LoadFunction(hime, "GetReadingString"); + videodata->ShowReadingWindow = (BOOL (WINAPI *)(HIMC, BOOL)) + SDL_LoadFunction(hime, "ShowReadingWindow"); + /* *INDENT-ON* */ // clang-format on + + if (videodata->ShowReadingWindow) { + HIMC himc = ImmGetContext(videodata->ime_hwnd_current); + if (himc) { + videodata->ShowReadingWindow(himc, FALSE); + ImmReleaseContext(videodata->ime_hwnd_current, himc); + } + } +} + +static void IME_SetWindow(SDL_VideoData *videodata, SDL_Window *window) +{ + HWND hwnd = window->internal->hwnd; + + if (hwnd != videodata->ime_hwnd_current) { + videodata->ime_hwnd_current = hwnd; + SDL_zero(videodata->ime_composition_area); + SDL_zero(videodata->ime_candidate_area); + } + + IME_SetTextInputArea(videodata, hwnd, &window->text_input_rect, window->text_input_cursor); +} + +#endif + +static void IME_SetTextInputArea(SDL_VideoData *videodata, HWND hwnd, const SDL_Rect *rect, int cursor) +{ + HIMC himc; + + himc = ImmGetContext(hwnd); + if (himc) { + COMPOSITIONFORM cof; + CANDIDATEFORM caf; + int font_height = rect->h; + + LOGFONTW font; + if (ImmGetCompositionFontW(himc, &font)) { + font_height = font.lfHeight; + } + + SDL_zero(cof); + cof.dwStyle = CFS_RECT; + cof.ptCurrentPos.x = rect->x + cursor; + cof.ptCurrentPos.y = rect->y + (rect->h - font_height) / 2; + cof.rcArea.left = rect->x; + cof.rcArea.right = (LONG)rect->x + rect->w; + cof.rcArea.top = rect->y; + cof.rcArea.bottom = (LONG)rect->y + rect->h; + if (SDL_memcmp(&cof, &videodata->ime_composition_area, sizeof(cof)) != 0) { + SDL_copyp(&videodata->ime_composition_area, &cof); + ImmSetCompositionWindow(himc, &cof); + } + + SDL_zero(caf); + caf.dwIndex = 0; + caf.dwStyle = CFS_EXCLUDE; + caf.ptCurrentPos.x = rect->x + cursor; + caf.ptCurrentPos.y = rect->y; + caf.rcArea.left = rect->x; + caf.rcArea.right = (LONG)rect->x + rect->w; + caf.rcArea.top = rect->y; + caf.rcArea.bottom = (LONG)rect->y + rect->h; + if (SDL_memcmp(&caf, &videodata->ime_candidate_area, sizeof(caf)) != 0) { + SDL_copyp(&videodata->ime_candidate_area, &caf); + ImmSetCandidateWindow(himc, &caf); + } + + ImmReleaseContext(hwnd, himc); + } +} + +#ifndef SDL_DISABLE_WINDOWS_IME + +static void IME_UpdateInputLocale(SDL_VideoData *videodata) +{ + HKL hklnext = GetKeyboardLayout(0); + + if (hklnext == videodata->ime_hkl) { + return; + } + + videodata->ime_hkl = hklnext; + videodata->ime_horizontal_candidates = (PRIMLANG() == LANG_KOREAN || LANG() == LANG_CHS); + videodata->ime_candlistindexbase = (videodata->ime_hkl == CHT_HKL_DAYI) ? 0 : 1; +} + +static void IME_ClearComposition(SDL_VideoData *videodata) +{ + HIMC himc = 0; + if (!videodata->ime_initialized) { + return; + } + + himc = ImmGetContext(videodata->ime_hwnd_current); + if (!himc) { + return; + } + + ImmNotifyIME(himc, NI_COMPOSITIONSTR, CPS_CANCEL, 0); + ImmSetCompositionString(himc, SCS_SETSTR, TEXT(""), sizeof(TCHAR), TEXT(""), sizeof(TCHAR)); + + ImmNotifyIME(himc, NI_CLOSECANDIDATE, 0, 0); + ImmReleaseContext(videodata->ime_hwnd_current, himc); + IME_SendClearComposition(videodata); +} + +static void IME_GetCompositionString(SDL_VideoData *videodata, HIMC himc, DWORD string) +{ + LONG length; + DWORD dwLang = ((DWORD_PTR)videodata->ime_hkl & 0xffff); + + videodata->ime_cursor = LOWORD(ImmGetCompositionStringW(himc, GCS_CURSORPOS, 0, 0)); + videodata->ime_selected_start = 0; + videodata->ime_selected_length = 0; + SDL_DebugIMELog("Cursor = %d", videodata->ime_cursor); + + length = ImmGetCompositionStringW(himc, string, NULL, 0); + if (length > 0 && videodata->ime_composition_length < length) { + if (videodata->ime_composition) { + SDL_free(videodata->ime_composition); + } + + videodata->ime_composition = (WCHAR *)SDL_malloc(length + sizeof(WCHAR)); + videodata->ime_composition_length = length; + } + + length = ImmGetCompositionStringW(himc, string, videodata->ime_composition, videodata->ime_composition_length); + if (length < 0) { + length = 0; + } + length /= sizeof(WCHAR); + + if ((dwLang == LANG_CHT || dwLang == LANG_CHS) && + videodata->ime_cursor > 0 && + videodata->ime_cursor < (int)(videodata->ime_composition_length / sizeof(WCHAR)) && + (videodata->ime_composition[0] == 0x3000 || videodata->ime_composition[0] == 0x0020)) { + // Traditional Chinese IMEs add a placeholder U+3000 + // Simplified Chinese IMEs seem to add a placeholder U+0020 sometimes + for (int i = videodata->ime_cursor + 1; i < length; ++i) { + videodata->ime_composition[i - 1] = videodata->ime_composition[i]; + } + --length; + } + + videodata->ime_composition[length] = 0; + + length = ImmGetCompositionStringW(himc, GCS_COMPATTR, NULL, 0); + if (length > 0) { + Uint8 *attributes = (Uint8 *)SDL_malloc(length); + if (attributes) { + int start = 0; + int end = 0; + + length = ImmGetCompositionString(himc, GCS_COMPATTR, attributes, length); + if (length < 0) { + length = 0; + } + + for (LONG i = 0; i < length; ++i) { + SDL_DebugIMELog("attrib[%d] = %d", i, attributes[i]); + } + + for (start = 0; start < length; ++start) { + if (attributes[start] == ATTR_TARGET_CONVERTED || attributes[start] == ATTR_TARGET_NOTCONVERTED) { + break; + } + } + + for (end = start; end < length; ++end) { + if (attributes[end] != ATTR_TARGET_CONVERTED && attributes[end] != ATTR_TARGET_NOTCONVERTED) { + break; + } + } + + if (end > start) { + videodata->ime_selected_start = start; + videodata->ime_selected_length = end - start; + } + + SDL_free(attributes); + } + } +} + +static void IME_SendInputEvent(SDL_VideoData *videodata) +{ + char *s = 0; + s = WIN_StringToUTF8W(videodata->ime_composition); + SDL_SendKeyboardText(s); + SDL_free(s); + + videodata->ime_composition[0] = 0; + videodata->ime_readingstring[0] = 0; + videodata->ime_cursor = 0; +} + +static void IME_SendEditingEvent(SDL_VideoData *videodata) +{ + char *s = NULL; + WCHAR *buffer = NULL; + size_t size = videodata->ime_composition_length; + if (videodata->ime_readingstring[0]) { + size_t len = SDL_min(SDL_wcslen(videodata->ime_composition), (size_t)videodata->ime_cursor); + + size += sizeof(videodata->ime_readingstring); + buffer = (WCHAR *)SDL_malloc(size + sizeof(WCHAR)); + if (!buffer) { + return; + } + buffer[0] = 0; + + SDL_wcslcpy(buffer, videodata->ime_composition, len + 1); + SDL_wcslcat(buffer, videodata->ime_readingstring, size); + SDL_wcslcat(buffer, &videodata->ime_composition[len], size); + } else { + buffer = (WCHAR *)SDL_malloc(size + sizeof(WCHAR)); + if (!buffer) { + return; + } + buffer[0] = 0; + SDL_wcslcpy(buffer, videodata->ime_composition, size); + } + + s = WIN_StringToUTF8W(buffer); + if (s) { + if (videodata->ime_readingstring[0]) { + SDL_SendEditingText(s, videodata->ime_cursor, (int)SDL_wcslen(videodata->ime_readingstring)); + } else if (videodata->ime_cursor == videodata->ime_selected_start) { + SDL_SendEditingText(s, videodata->ime_selected_start, videodata->ime_selected_length); + } else { + SDL_SendEditingText(s, videodata->ime_cursor, 0); + } + if (*s) { + videodata->ime_needs_clear_composition = true; + } + SDL_free(s); + } + SDL_free(buffer); +} + +static void IME_SendClearComposition(SDL_VideoData *videodata) +{ + if (videodata->ime_needs_clear_composition) { + SDL_SendEditingText("", 0, 0); + videodata->ime_needs_clear_composition = false; + } +} + +static bool IME_OpenCandidateList(SDL_VideoData *videodata) +{ + videodata->ime_candidates_open = true; + videodata->ime_candcount = 0; + return true; +} + +static void IME_AddCandidate(SDL_VideoData *videodata, UINT i, LPCWSTR candidate) +{ + if (videodata->ime_candidates[i]) { + SDL_free(videodata->ime_candidates[i]); + videodata->ime_candidates[i] = NULL; + } + + SDL_COMPILE_TIME_ASSERT(IME_CANDIDATE_INDEXING_REQUIRES, MAX_CANDLIST == 10); + char *candidate_utf8 = WIN_StringToUTF8W(candidate); + SDL_asprintf(&videodata->ime_candidates[i], "%d %s", ((i + videodata->ime_candlistindexbase) % 10), candidate_utf8); + SDL_free(candidate_utf8); + + videodata->ime_candcount = (i + 1); +} + +static void IME_SendCandidateList(SDL_VideoData *videodata) +{ + SDL_SendEditingTextCandidates(videodata->ime_candidates, videodata->ime_candcount, videodata->ime_candsel, videodata->ime_horizontal_candidates); +} + +static void IME_CloseCandidateList(SDL_VideoData *videodata) +{ + videodata->ime_candidates_open = false; + + if (videodata->ime_candcount > 0) { + for (int i = 0; i < videodata->ime_candcount; ++i) { + SDL_free(videodata->ime_candidates[i]); + videodata->ime_candidates[i] = NULL; + } + videodata->ime_candcount = 0; + + SDL_SendEditingTextCandidates(NULL, 0, -1, false); + } +} + +static void IME_GetCandidateList(SDL_VideoData *videodata, HWND hwnd) +{ + HIMC himc; + DWORD size; + LPCANDIDATELIST cand_list; + bool has_candidates = false; + + himc = ImmGetContext(hwnd); + if (himc) { + size = ImmGetCandidateListW(himc, 0, NULL, 0); + if (size != 0) { + cand_list = (LPCANDIDATELIST)SDL_malloc(size); + if (cand_list != NULL) { + size = ImmGetCandidateListW(himc, 0, cand_list, size); + if (size != 0) { + if (IME_OpenCandidateList(videodata)) { + UINT i, j; + UINT page_start = 0; + UINT page_size = 0; + + videodata->ime_candsel = cand_list->dwSelection; + + if (LANG() == LANG_CHS && IME_GetId(videodata, 0)) { + const UINT maxcandchar = 18; + size_t cchars = 0; + + for (i = 0; i < cand_list->dwCount; ++i) { + size_t len = SDL_wcslen((LPWSTR)((DWORD_PTR)cand_list + cand_list->dwOffset[i])) + 1; + if (len + cchars > maxcandchar) { + if (i > cand_list->dwSelection) { + break; + } + + page_start = i; + cchars = len; + } else { + cchars += len; + } + } + page_size = i - page_start; + } else { + page_size = SDL_min(cand_list->dwPageSize == 0 ? MAX_CANDLIST : cand_list->dwPageSize, MAX_CANDLIST); + page_start = (cand_list->dwSelection / page_size) * page_size; + } + for (i = page_start, j = 0; (DWORD)i < cand_list->dwCount && j < page_size; i++, j++) { + LPCWSTR candidate = (LPCWSTR)((DWORD_PTR)cand_list + cand_list->dwOffset[i]); + IME_AddCandidate(videodata, j, candidate); + } + + has_candidates = true; + IME_SendCandidateList(videodata); + } + } + SDL_free(cand_list); + } + } + ImmReleaseContext(hwnd, himc); + } + + if (!has_candidates) { + IME_CloseCandidateList(videodata); + } +} + +bool WIN_HandleIMEMessage(HWND hwnd, UINT msg, WPARAM wParam, LPARAM *lParam, SDL_VideoData *videodata) +{ + bool trap = false; + HIMC himc = 0; + + if (msg == WM_IME_SETCONTEXT) { + SDL_DebugIMELog("WM_IME_SETCONTEXT"); + + LPARAM element_mask; + if (videodata->ime_internal_composition && videodata->ime_internal_candidates) { + element_mask = 0; + } else { + element_mask = ISC_SHOWUIALL; + if (videodata->ime_internal_composition) { + element_mask &= ~ISC_SHOWUICOMPOSITIONWINDOW; + } + if (videodata->ime_internal_candidates) { + element_mask &= ~ISC_SHOWUIALLCANDIDATEWINDOW; + } + } + *lParam &= element_mask; + + return false; + } + + if (!videodata->ime_initialized || !videodata->ime_available || !videodata->ime_enabled) { + return false; + } + + switch (msg) { + case WM_KEYDOWN: + if (wParam == VK_PROCESSKEY) { + SDL_DebugIMELog("WM_KEYDOWN VK_PROCESSKEY"); + trap = true; + } else { + SDL_DebugIMELog("WM_KEYDOWN normal"); + } + break; + case WM_INPUTLANGCHANGE: + SDL_DebugIMELog("WM_INPUTLANGCHANGE"); + IME_InputLangChanged(videodata); + break; + case WM_IME_STARTCOMPOSITION: + SDL_DebugIMELog("WM_IME_STARTCOMPOSITION"); + if (videodata->ime_internal_composition) { + trap = true; + } + break; + case WM_IME_COMPOSITION: + SDL_DebugIMELog("WM_IME_COMPOSITION %x", lParam); + if (videodata->ime_internal_composition) { + trap = true; + himc = ImmGetContext(hwnd); + if (*lParam & GCS_RESULTSTR) { + SDL_DebugIMELog("GCS_RESULTSTR"); + IME_GetCompositionString(videodata, himc, GCS_RESULTSTR); + IME_SendClearComposition(videodata); + IME_SendInputEvent(videodata); + } + if (*lParam & GCS_COMPSTR) { + SDL_DebugIMELog("GCS_COMPSTR"); + videodata->ime_readingstring[0] = 0; + IME_GetCompositionString(videodata, himc, GCS_COMPSTR); + IME_SendEditingEvent(videodata); + } + ImmReleaseContext(hwnd, himc); + } + break; + case WM_IME_ENDCOMPOSITION: + SDL_DebugIMELog("WM_IME_ENDCOMPOSITION"); + if (videodata->ime_internal_composition) { + trap = true; + videodata->ime_composition[0] = 0; + videodata->ime_readingstring[0] = 0; + videodata->ime_cursor = 0; + videodata->ime_selected_start = 0; + videodata->ime_selected_length = 0; + IME_SendClearComposition(videodata); + } + break; + case WM_IME_NOTIFY: + SDL_DebugIMELog("WM_IME_NOTIFY %x", wParam); + switch (wParam) { + case IMN_SETCOMPOSITIONWINDOW: + SDL_DebugIMELog("IMN_SETCOMPOSITIONWINDOW"); + break; + case IMN_SETCOMPOSITIONFONT: + SDL_DebugIMELog("IMN_SETCOMPOSITIONFONT"); + break; + case IMN_SETCANDIDATEPOS: + SDL_DebugIMELog("IMN_SETCANDIDATEPOS"); + break; + case IMN_SETCONVERSIONMODE: + case IMN_SETOPENSTATUS: + SDL_DebugIMELog("%s", wParam == IMN_SETCONVERSIONMODE ? "IMN_SETCONVERSIONMODE" : "IMN_SETOPENSTATUS"); + IME_UpdateInputLocale(videodata); + break; + case IMN_OPENCANDIDATE: + case IMN_CHANGECANDIDATE: + SDL_DebugIMELog("%s", wParam == IMN_OPENCANDIDATE ? "IMN_OPENCANDIDATE" : "IMN_CHANGECANDIDATE"); + if (videodata->ime_internal_candidates) { + trap = true; + videodata->ime_update_candidates = true; + } + break; + case IMN_CLOSECANDIDATE: + SDL_DebugIMELog("IMN_CLOSECANDIDATE"); + if (videodata->ime_internal_candidates) { + trap = true; + videodata->ime_update_candidates = false; + IME_CloseCandidateList(videodata); + } + break; + case IMN_PRIVATE: + { + DWORD dwId = IME_GetId(videodata, 0); + SDL_DebugIMELog("IMN_PRIVATE %u", dwId); + IME_GetReadingString(videodata, hwnd); + switch (dwId) { + case IMEID_CHT_VER42: + case IMEID_CHT_VER43: + case IMEID_CHT_VER44: + case IMEID_CHS_VER41: + case IMEID_CHS_VER42: + if (*lParam == 1 || *lParam == 2) { + trap = true; + } + + break; + case IMEID_CHT_VER50: + case IMEID_CHT_VER51: + case IMEID_CHT_VER52: + case IMEID_CHT_VER60: + case IMEID_CHS_VER53: + if (*lParam == 16 || *lParam == 17 || *lParam == 26 || *lParam == 27 || *lParam == 28) { + trap = true; + } + break; + } + } break; + default: + trap = true; + break; + } + break; + } + return trap; +} + +void WIN_UpdateIMECandidates(SDL_VideoDevice *_this) +{ + SDL_VideoData *videodata = _this->internal; + + if (videodata->ime_update_candidates) { + IME_GetCandidateList(videodata, videodata->ime_hwnd_current); + videodata->ime_update_candidates = false; + } +} + +#endif // SDL_DISABLE_WINDOWS_IME + +#endif // SDL_VIDEO_DRIVER_WINDOWS diff --git a/contrib/SDL-3.2.8/src/video/windows/SDL_windowskeyboard.h b/contrib/SDL-3.2.8/src/video/windows/SDL_windowskeyboard.h new file mode 100644 index 0000000..592da9d --- /dev/null +++ b/contrib/SDL-3.2.8/src/video/windows/SDL_windowskeyboard.h @@ -0,0 +1,40 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include "SDL_internal.h" + +#ifndef SDL_windowskeyboard_h_ +#define SDL_windowskeyboard_h_ + +extern void WIN_InitKeyboard(SDL_VideoDevice *_this); +extern void WIN_UpdateKeymap(bool send_event); +extern void WIN_QuitKeyboard(SDL_VideoDevice *_this); + +extern void WIN_ResetDeadKeys(void); + +extern bool WIN_StartTextInput(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID props); +extern bool WIN_StopTextInput(SDL_VideoDevice *_this, SDL_Window *window); +extern bool WIN_UpdateTextInputArea(SDL_VideoDevice *_this, SDL_Window *window); +extern bool WIN_ClearComposition(SDL_VideoDevice *_this, SDL_Window *window); + +extern bool WIN_HandleIMEMessage(HWND hwnd, UINT msg, WPARAM wParam, LPARAM *lParam, struct SDL_VideoData *videodata); +extern void WIN_UpdateIMECandidates(SDL_VideoDevice *_this); + +#endif // SDL_windowskeyboard_h_ diff --git a/contrib/SDL-3.2.8/src/video/windows/SDL_windowsmessagebox.c b/contrib/SDL-3.2.8/src/video/windows/SDL_windowsmessagebox.c new file mode 100644 index 0000000..c174e3d --- /dev/null +++ b/contrib/SDL-3.2.8/src/video/windows/SDL_windowsmessagebox.c @@ -0,0 +1,1086 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include "SDL_internal.h" + +#if defined(SDL_VIDEO_DRIVER_WINDOWS) && !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) + +#ifdef HAVE_LIMITS_H +#include +#endif +#ifndef SIZE_MAX +#define SIZE_MAX ((size_t)-1) +#endif + +#include "../../core/windows/SDL_windows.h" + +#include "SDL_windowsvideo.h" + +#ifndef SS_EDITCONTROL +#define SS_EDITCONTROL 0x2000 +#endif + +#ifndef IDOK +#define IDOK 1 +#endif + +#ifndef IDCANCEL +#define IDCANCEL 2 +#endif + +// Custom dialog return codes +#define IDCLOSED 20 +#define IDINVALPTRINIT 50 +#define IDINVALPTRCOMMAND 51 +#define IDINVALPTRSETFOCUS 52 +#define IDINVALPTRDLGITEM 53 +// First button ID +#define IDBUTTONINDEX0 100 + +#define DLGITEMTYPEBUTTON 0x0080 +#define DLGITEMTYPESTATIC 0x0082 + +/* Windows only sends the lower 16 bits of the control ID when a button + * gets clicked. There are also some predefined and custom IDs that lower + * the available number further. 2^16 - 101 buttons should be enough for + * everyone, no need to make the code more complex. + */ +#define MAX_BUTTONS (0xffff - 100) + +// Display a Windows message box + +typedef HRESULT(CALLBACK *PFTASKDIALOGCALLBACK)(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, LONG_PTR lpRefData); + +enum _TASKDIALOG_FLAGS +{ + TDF_ENABLE_HYPERLINKS = 0x0001, + TDF_USE_HICON_MAIN = 0x0002, + TDF_USE_HICON_FOOTER = 0x0004, + TDF_ALLOW_DIALOG_CANCELLATION = 0x0008, + TDF_USE_COMMAND_LINKS = 0x0010, + TDF_USE_COMMAND_LINKS_NO_ICON = 0x0020, + TDF_EXPAND_FOOTER_AREA = 0x0040, + TDF_EXPANDED_BY_DEFAULT = 0x0080, + TDF_VERIFICATION_FLAG_CHECKED = 0x0100, + TDF_SHOW_PROGRESS_BAR = 0x0200, + TDF_SHOW_MARQUEE_PROGRESS_BAR = 0x0400, + TDF_CALLBACK_TIMER = 0x0800, + TDF_POSITION_RELATIVE_TO_WINDOW = 0x1000, + TDF_RTL_LAYOUT = 0x2000, + TDF_NO_DEFAULT_RADIO_BUTTON = 0x4000, + TDF_CAN_BE_MINIMIZED = 0x8000, + // #if (NTDDI_VERSION >= NTDDI_WIN8) + TDF_NO_SET_FOREGROUND = 0x00010000, // Don't call SetForegroundWindow() when activating the dialog + // #endif // (NTDDI_VERSION >= NTDDI_WIN8) + TDF_SIZE_TO_CONTENT = 0x01000000 // used by ShellMessageBox to emulate MessageBox sizing behavior +}; +typedef int TASKDIALOG_FLAGS; // Note: _TASKDIALOG_FLAGS is an int + +typedef enum _TASKDIALOG_MESSAGES +{ + TDM_NAVIGATE_PAGE = WM_USER + 101, + TDM_CLICK_BUTTON = WM_USER + 102, // wParam = Button ID + TDM_SET_MARQUEE_PROGRESS_BAR = WM_USER + 103, // wParam = 0 (nonMarque) wParam != 0 (Marquee) + TDM_SET_PROGRESS_BAR_STATE = WM_USER + 104, // wParam = new progress state + TDM_SET_PROGRESS_BAR_RANGE = WM_USER + 105, // lParam = MAKELPARAM(nMinRange, nMaxRange) + TDM_SET_PROGRESS_BAR_POS = WM_USER + 106, // wParam = new position + TDM_SET_PROGRESS_BAR_MARQUEE = WM_USER + 107, // wParam = 0 (stop marquee), wParam != 0 (start marquee), lparam = speed (milliseconds between repaints) + TDM_SET_ELEMENT_TEXT = WM_USER + 108, // wParam = element (TASKDIALOG_ELEMENTS), lParam = new element text (LPCWSTR) + TDM_CLICK_RADIO_BUTTON = WM_USER + 110, // wParam = Radio Button ID + TDM_ENABLE_BUTTON = WM_USER + 111, // lParam = 0 (disable), lParam != 0 (enable), wParam = Button ID + TDM_ENABLE_RADIO_BUTTON = WM_USER + 112, // lParam = 0 (disable), lParam != 0 (enable), wParam = Radio Button ID + TDM_CLICK_VERIFICATION = WM_USER + 113, // wParam = 0 (unchecked), 1 (checked), lParam = 1 (set key focus) + TDM_UPDATE_ELEMENT_TEXT = WM_USER + 114, // wParam = element (TASKDIALOG_ELEMENTS), lParam = new element text (LPCWSTR) + TDM_SET_BUTTON_ELEVATION_REQUIRED_STATE = WM_USER + 115, // wParam = Button ID, lParam = 0 (elevation not required), lParam != 0 (elevation required) + TDM_UPDATE_ICON = WM_USER + 116 // wParam = icon element (TASKDIALOG_ICON_ELEMENTS), lParam = new icon (hIcon if TDF_USE_HICON_* was set, PCWSTR otherwise) +} TASKDIALOG_MESSAGES; + +typedef enum _TASKDIALOG_NOTIFICATIONS +{ + TDN_CREATED = 0, + TDN_NAVIGATED = 1, + TDN_BUTTON_CLICKED = 2, // wParam = Button ID + TDN_HYPERLINK_CLICKED = 3, // lParam = (LPCWSTR)pszHREF + TDN_TIMER = 4, // wParam = Milliseconds since dialog created or timer reset + TDN_DESTROYED = 5, + TDN_RADIO_BUTTON_CLICKED = 6, // wParam = Radio Button ID + TDN_DIALOG_CONSTRUCTED = 7, + TDN_VERIFICATION_CLICKED = 8, // wParam = 1 if checkbox checked, 0 if not, lParam is unused and always 0 + TDN_HELP = 9, + TDN_EXPANDO_BUTTON_CLICKED = 10 // wParam = 0 (dialog is now collapsed), wParam != 0 (dialog is now expanded) +} TASKDIALOG_NOTIFICATIONS; + +typedef enum _TASKDIALOG_ELEMENTS +{ + TDE_CONTENT, + TDE_EXPANDED_INFORMATION, + TDE_FOOTER, + TDE_MAIN_INSTRUCTION +} TASKDIALOG_ELEMENTS; + +typedef enum _TASKDIALOG_ICON_ELEMENTS +{ + TDIE_ICON_MAIN, + TDIE_ICON_FOOTER +} TASKDIALOG_ICON_ELEMENTS; + +#define TD_WARNING_ICON MAKEINTRESOURCEW(-1) +#define TD_ERROR_ICON MAKEINTRESOURCEW(-2) +#define TD_INFORMATION_ICON MAKEINTRESOURCEW(-3) +#define TD_SHIELD_ICON MAKEINTRESOURCEW(-4) + +enum _TASKDIALOG_COMMON_BUTTON_FLAGS +{ + TDCBF_OK_BUTTON = 0x0001, // selected control return value IDOK + TDCBF_YES_BUTTON = 0x0002, // selected control return value IDYES + TDCBF_NO_BUTTON = 0x0004, // selected control return value IDNO + TDCBF_CANCEL_BUTTON = 0x0008, // selected control return value IDCANCEL + TDCBF_RETRY_BUTTON = 0x0010, // selected control return value IDRETRY + TDCBF_CLOSE_BUTTON = 0x0020 // selected control return value IDCLOSE +}; +typedef int TASKDIALOG_COMMON_BUTTON_FLAGS; // Note: _TASKDIALOG_COMMON_BUTTON_FLAGS is an int + +#pragma pack(push, 1) + +typedef struct _TASKDIALOG_BUTTON +{ + int nButtonID; + PCWSTR pszButtonText; +} TASKDIALOG_BUTTON; + +typedef struct _TASKDIALOGCONFIG +{ + UINT cbSize; + HWND hwndParent; // incorrectly named, this is the owner window, not a parent. + HINSTANCE hInstance; // used for MAKEINTRESOURCE() strings + TASKDIALOG_FLAGS dwFlags; // TASKDIALOG_FLAGS (TDF_XXX) flags + TASKDIALOG_COMMON_BUTTON_FLAGS dwCommonButtons; // TASKDIALOG_COMMON_BUTTON (TDCBF_XXX) flags + PCWSTR pszWindowTitle; // string or MAKEINTRESOURCE() + union + { + HICON hMainIcon; + PCWSTR pszMainIcon; + } /*DUMMYUNIONNAME*/; + PCWSTR pszMainInstruction; + PCWSTR pszContent; + UINT cButtons; + const TASKDIALOG_BUTTON *pButtons; + int nDefaultButton; + UINT cRadioButtons; + const TASKDIALOG_BUTTON *pRadioButtons; + int nDefaultRadioButton; + PCWSTR pszVerificationText; + PCWSTR pszExpandedInformation; + PCWSTR pszExpandedControlText; + PCWSTR pszCollapsedControlText; + union + { + HICON hFooterIcon; + PCWSTR pszFooterIcon; + } /*DUMMYUNIONNAME2*/; + PCWSTR pszFooter; + PFTASKDIALOGCALLBACK pfCallback; + LONG_PTR lpCallbackData; + UINT cxWidth; // width of the Task Dialog's client area in DLU's. If 0, Task Dialog will calculate the ideal width. +} TASKDIALOGCONFIG; + +typedef struct +{ + WORD dlgVer; + WORD signature; + DWORD helpID; + DWORD exStyle; + DWORD style; + WORD cDlgItems; + short x; + short y; + short cx; + short cy; +} DLGTEMPLATEEX; + +typedef struct +{ + DWORD helpID; + DWORD exStyle; + DWORD style; + short x; + short y; + short cx; + short cy; + DWORD id; +} DLGITEMTEMPLATEEX; + +#pragma pack(pop) + +typedef struct +{ + DLGTEMPLATEEX *lpDialog; + void *data; + size_t size; + size_t used; + WORD numbuttons; +} WIN_DialogData; + +static bool GetButtonIndex(const SDL_MessageBoxData *messageboxdata, SDL_MessageBoxButtonFlags flags, size_t *i) +{ + for (*i = 0; *i < (size_t)messageboxdata->numbuttons; ++*i) { + if (messageboxdata->buttons[*i].flags & flags) { + return true; + } + } + return false; +} + +static INT_PTR CALLBACK MessageBoxDialogProc(HWND hDlg, UINT iMessage, WPARAM wParam, LPARAM lParam) +{ + const SDL_MessageBoxData *messageboxdata; + size_t buttonindex; + + switch (iMessage) { + case WM_INITDIALOG: + if (lParam == 0) { + EndDialog(hDlg, IDINVALPTRINIT); + return TRUE; + } + messageboxdata = (const SDL_MessageBoxData *)lParam; + SetWindowLongPtr(hDlg, GWLP_USERDATA, lParam); + + if (GetButtonIndex(messageboxdata, SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT, &buttonindex)) { + // Focus on the first default return-key button + HWND buttonctl = GetDlgItem(hDlg, (int)(IDBUTTONINDEX0 + buttonindex)); + if (!buttonctl) { + EndDialog(hDlg, IDINVALPTRDLGITEM); + } + PostMessage(hDlg, WM_NEXTDLGCTL, (WPARAM)buttonctl, TRUE); + } else { + // Give the focus to the dialog window instead + SetFocus(hDlg); + } + return FALSE; + case WM_SETFOCUS: + messageboxdata = (const SDL_MessageBoxData *)GetWindowLongPtr(hDlg, GWLP_USERDATA); + if (!messageboxdata) { + EndDialog(hDlg, IDINVALPTRSETFOCUS); + return TRUE; + } + + // Let the default button be focused if there is one. Otherwise, prevent any initial focus. + if (GetButtonIndex(messageboxdata, SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT, &buttonindex)) { + return FALSE; + } + return TRUE; + case WM_COMMAND: + messageboxdata = (const SDL_MessageBoxData *)GetWindowLongPtr(hDlg, GWLP_USERDATA); + if (!messageboxdata) { + EndDialog(hDlg, IDINVALPTRCOMMAND); + return TRUE; + } + + // Return the ID of the button that was pushed + if (wParam == IDOK) { + if (GetButtonIndex(messageboxdata, SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT, &buttonindex)) { + EndDialog(hDlg, IDBUTTONINDEX0 + buttonindex); + } + } else if (wParam == IDCANCEL) { + if (GetButtonIndex(messageboxdata, SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT, &buttonindex)) { + EndDialog(hDlg, IDBUTTONINDEX0 + buttonindex); + } else { + // Closing of window was requested by user or system. It would be rude not to comply. + EndDialog(hDlg, IDCLOSED); + } + } else if (wParam >= IDBUTTONINDEX0 && (int)wParam - IDBUTTONINDEX0 < messageboxdata->numbuttons) { + EndDialog(hDlg, wParam); + } + return TRUE; + + default: + break; + } + return FALSE; +} + +static bool ExpandDialogSpace(WIN_DialogData *dialog, size_t space) +{ + // Growing memory in 64 KiB steps. + const size_t sizestep = 0x10000; + size_t size = dialog->size; + + if (size == 0) { + // Start with 4 KiB or a multiple of 64 KiB to fit the data. + size = 0x1000; + if (SIZE_MAX - sizestep < space) { + size = space; + } else if (space > size) { + size = (space + sizestep) & ~(sizestep - 1); + } + } else if (SIZE_MAX - dialog->used < space) { + SDL_OutOfMemory(); + return false; + } else if (SIZE_MAX - (dialog->used + space) < sizestep) { + // Close to the maximum. + size = dialog->used + space; + } else if (size < dialog->used + space) { + // Round up to the next 64 KiB block. + size = dialog->used + space; + size += sizestep - size % sizestep; + } + + if (size > dialog->size) { + void *data = SDL_realloc(dialog->data, size); + if (!data) { + return false; + } + dialog->data = data; + dialog->size = size; + dialog->lpDialog = (DLGTEMPLATEEX *)dialog->data; + } + return true; +} + +static bool AlignDialogData(WIN_DialogData *dialog, size_t size) +{ + size_t padding = (dialog->used % size); + + if (!ExpandDialogSpace(dialog, padding)) { + return false; + } + + dialog->used += padding; + + return true; +} + +static bool AddDialogData(WIN_DialogData *dialog, const void *data, size_t size) +{ + if (!ExpandDialogSpace(dialog, size)) { + return false; + } + + SDL_memcpy((Uint8 *)dialog->data + dialog->used, data, size); + dialog->used += size; + + return true; +} + +static bool AddDialogString(WIN_DialogData *dialog, const char *string) +{ + WCHAR *wstring; + WCHAR *p; + size_t count; + bool status; + + if (!string) { + string = ""; + } + + wstring = WIN_UTF8ToStringW(string); + if (!wstring) { + return false; + } + + // Find out how many characters we have, including null terminator + count = 0; + for (p = wstring; *p; ++p) { + ++count; + } + ++count; + + status = AddDialogData(dialog, wstring, count * sizeof(WCHAR)); + SDL_free(wstring); + return status; +} + +static int s_BaseUnitsX; +static int s_BaseUnitsY; +static void Vec2ToDLU(short *x, short *y) +{ + SDL_assert(s_BaseUnitsX != 0); // we init in WIN_ShowMessageBox(), which is the only public function... + + *x = (short)MulDiv(*x, 4, s_BaseUnitsX); + *y = (short)MulDiv(*y, 8, s_BaseUnitsY); +} + +static bool AddDialogControl(WIN_DialogData *dialog, WORD type, DWORD style, DWORD exStyle, int x, int y, int w, int h, int id, const char *caption, WORD ordinal) +{ + DLGITEMTEMPLATEEX item; + WORD marker = 0xFFFF; + WORD extraData = 0; + + SDL_zero(item); + item.style = style; + item.exStyle = exStyle; + item.x = (short)x; + item.y = (short)y; + item.cx = (short)w; + item.cy = (short)h; + item.id = id; + + Vec2ToDLU(&item.x, &item.y); + Vec2ToDLU(&item.cx, &item.cy); + + if (!AlignDialogData(dialog, sizeof(DWORD))) { + return false; + } + if (!AddDialogData(dialog, &item, sizeof(item))) { + return false; + } + if (!AddDialogData(dialog, &marker, sizeof(marker))) { + return false; + } + if (!AddDialogData(dialog, &type, sizeof(type))) { + return false; + } + if (type == DLGITEMTYPEBUTTON || (type == DLGITEMTYPESTATIC && caption)) { + if (!AddDialogString(dialog, caption)) { + return false; + } + } else { + if (!AddDialogData(dialog, &marker, sizeof(marker))) { + return false; + } + if (!AddDialogData(dialog, &ordinal, sizeof(ordinal))) { + return false; + } + } + if (!AddDialogData(dialog, &extraData, sizeof(extraData))) { + return false; + } + if (type == DLGITEMTYPEBUTTON) { + dialog->numbuttons++; + } + ++dialog->lpDialog->cDlgItems; + + return true; +} + +static bool AddDialogStaticText(WIN_DialogData *dialog, int x, int y, int w, int h, const char *text) +{ + DWORD style = WS_VISIBLE | WS_CHILD | SS_LEFT | SS_NOPREFIX | SS_EDITCONTROL | WS_GROUP; + return AddDialogControl(dialog, DLGITEMTYPESTATIC, style, 0, x, y, w, h, -1, text, 0); +} + +static bool AddDialogStaticIcon(WIN_DialogData *dialog, int x, int y, int w, int h, Uint16 ordinal) +{ + DWORD style = WS_VISIBLE | WS_CHILD | SS_ICON | WS_GROUP; + return AddDialogControl(dialog, DLGITEMTYPESTATIC, style, 0, x, y, w, h, -2, NULL, ordinal); +} + +static bool AddDialogButton(WIN_DialogData *dialog, int x, int y, int w, int h, const char *text, int id, bool isDefault) +{ + DWORD style = WS_VISIBLE | WS_CHILD | WS_TABSTOP; + if (isDefault) { + style |= BS_DEFPUSHBUTTON; + } else { + style |= BS_PUSHBUTTON; + } + // The first button marks the start of the group. + if (dialog->numbuttons == 0) { + style |= WS_GROUP; + } + return AddDialogControl(dialog, DLGITEMTYPEBUTTON, style, 0, x, y, w, h, id, text, 0); +} + +static void FreeDialogData(WIN_DialogData *dialog) +{ + SDL_free(dialog->data); + SDL_free(dialog); +} + +static WIN_DialogData *CreateDialogData(int w, int h, const char *caption) +{ + WIN_DialogData *dialog; + DLGTEMPLATEEX dialogTemplate; + WORD WordToPass; + + SDL_zero(dialogTemplate); + dialogTemplate.dlgVer = 1; + dialogTemplate.signature = 0xffff; + dialogTemplate.style = (WS_CAPTION | DS_CENTER | DS_SHELLFONT); + dialogTemplate.x = 0; + dialogTemplate.y = 0; + dialogTemplate.cx = (short)w; + dialogTemplate.cy = (short)h; + Vec2ToDLU(&dialogTemplate.cx, &dialogTemplate.cy); + + dialog = (WIN_DialogData *)SDL_calloc(1, sizeof(*dialog)); + if (!dialog) { + return NULL; + } + + if (!AddDialogData(dialog, &dialogTemplate, sizeof(dialogTemplate))) { + FreeDialogData(dialog); + return NULL; + } + + // No menu + WordToPass = 0; + if (!AddDialogData(dialog, &WordToPass, 2)) { + FreeDialogData(dialog); + return NULL; + } + + // No custom class + if (!AddDialogData(dialog, &WordToPass, 2)) { + FreeDialogData(dialog); + return NULL; + } + + // title + if (!AddDialogString(dialog, caption)) { + FreeDialogData(dialog); + return NULL; + } + + // Font stuff + { + /* + * We want to use the system messagebox font. + */ + BYTE ToPass; + + NONCLIENTMETRICSA NCM; + NCM.cbSize = sizeof(NCM); + SystemParametersInfoA(SPI_GETNONCLIENTMETRICS, 0, &NCM, 0); + + // Font size - convert to logical font size for dialog parameter. + { + HDC ScreenDC = GetDC(NULL); + int LogicalPixelsY = GetDeviceCaps(ScreenDC, LOGPIXELSY); + if (!LogicalPixelsY) { + LogicalPixelsY = 72; // This can happen if the application runs out of GDI handles + } + + WordToPass = (WORD)(-72 * NCM.lfMessageFont.lfHeight / LogicalPixelsY); + ReleaseDC(NULL, ScreenDC); + } + + if (!AddDialogData(dialog, &WordToPass, 2)) { + FreeDialogData(dialog); + return NULL; + } + + // Font weight + WordToPass = (WORD)NCM.lfMessageFont.lfWeight; + if (!AddDialogData(dialog, &WordToPass, 2)) { + FreeDialogData(dialog); + return NULL; + } + + // italic? + ToPass = NCM.lfMessageFont.lfItalic; + if (!AddDialogData(dialog, &ToPass, 1)) { + FreeDialogData(dialog); + return NULL; + } + + // charset? + ToPass = NCM.lfMessageFont.lfCharSet; + if (!AddDialogData(dialog, &ToPass, 1)) { + FreeDialogData(dialog); + return NULL; + } + + // font typeface. + if (!AddDialogString(dialog, NCM.lfMessageFont.lfFaceName)) { + FreeDialogData(dialog); + return NULL; + } + } + + return dialog; +} + +/* Escaping ampersands is necessary to disable mnemonics in dialog controls. + * The caller provides a char** for dst and a size_t* for dstlen where the + * address of the work buffer and its size will be stored. Their values must be + * NULL and 0 on the first call. src is the string to be escaped. On error, the + * function returns NULL and, on success, returns a pointer to the escaped + * sequence as a read-only string that is valid until the next call or until the + * work buffer is freed. Once all strings have been processed, it's the caller's + * responsibility to free the work buffer with SDL_free, even on errors. + */ +static const char *EscapeAmpersands(char **dst, size_t *dstlen, const char *src) +{ + char *newdst; + size_t ampcount = 0; + size_t srclen = 0; + + if (!src) { + return NULL; + } + + while (src[srclen]) { + if (src[srclen] == '&') { + ampcount++; + } + srclen++; + } + srclen++; + + if (ampcount == 0) { + // Nothing to do. + return src; + } + if (SIZE_MAX - srclen < ampcount) { + return NULL; + } + if (!*dst || *dstlen < srclen + ampcount) { + // Allocating extra space in case the next strings are a bit longer. + size_t extraspace = SIZE_MAX - (srclen + ampcount); + if (extraspace > 512) { + extraspace = 512; + } + *dstlen = srclen + ampcount + extraspace; + SDL_free(*dst); + *dst = NULL; + newdst = (char *)SDL_malloc(*dstlen); + if (!newdst) { + return NULL; + } + *dst = newdst; + } else { + newdst = *dst; + } + + // The escape character is the ampersand itself. + while (srclen--) { + if (*src == '&') { + *newdst++ = '&'; + } + *newdst++ = *src++; + } + + return *dst; +} + +static float WIN_GetContentScale(void) +{ + int dpi = 0; + +#if 0 // We don't know what monitor the dialog will be shown on + UINT hdpi_uint, vdpi_uint; + if (GetDpiForMonitor(hMonitor, MDT_EFFECTIVE_DPI, &hdpi_uint, &vdpi_uint) == S_OK) { + dpi = (int)hdpi_uint; + } +#endif + if (dpi == 0) { + // Window 8.0 and below: same DPI for all monitors + HDC hdc = GetDC(NULL); + if (hdc) { + dpi = GetDeviceCaps(hdc, LOGPIXELSX); + ReleaseDC(NULL, hdc); + } + } + if (dpi == 0) { + // Safe default + dpi = USER_DEFAULT_SCREEN_DPI; + } + return dpi / (float)USER_DEFAULT_SCREEN_DPI; +} + +// This function is called if a Task Dialog is unsupported. +static bool WIN_ShowOldMessageBox(const SDL_MessageBoxData *messageboxdata, int *buttonID) +{ + WIN_DialogData *dialog; + int i, x, y; + HFONT DialogFont; + SIZE Size; + RECT TextSize; + wchar_t *wmessage; + TEXTMETRIC TM; + HDC FontDC; + INT_PTR rc; + char *ampescape = NULL; + size_t ampescapesize = 0; + Uint16 defbuttoncount = 0; + Uint16 icon = 0; + bool result; + + HWND ParentWindow = NULL; + + const float scale = WIN_GetContentScale(); + const int ButtonWidth = (int)SDL_roundf(88 * scale); + const int ButtonHeight = (int)SDL_roundf(26 * scale); + const int TextMargin = (int)SDL_roundf(16 * scale); + const int ButtonMargin = (int)SDL_roundf(12 * scale); + const int IconWidth = GetSystemMetrics(SM_CXICON); + const int IconHeight = GetSystemMetrics(SM_CYICON); + const int IconMargin = (int)SDL_roundf(20 * scale); + + if (messageboxdata->numbuttons > MAX_BUTTONS) { + return SDL_SetError("Number of buttons exceeds limit of %d", MAX_BUTTONS); + } + + switch (messageboxdata->flags & (SDL_MESSAGEBOX_ERROR | SDL_MESSAGEBOX_WARNING | SDL_MESSAGEBOX_INFORMATION)) { + case SDL_MESSAGEBOX_ERROR: + icon = (Uint16)(size_t)IDI_ERROR; + break; + case SDL_MESSAGEBOX_WARNING: + icon = (Uint16)(size_t)IDI_WARNING; + break; + case SDL_MESSAGEBOX_INFORMATION: + icon = (Uint16)(size_t)IDI_INFORMATION; + break; + } + + /* Jan 25th, 2013 - dant@fleetsa.com + * + * I've tried to make this more reasonable, but I've run in to a lot + * of nonsense. + * + * The original issue is the code was written in pixels and not + * dialog units (DLUs). All DialogBox functions use DLUs, which + * vary based on the selected font (yay). + * + * According to MSDN, the most reliable way to convert is via + * MapDialogUnits, which requires an HWND, which we don't have + * at time of template creation. + * + * We do however have: + * The system font (DLU width 8 for me) + * The font we select for the dialog (DLU width 6 for me) + * + * Based on experimentation, *neither* of these return the value + * actually used. Stepping in to MapDialogUnits(), the conversion + * is fairly clear, and uses 7 for me. + * + * As a result, some of this is hacky to ensure the sizing is + * somewhat correct. + * + * Honestly, a long term solution is to use CreateWindow, not CreateDialog. + * + * In order to get text dimensions we need to have a DC with the desired font. + * I'm assuming a dialog box in SDL is rare enough we can to the create. + */ + FontDC = CreateCompatibleDC(0); + + { + // Create a duplicate of the font used in system message boxes. + LOGFONT lf; + NONCLIENTMETRICS NCM; + NCM.cbSize = sizeof(NCM); + SystemParametersInfo(SPI_GETNONCLIENTMETRICS, 0, &NCM, 0); + lf = NCM.lfMessageFont; + DialogFont = CreateFontIndirect(&lf); + } + + // Select the font in to our DC + SelectObject(FontDC, DialogFont); + + { + // Get the metrics to try and figure our DLU conversion. + GetTextMetrics(FontDC, &TM); + + /* Calculation from the following documentation: + * https://support.microsoft.com/en-gb/help/125681/how-to-calculate-dialog-base-units-with-non-system-based-font + * This fixes bug 2137, dialog box calculation with a fixed-width system font + */ + { + SIZE extent; + GetTextExtentPoint32A(FontDC, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz", 52, &extent); + s_BaseUnitsX = (extent.cx / 26 + 1) / 2; + } + // s_BaseUnitsX = TM.tmAveCharWidth + 1; + s_BaseUnitsY = TM.tmHeight; + } + + /* Measure the *pixel* size of the string. */ + wmessage = WIN_UTF8ToStringW(messageboxdata->message); + SDL_zero(TextSize); + DrawTextW(FontDC, wmessage, -1, &TextSize, DT_CALCRECT | DT_LEFT | DT_NOPREFIX | DT_EDITCONTROL); + + // Add margins and some padding for hangs, etc. + TextSize.left += TextMargin; + TextSize.right += TextMargin + 2; + TextSize.top += TextMargin; + TextSize.bottom += TextMargin + 2; + + // Done with the DC, and the string + DeleteDC(FontDC); + SDL_free(wmessage); + + // Increase the size of the dialog by some border spacing around the text. + Size.cx = TextSize.right - TextSize.left; + Size.cy = TextSize.bottom - TextSize.top; + Size.cx += TextMargin * 2; + Size.cy += TextMargin * 2; + + // Make dialog wider and shift text over for the icon. + if (icon) { + Size.cx += IconMargin + IconWidth; + TextSize.left += IconMargin + IconWidth; + TextSize.right += IconMargin + IconWidth; + } + + // Ensure the size is wide enough for all of the buttons. + if (Size.cx < (LONG)messageboxdata->numbuttons * (ButtonWidth + ButtonMargin) + ButtonMargin) { + Size.cx = (LONG)messageboxdata->numbuttons * (ButtonWidth + ButtonMargin) + ButtonMargin; + } + + // Reset the height to the icon size if it is actually bigger than the text. + if (icon && Size.cy < (LONG)IconMargin * 2 + IconHeight) { + Size.cy = (LONG)IconMargin * 2 + IconHeight; + } + + // Add vertical space for the buttons and border. + Size.cy += ButtonHeight + TextMargin; + + dialog = CreateDialogData(Size.cx, Size.cy, messageboxdata->title); + if (!dialog) { + return false; + } + + if (icon && !AddDialogStaticIcon(dialog, IconMargin, IconMargin, IconWidth, IconHeight, icon)) { + FreeDialogData(dialog); + return false; + } + + if (!AddDialogStaticText(dialog, TextSize.left, TextSize.top, TextSize.right - TextSize.left, TextSize.bottom - TextSize.top, messageboxdata->message)) { + FreeDialogData(dialog); + return false; + } + + // Align the buttons to the right/bottom. + x = Size.cx - (ButtonWidth + ButtonMargin) * messageboxdata->numbuttons; + y = Size.cy - ButtonHeight - ButtonMargin; + for (i = 0; i < messageboxdata->numbuttons; i++) { + bool isdefault = false; + const char *buttontext; + const SDL_MessageBoxButtonData *sdlButton; + + /* We always have to create the dialog buttons from left to right + * so that the tab order is correct. Select the info to use + * depending on which order was requested. */ + if (messageboxdata->flags & SDL_MESSAGEBOX_BUTTONS_LEFT_TO_RIGHT) { + sdlButton = &messageboxdata->buttons[i]; + } else { + sdlButton = &messageboxdata->buttons[messageboxdata->numbuttons - 1 - i]; + } + + if (sdlButton->flags & SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT) { + defbuttoncount++; + if (defbuttoncount == 1) { + isdefault = true; + } + } + + buttontext = EscapeAmpersands(&escape, &escapesize, sdlButton->text); + /* Make sure to provide the correct ID to keep buttons indexed in the + * same order as how they are in messageboxdata. */ + if (!buttontext || !AddDialogButton(dialog, x, y, ButtonWidth, ButtonHeight, buttontext, IDBUTTONINDEX0 + (int)(sdlButton - messageboxdata->buttons), isdefault)) { + FreeDialogData(dialog); + SDL_free(ampescape); + return false; + } + + x += ButtonWidth + ButtonMargin; + } + SDL_free(ampescape); + + /* If we have a parent window, get the Instance and HWND for them + * so that our little dialog gets exclusive focus at all times. */ + if (messageboxdata->window) { + ParentWindow = messageboxdata->window->internal->hwnd; + } + + rc = DialogBoxIndirectParam(NULL, (DLGTEMPLATE *)dialog->lpDialog, ParentWindow, MessageBoxDialogProc, (LPARAM)messageboxdata); + if (rc >= IDBUTTONINDEX0 && rc - IDBUTTONINDEX0 < messageboxdata->numbuttons) { + *buttonID = messageboxdata->buttons[rc - IDBUTTONINDEX0].buttonID; + result = true; + } else if (rc == IDCLOSED) { + // Dialog window closed by user or system. + // This could use a special return code. + result = true; + *buttonID = -1; + } else { + if (rc == 0) { + SDL_SetError("Invalid parent window handle"); + } else if (rc == -1) { + SDL_SetError("The message box encountered an error."); + } else if (rc == IDINVALPTRINIT || rc == IDINVALPTRSETFOCUS || rc == IDINVALPTRCOMMAND) { + SDL_SetError("Invalid message box pointer in dialog procedure"); + } else if (rc == IDINVALPTRDLGITEM) { + SDL_SetError("Couldn't find dialog control of the default enter-key button"); + } else { + SDL_SetError("An unknown error occurred"); + } + result = false; + } + + FreeDialogData(dialog); + return result; +} + +/* TaskDialogIndirect procedure + * This is because SDL targets Windows XP (0x501), so this is not defined in the platform SDK. + */ +/* *INDENT-OFF* */ // clang-format off +typedef HRESULT (FAR WINAPI *TASKDIALOGINDIRECTPROC)(const TASKDIALOGCONFIG *pTaskConfig, int *pnButton, int *pnRadioButton, BOOL *pfVerificationFlagChecked); +/* *INDENT-ON* */ // clang-format on + +bool WIN_ShowMessageBox(const SDL_MessageBoxData *messageboxdata, int *buttonID) +{ + HWND ParentWindow = NULL; + wchar_t *wmessage; + wchar_t *wtitle; + TASKDIALOGCONFIG TaskConfig; + TASKDIALOG_BUTTON *pButtons; + TASKDIALOG_BUTTON *pButton; + HMODULE hComctl32; + TASKDIALOGINDIRECTPROC pfnTaskDialogIndirect; + HRESULT hr; + char *ampescape = NULL; + size_t ampescapesize = 0; + int nButton; + int nCancelButton; + int i; + bool result = false; + + if (SIZE_MAX / sizeof(TASKDIALOG_BUTTON) < messageboxdata->numbuttons) { + return SDL_OutOfMemory(); + } + + HMODULE hUser32 = GetModuleHandle(TEXT("user32.dll")); + typedef DPI_AWARENESS_CONTEXT (WINAPI * SetThreadDpiAwarenessContext_t)(DPI_AWARENESS_CONTEXT); + SetThreadDpiAwarenessContext_t SetThreadDpiAwarenessContextFunc = (SetThreadDpiAwarenessContext_t)GetProcAddress(hUser32, "SetThreadDpiAwarenessContext"); + DPI_AWARENESS_CONTEXT previous_context = DPI_AWARENESS_CONTEXT_UNAWARE; + if (SetThreadDpiAwarenessContextFunc) { + previous_context = SetThreadDpiAwarenessContextFunc(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2); + } + + // If we cannot load comctl32.dll use the old messagebox! + hComctl32 = LoadLibrary(TEXT("comctl32.dll")); + if (!hComctl32) { + result = WIN_ShowOldMessageBox(messageboxdata, buttonID); + goto done; + } + + /* If TaskDialogIndirect doesn't exist use the old messagebox! + This will fail prior to Windows Vista. + The manifest file in the application may require targeting version 6 of comctl32.dll, even + when we use LoadLibrary here! + If you don't want to bother with manifests, put this #pragma in your app's source code somewhere: + #pragma comment(linker,"\"/manifestdependency:type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"") + */ + pfnTaskDialogIndirect = (TASKDIALOGINDIRECTPROC)GetProcAddress(hComctl32, "TaskDialogIndirect"); + if (!pfnTaskDialogIndirect) { + FreeLibrary(hComctl32); + result = WIN_ShowOldMessageBox(messageboxdata, buttonID); + goto done; + } + + /* If we have a parent window, get the Instance and HWND for them + so that our little dialog gets exclusive focus at all times. */ + if (messageboxdata->window) { + ParentWindow = messageboxdata->window->internal->hwnd; + } + + wmessage = WIN_UTF8ToStringW(messageboxdata->message); + wtitle = WIN_UTF8ToStringW(messageboxdata->title); + + SDL_zero(TaskConfig); + TaskConfig.cbSize = sizeof(TASKDIALOGCONFIG); + TaskConfig.hwndParent = ParentWindow; + TaskConfig.dwFlags = TDF_SIZE_TO_CONTENT; + TaskConfig.pszWindowTitle = wtitle; + if (messageboxdata->flags & SDL_MESSAGEBOX_ERROR) { + TaskConfig.pszMainIcon = TD_ERROR_ICON; + } else if (messageboxdata->flags & SDL_MESSAGEBOX_WARNING) { + TaskConfig.pszMainIcon = TD_WARNING_ICON; + } else if (messageboxdata->flags & SDL_MESSAGEBOX_INFORMATION) { + TaskConfig.pszMainIcon = TD_INFORMATION_ICON; + } else { + TaskConfig.pszMainIcon = NULL; + } + + TaskConfig.pszContent = wmessage; + TaskConfig.cButtons = messageboxdata->numbuttons; + pButtons = (TASKDIALOG_BUTTON *)SDL_malloc(sizeof(TASKDIALOG_BUTTON) * messageboxdata->numbuttons); + TaskConfig.nDefaultButton = 0; + nCancelButton = 0; + for (i = 0; i < messageboxdata->numbuttons; i++) { + const char *buttontext; + if (messageboxdata->flags & SDL_MESSAGEBOX_BUTTONS_LEFT_TO_RIGHT) { + pButton = &pButtons[i]; + } else { + pButton = &pButtons[messageboxdata->numbuttons - 1 - i]; + } + if (messageboxdata->buttons[i].flags & SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT) { + nCancelButton = messageboxdata->buttons[i].buttonID; + pButton->nButtonID = IDCANCEL; + } else { + pButton->nButtonID = IDBUTTONINDEX0 + i; + } + buttontext = EscapeAmpersands(&escape, &escapesize, messageboxdata->buttons[i].text); + if (!buttontext) { + int j; + FreeLibrary(hComctl32); + SDL_free(ampescape); + SDL_free(wmessage); + SDL_free(wtitle); + for (j = 0; j < i; j++) { + SDL_free((wchar_t *)pButtons[j].pszButtonText); + } + SDL_free(pButtons); + return false; + } + pButton->pszButtonText = WIN_UTF8ToStringW(buttontext); + if (messageboxdata->buttons[i].flags & SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT) { + TaskConfig.nDefaultButton = pButton->nButtonID; + } + } + TaskConfig.pButtons = pButtons; + + // Show the Task Dialog + hr = pfnTaskDialogIndirect(&TaskConfig, &nButton, NULL, NULL); + + // Free everything + FreeLibrary(hComctl32); + SDL_free(ampescape); + SDL_free(wmessage); + SDL_free(wtitle); + for (i = 0; i < messageboxdata->numbuttons; i++) { + SDL_free((wchar_t *)pButtons[i].pszButtonText); + } + SDL_free(pButtons); + + // Check the Task Dialog was successful and give the result + if (SUCCEEDED(hr)) { + if (nButton == IDCANCEL) { + *buttonID = nCancelButton; + } else if (nButton >= IDBUTTONINDEX0 && nButton < IDBUTTONINDEX0 + messageboxdata->numbuttons) { + *buttonID = messageboxdata->buttons[nButton - IDBUTTONINDEX0].buttonID; + } else { + *buttonID = -1; + } + result = true; + } else { + // We failed showing the Task Dialog, use the old message box! + result = WIN_ShowOldMessageBox(messageboxdata, buttonID); + } + +done: + if (SetThreadDpiAwarenessContextFunc) { + SetThreadDpiAwarenessContextFunc(previous_context); + } + return result; +} + +#endif // SDL_VIDEO_DRIVER_WINDOWS diff --git a/contrib/SDL-3.2.8/src/video/windows/SDL_windowsmessagebox.h b/contrib/SDL-3.2.8/src/video/windows/SDL_windowsmessagebox.h new file mode 100644 index 0000000..28ed20c --- /dev/null +++ b/contrib/SDL-3.2.8/src/video/windows/SDL_windowsmessagebox.h @@ -0,0 +1,27 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include "SDL_internal.h" + +#ifdef SDL_VIDEO_DRIVER_WINDOWS + +extern bool WIN_ShowMessageBox(const SDL_MessageBoxData *messageboxdata, int *buttonID); + +#endif // SDL_VIDEO_DRIVER_WINDOWS diff --git a/contrib/SDL-3.2.8/src/video/windows/SDL_windowsmodes.c b/contrib/SDL-3.2.8/src/video/windows/SDL_windowsmodes.c new file mode 100644 index 0000000..77ebab2 --- /dev/null +++ b/contrib/SDL-3.2.8/src/video/windows/SDL_windowsmodes.c @@ -0,0 +1,927 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include "SDL_internal.h" + +#if defined(SDL_VIDEO_DRIVER_WINDOWS) && !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) + +#include "SDL_windowsvideo.h" +#include "../../events/SDL_displayevents_c.h" + +#ifdef HAVE_DXGI1_6_H +#define COBJMACROS +#include +#endif + +// Windows CE compatibility +#ifndef CDS_FULLSCREEN +#define CDS_FULLSCREEN 0 +#endif + +// #define DEBUG_MODES +// #define HIGHDPI_DEBUG_VERBOSE + +static void WIN_UpdateDisplayMode(SDL_VideoDevice *_this, LPCWSTR deviceName, DWORD index, SDL_DisplayMode *mode) +{ + SDL_DisplayModeData *data = (SDL_DisplayModeData *)mode->internal; + HDC hdc; + + data->DeviceMode.dmFields = (DM_BITSPERPEL | DM_PELSWIDTH | DM_PELSHEIGHT | DM_DISPLAYFREQUENCY | DM_DISPLAYFLAGS); + + // NOLINTNEXTLINE(bugprone-assignment-in-if-condition): No simple way to extract the assignment + if (index == ENUM_CURRENT_SETTINGS && (hdc = CreateDC(deviceName, NULL, NULL, NULL)) != NULL) { + char bmi_data[sizeof(BITMAPINFOHEADER) + 256 * sizeof(RGBQUAD)]; + LPBITMAPINFO bmi; + HBITMAP hbm; + + SDL_zeroa(bmi_data); + bmi = (LPBITMAPINFO)bmi_data; + bmi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + + hbm = CreateCompatibleBitmap(hdc, 1, 1); + GetDIBits(hdc, hbm, 0, 1, NULL, bmi, DIB_RGB_COLORS); + GetDIBits(hdc, hbm, 0, 1, NULL, bmi, DIB_RGB_COLORS); + DeleteObject(hbm); + DeleteDC(hdc); + if (bmi->bmiHeader.biCompression == BI_BITFIELDS) { + switch (*(Uint32 *)bmi->bmiColors) { + case 0x00FF0000: + mode->format = SDL_PIXELFORMAT_XRGB8888; + break; + case 0x000000FF: + mode->format = SDL_PIXELFORMAT_XBGR8888; + break; + case 0xF800: + mode->format = SDL_PIXELFORMAT_RGB565; + break; + case 0x7C00: + mode->format = SDL_PIXELFORMAT_XRGB1555; + break; + } + } else if (bmi->bmiHeader.biCompression == BI_RGB) { + if (bmi->bmiHeader.biBitCount == 24) { + mode->format = SDL_PIXELFORMAT_RGB24; + } else if (bmi->bmiHeader.biBitCount == 8) { + mode->format = SDL_PIXELFORMAT_INDEX8; + } else if (bmi->bmiHeader.biBitCount == 4) { + mode->format = SDL_PIXELFORMAT_INDEX4LSB; + } + } + } else if (mode->format == SDL_PIXELFORMAT_UNKNOWN) { + // FIXME: Can we tell what this will be? + if ((data->DeviceMode.dmFields & DM_BITSPERPEL) == DM_BITSPERPEL) { + switch (data->DeviceMode.dmBitsPerPel) { + case 32: + mode->format = SDL_PIXELFORMAT_XRGB8888; + break; + case 24: + mode->format = SDL_PIXELFORMAT_RGB24; + break; + case 16: + mode->format = SDL_PIXELFORMAT_RGB565; + break; + case 15: + mode->format = SDL_PIXELFORMAT_XRGB1555; + break; + case 8: + mode->format = SDL_PIXELFORMAT_INDEX8; + break; + case 4: + mode->format = SDL_PIXELFORMAT_INDEX4LSB; + break; + } + } + } +} + +static void *WIN_GetDXGIOutput(SDL_VideoDevice *_this, const WCHAR *DeviceName) +{ + void *result = NULL; + +#ifdef HAVE_DXGI_H + const SDL_VideoData *videodata = (const SDL_VideoData *)_this->internal; + int nAdapter, nOutput; + IDXGIAdapter *pDXGIAdapter; + IDXGIOutput *pDXGIOutput; + + if (!videodata->pDXGIFactory) { + return NULL; + } + + nAdapter = 0; + while (!result && SUCCEEDED(IDXGIFactory_EnumAdapters(videodata->pDXGIFactory, nAdapter, &pDXGIAdapter))) { + nOutput = 0; + while (!result && SUCCEEDED(IDXGIAdapter_EnumOutputs(pDXGIAdapter, nOutput, &pDXGIOutput))) { + DXGI_OUTPUT_DESC outputDesc; + if (SUCCEEDED(IDXGIOutput_GetDesc(pDXGIOutput, &outputDesc))) { + if (SDL_wcscmp(outputDesc.DeviceName, DeviceName) == 0) { + result = pDXGIOutput; + } + } + if (pDXGIOutput != result) { + IDXGIOutput_Release(pDXGIOutput); + } + nOutput++; + } + IDXGIAdapter_Release(pDXGIAdapter); + nAdapter++; + } +#endif + return result; +} + +static void WIN_ReleaseDXGIOutput(void *dxgi_output) +{ +#ifdef HAVE_DXGI_H + IDXGIOutput *pDXGIOutput = (IDXGIOutput *)dxgi_output; + + if (pDXGIOutput) { + IDXGIOutput_Release(pDXGIOutput); + } +#endif +} + +static SDL_DisplayOrientation WIN_GetNaturalOrientation(DEVMODE *mode) +{ + int width = mode->dmPelsWidth; + int height = mode->dmPelsHeight; + + // Use unrotated width/height to guess orientation + if (mode->dmDisplayOrientation == DMDO_90 || mode->dmDisplayOrientation == DMDO_270) { + int temp = width; + width = height; + height = temp; + } + + if (width >= height) { + return SDL_ORIENTATION_LANDSCAPE; + } else { + return SDL_ORIENTATION_PORTRAIT; + } +} + +static SDL_DisplayOrientation WIN_GetDisplayOrientation(DEVMODE *mode) +{ + if (WIN_GetNaturalOrientation(mode) == SDL_ORIENTATION_LANDSCAPE) { + switch (mode->dmDisplayOrientation) { + case DMDO_DEFAULT: + return SDL_ORIENTATION_LANDSCAPE; + case DMDO_90: + return SDL_ORIENTATION_PORTRAIT; + case DMDO_180: + return SDL_ORIENTATION_LANDSCAPE_FLIPPED; + case DMDO_270: + return SDL_ORIENTATION_PORTRAIT_FLIPPED; + default: + return SDL_ORIENTATION_UNKNOWN; + } + } else { + switch (mode->dmDisplayOrientation) { + case DMDO_DEFAULT: + return SDL_ORIENTATION_PORTRAIT; + case DMDO_90: + return SDL_ORIENTATION_LANDSCAPE_FLIPPED; + case DMDO_180: + return SDL_ORIENTATION_PORTRAIT_FLIPPED; + case DMDO_270: + return SDL_ORIENTATION_LANDSCAPE; + default: + return SDL_ORIENTATION_UNKNOWN; + } + } +} + +static void WIN_GetRefreshRate(void *dxgi_output, DEVMODE *mode, int *numerator, int *denominator) +{ + // We're not currently using DXGI to query display modes, so fake NTSC timings + switch (mode->dmDisplayFrequency) { + case 119: + case 59: + case 29: + *numerator = (mode->dmDisplayFrequency + 1) * 1000; + *denominator = 1001; + break; + default: + *numerator = mode->dmDisplayFrequency; + *denominator = 1; + break; + } + +#ifdef HAVE_DXGI_H + if (dxgi_output) { + IDXGIOutput *pDXGIOutput = (IDXGIOutput *)dxgi_output; + DXGI_MODE_DESC modeToMatch; + DXGI_MODE_DESC closestMatch; + + SDL_zero(modeToMatch); + modeToMatch.Width = mode->dmPelsWidth; + modeToMatch.Height = mode->dmPelsHeight; + modeToMatch.RefreshRate.Numerator = *numerator; + modeToMatch.RefreshRate.Denominator = *denominator; + modeToMatch.Format = DXGI_FORMAT_R8G8B8A8_UNORM; + + if (SUCCEEDED(IDXGIOutput_FindClosestMatchingMode(pDXGIOutput, &modeToMatch, &closestMatch, NULL))) { + *numerator = closestMatch.RefreshRate.Numerator; + *denominator = closestMatch.RefreshRate.Denominator; + } + } +#endif // HAVE_DXGI_H +} + +static float WIN_GetContentScale(SDL_VideoDevice *_this, HMONITOR hMonitor) +{ + const SDL_VideoData *videodata = (const SDL_VideoData *)_this->internal; + int dpi = 0; + + if (videodata->GetDpiForMonitor) { + UINT hdpi_uint, vdpi_uint; + if (videodata->GetDpiForMonitor(hMonitor, MDT_EFFECTIVE_DPI, &hdpi_uint, &vdpi_uint) == S_OK) { + dpi = (int)hdpi_uint; + } + } + if (dpi == 0) { + // Window 8.0 and below: same DPI for all monitors + HDC hdc = GetDC(NULL); + if (hdc) { + dpi = GetDeviceCaps(hdc, LOGPIXELSX); + ReleaseDC(NULL, hdc); + } + } + if (dpi == 0) { + // Safe default + dpi = USER_DEFAULT_SCREEN_DPI; + } + return dpi / (float)USER_DEFAULT_SCREEN_DPI; +} + +static bool WIN_GetDisplayMode(SDL_VideoDevice *_this, void *dxgi_output, HMONITOR hMonitor, LPCWSTR deviceName, DWORD index, SDL_DisplayMode *mode, SDL_DisplayOrientation *natural_orientation, SDL_DisplayOrientation *current_orientation) +{ + SDL_DisplayModeData *data; + DEVMODE devmode; + + devmode.dmSize = sizeof(devmode); + devmode.dmDriverExtra = 0; + if (!EnumDisplaySettingsW(deviceName, index, &devmode)) { + return false; + } + + data = (SDL_DisplayModeData *)SDL_malloc(sizeof(*data)); + if (!data) { + return false; + } + + SDL_zerop(mode); + mode->internal = data; + data->DeviceMode = devmode; + + mode->format = SDL_PIXELFORMAT_UNKNOWN; + mode->w = data->DeviceMode.dmPelsWidth; + mode->h = data->DeviceMode.dmPelsHeight; + WIN_GetRefreshRate(dxgi_output, &data->DeviceMode, &mode->refresh_rate_numerator, &mode->refresh_rate_denominator); + + // Fill in the mode information + WIN_UpdateDisplayMode(_this, deviceName, index, mode); + + if (natural_orientation) { + *natural_orientation = WIN_GetNaturalOrientation(&devmode); + } + if (current_orientation) { + *current_orientation = WIN_GetDisplayOrientation(&devmode); + } + + return true; +} + +static char *WIN_GetDisplayNameVista(SDL_VideoData *videodata, const WCHAR *deviceName) +{ + DISPLAYCONFIG_PATH_INFO *paths = NULL; + DISPLAYCONFIG_MODE_INFO *modes = NULL; + char *result = NULL; + UINT32 pathCount = 0; + UINT32 modeCount = 0; + UINT32 i; + LONG rc; + + if (!videodata->GetDisplayConfigBufferSizes || !videodata->QueryDisplayConfig || !videodata->DisplayConfigGetDeviceInfo) { + return NULL; + } + + do { + rc = videodata->GetDisplayConfigBufferSizes(QDC_ONLY_ACTIVE_PATHS, &pathCount, &modeCount); + if (rc != ERROR_SUCCESS) { + goto WIN_GetDisplayNameVista_failed; + } + + SDL_free(paths); + SDL_free(modes); + + paths = (DISPLAYCONFIG_PATH_INFO *)SDL_malloc(sizeof(DISPLAYCONFIG_PATH_INFO) * pathCount); + modes = (DISPLAYCONFIG_MODE_INFO *)SDL_malloc(sizeof(DISPLAYCONFIG_MODE_INFO) * modeCount); + if ((!paths) || (!modes)) { + goto WIN_GetDisplayNameVista_failed; + } + + rc = videodata->QueryDisplayConfig(QDC_ONLY_ACTIVE_PATHS, &pathCount, paths, &modeCount, modes, 0); + } while (rc == ERROR_INSUFFICIENT_BUFFER); + + if (rc == ERROR_SUCCESS) { + for (i = 0; i < pathCount; i++) { + DISPLAYCONFIG_SOURCE_DEVICE_NAME sourceName; + DISPLAYCONFIG_TARGET_DEVICE_NAME targetName; + + SDL_zero(sourceName); + sourceName.header.adapterId = paths[i].targetInfo.adapterId; + sourceName.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_NAME; + sourceName.header.size = sizeof(sourceName); + sourceName.header.id = paths[i].sourceInfo.id; + rc = videodata->DisplayConfigGetDeviceInfo(&sourceName.header); + if (rc != ERROR_SUCCESS) { + break; + } else if (SDL_wcscmp(deviceName, sourceName.viewGdiDeviceName) != 0) { + continue; + } + + SDL_zero(targetName); + targetName.header.adapterId = paths[i].targetInfo.adapterId; + targetName.header.id = paths[i].targetInfo.id; + targetName.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_TARGET_NAME; + targetName.header.size = sizeof(targetName); + rc = videodata->DisplayConfigGetDeviceInfo(&targetName.header); + if (rc == ERROR_SUCCESS) { + result = WIN_StringToUTF8W(targetName.monitorFriendlyDeviceName); + /* if we got an empty string, treat it as failure so we'll fallback + to getting the generic name. */ + if (result && (*result == '\0')) { + SDL_free(result); + result = NULL; + } + } + break; + } + } + + SDL_free(paths); + SDL_free(modes); + return result; + +WIN_GetDisplayNameVista_failed: + SDL_free(result); + SDL_free(paths); + SDL_free(modes); + return NULL; +} + +#ifdef HAVE_DXGI1_6_H +static bool WIN_GetMonitorDESC1(HMONITOR hMonitor, DXGI_OUTPUT_DESC1 *desc) +{ + typedef HRESULT (WINAPI * PFN_CREATE_DXGI_FACTORY)(REFIID riid, void **ppFactory); + PFN_CREATE_DXGI_FACTORY CreateDXGIFactoryFunc = NULL; + SDL_SharedObject *hDXGIMod = NULL; + bool found = false; + + hDXGIMod = SDL_LoadObject("dxgi.dll"); + if (hDXGIMod) { + CreateDXGIFactoryFunc = (PFN_CREATE_DXGI_FACTORY)SDL_LoadFunction(hDXGIMod, "CreateDXGIFactory1"); + } + if (CreateDXGIFactoryFunc) { + static const GUID SDL_IID_IDXGIFactory1 = { 0x770aae78, 0xf26f, 0x4dba, { 0xa8, 0x29, 0x25, 0x3c, 0x83, 0xd1, 0xb3, 0x87 } }; + static const GUID SDL_IID_IDXGIOutput6 = { 0x068346e8, 0xaaec, 0x4b84, { 0xad, 0xd7, 0x13, 0x7f, 0x51, 0x3f, 0x77, 0xa1 } }; + IDXGIFactory1 *dxgiFactory; + + if (SUCCEEDED(CreateDXGIFactoryFunc(&SDL_IID_IDXGIFactory1, (void **)&dxgiFactory))) { + IDXGIAdapter1 *dxgiAdapter; + UINT adapter = 0; + while (!found && SUCCEEDED(IDXGIFactory1_EnumAdapters1(dxgiFactory, adapter, &dxgiAdapter))) { + IDXGIOutput *dxgiOutput; + UINT output = 0; + while (!found && SUCCEEDED(IDXGIAdapter1_EnumOutputs(dxgiAdapter, output, &dxgiOutput))) { + IDXGIOutput6 *dxgiOutput6; + if (SUCCEEDED(IDXGIOutput_QueryInterface(dxgiOutput, &SDL_IID_IDXGIOutput6, (void **)&dxgiOutput6))) { + if (SUCCEEDED(IDXGIOutput6_GetDesc1(dxgiOutput6, desc))) { + if (desc->Monitor == hMonitor) { + found = true; + } + } + IDXGIOutput6_Release(dxgiOutput6); + } + IDXGIOutput_Release(dxgiOutput); + ++output; + } + IDXGIAdapter1_Release(dxgiAdapter); + ++adapter; + } + IDXGIFactory2_Release(dxgiFactory); + } + } + if (hDXGIMod) { + SDL_UnloadObject(hDXGIMod); + } + return found; +} + +static bool WIN_GetMonitorPathInfo(SDL_VideoData *videodata, HMONITOR hMonitor, DISPLAYCONFIG_PATH_INFO *path_info) +{ + LONG result; + MONITORINFOEXW view_info; + UINT32 i; + UINT32 num_path_array_elements = 0; + UINT32 num_mode_info_array_elements = 0; + DISPLAYCONFIG_PATH_INFO *path_infos = NULL, *new_path_infos; + DISPLAYCONFIG_MODE_INFO *mode_infos = NULL, *new_mode_infos; + bool found = false; + + if (!videodata->GetDisplayConfigBufferSizes || !videodata->QueryDisplayConfig || !videodata->DisplayConfigGetDeviceInfo) { + return false; + } + + SDL_zero(view_info); + view_info.cbSize = sizeof(view_info); + if (!GetMonitorInfoW(hMonitor, (MONITORINFO *)&view_info)) { + goto done; + } + + do { + if (videodata->GetDisplayConfigBufferSizes(QDC_ONLY_ACTIVE_PATHS, &num_path_array_elements, &num_mode_info_array_elements) != ERROR_SUCCESS) { + SDL_free(path_infos); + SDL_free(mode_infos); + return false; + } + + new_path_infos = (DISPLAYCONFIG_PATH_INFO *)SDL_realloc(path_infos, num_path_array_elements * sizeof(*path_infos)); + if (!new_path_infos) { + goto done; + } + path_infos = new_path_infos; + + new_mode_infos = (DISPLAYCONFIG_MODE_INFO *)SDL_realloc(mode_infos, num_mode_info_array_elements * sizeof(*mode_infos)); + if (!new_mode_infos) { + goto done; + } + mode_infos = new_mode_infos; + + result = videodata->QueryDisplayConfig(QDC_ONLY_ACTIVE_PATHS, &num_path_array_elements, path_infos, &num_mode_info_array_elements, mode_infos, NULL); + + } while (result == ERROR_INSUFFICIENT_BUFFER); + + if (result == ERROR_SUCCESS) { + for (i = 0; i < num_path_array_elements; ++i) { + DISPLAYCONFIG_SOURCE_DEVICE_NAME device_name; + + SDL_zero(device_name); + device_name.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_NAME; + device_name.header.size = sizeof(device_name); + device_name.header.adapterId = path_infos[i].sourceInfo.adapterId; + device_name.header.id = path_infos[i].sourceInfo.id; + if (videodata->DisplayConfigGetDeviceInfo(&device_name.header) == ERROR_SUCCESS) { + if (SDL_wcscmp(view_info.szDevice, device_name.viewGdiDeviceName) == 0) { + SDL_copyp(path_info, &path_infos[i]); + found = true; + break; + } + } + } + } + +done: + SDL_free(path_infos); + SDL_free(mode_infos); + + return found; +} + +static float WIN_GetSDRWhitePoint(SDL_VideoDevice *_this, HMONITOR hMonitor) +{ + DISPLAYCONFIG_PATH_INFO path_info; + SDL_VideoData *videodata = _this->internal; + float SDR_white_level = 1.0f; + + if (WIN_GetMonitorPathInfo(videodata, hMonitor, &path_info)) { + /* workarounds for https://github.com/libsdl-org/SDL/issues/11193 */ + struct SDL_DISPLAYCONFIG_SDR_WHITE_LEVEL { + DISPLAYCONFIG_DEVICE_INFO_HEADER header; + ULONG SDRWhiteLevel; + } white_level; + #define DISPLAYCONFIG_DEVICE_INFO_GET_SDR_WHITE_LEVEL 11 + + SDL_zero(white_level); + white_level.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_SDR_WHITE_LEVEL; + white_level.header.size = sizeof(white_level); + white_level.header.adapterId = path_info.targetInfo.adapterId; + white_level.header.id = path_info.targetInfo.id; + // WIN_GetMonitorPathInfo() succeeded: DisplayConfigGetDeviceInfo is not NULL + if (videodata->DisplayConfigGetDeviceInfo(&white_level.header) == ERROR_SUCCESS && + white_level.SDRWhiteLevel > 0) { + SDR_white_level = (white_level.SDRWhiteLevel / 1000.0f); + } + } + return SDR_white_level; +} + +static void WIN_GetHDRProperties(SDL_VideoDevice *_this, HMONITOR hMonitor, SDL_HDROutputProperties *HDR) +{ + DXGI_OUTPUT_DESC1 desc; + + SDL_zerop(HDR); + + if (WIN_GetMonitorDESC1(hMonitor, &desc)) { + if (desc.ColorSpace == DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020) { + HDR->SDR_white_level = WIN_GetSDRWhitePoint(_this, hMonitor); + HDR->HDR_headroom = (desc.MaxLuminance / 80.0f) / HDR->SDR_white_level; + } + } +} +#endif // HAVE_DXGI1_6_H + +static void WIN_AddDisplay(SDL_VideoDevice *_this, HMONITOR hMonitor, const MONITORINFOEXW *info, int *display_index) +{ + int i, index = *display_index; + SDL_VideoDisplay display; + SDL_DisplayData *displaydata; + void *dxgi_output = NULL; + SDL_DisplayMode mode; + SDL_DisplayOrientation natural_orientation; + SDL_DisplayOrientation current_orientation; + float content_scale = WIN_GetContentScale(_this, hMonitor); + +#ifdef DEBUG_MODES + SDL_Log("Display: %s", WIN_StringToUTF8W(info->szDevice)); +#endif + + dxgi_output = WIN_GetDXGIOutput(_this, info->szDevice); + bool found = WIN_GetDisplayMode(_this, dxgi_output, hMonitor, info->szDevice, ENUM_CURRENT_SETTINGS, &mode, &natural_orientation, ¤t_orientation); + WIN_ReleaseDXGIOutput(dxgi_output); + if (!found) { + return; + } + + // Prevent adding duplicate displays. Do this after we know the display is + // ready to be added to allow any displays that we can't fully query to be + // removed + for (i = 0; i < _this->num_displays; ++i) { + SDL_DisplayData *internal = _this->displays[i]->internal; + if (SDL_wcscmp(internal->DeviceName, info->szDevice) == 0) { + bool moved = (index != i); + bool changed_bounds = false; + + if (internal->state != DisplayRemoved) { + // We've already enumerated this display, don't move it + return; + } + + if (index >= _this->num_displays) { + // This should never happen due to the check above, but just in case... + return; + } + + if (moved) { + SDL_VideoDisplay *tmp; + + tmp = _this->displays[index]; + _this->displays[index] = _this->displays[i]; + _this->displays[i] = tmp; + i = index; + } + + internal->MonitorHandle = hMonitor; + internal->state = DisplayUnchanged; + + if (!_this->setting_display_mode) { + SDL_VideoDisplay *existing_display = _this->displays[i]; + SDL_Rect bounds; + + SDL_ResetFullscreenDisplayModes(existing_display); + SDL_SetDesktopDisplayMode(existing_display, &mode); + if (WIN_GetDisplayBounds(_this, existing_display, &bounds) && + SDL_memcmp(&internal->bounds, &bounds, sizeof(bounds)) != 0) { + changed_bounds = true; + SDL_copyp(&internal->bounds, &bounds); + } + if (moved || changed_bounds) { + SDL_SendDisplayEvent(existing_display, SDL_EVENT_DISPLAY_MOVED, 0, 0); + } + SDL_SendDisplayEvent(existing_display, SDL_EVENT_DISPLAY_ORIENTATION, current_orientation, 0); + SDL_SetDisplayContentScale(existing_display, content_scale); +#ifdef HAVE_DXGI1_6_H + SDL_HDROutputProperties HDR; + WIN_GetHDRProperties(_this, hMonitor, &HDR); + SDL_SetDisplayHDRProperties(existing_display, &HDR); +#endif + } + goto done; + } + } + + displaydata = (SDL_DisplayData *)SDL_calloc(1, sizeof(*displaydata)); + if (!displaydata) { + return; + } + SDL_memcpy(displaydata->DeviceName, info->szDevice, sizeof(displaydata->DeviceName)); + displaydata->MonitorHandle = hMonitor; + displaydata->state = DisplayAdded; + + SDL_zero(display); + display.name = WIN_GetDisplayNameVista(_this->internal, info->szDevice); + if (!display.name) { + DISPLAY_DEVICEW device; + SDL_zero(device); + device.cb = sizeof(device); + if (EnumDisplayDevicesW(info->szDevice, 0, &device, 0)) { + display.name = WIN_StringToUTF8W(device.DeviceString); + } + } + + display.desktop_mode = mode; + display.natural_orientation = natural_orientation; + display.current_orientation = current_orientation; + display.content_scale = content_scale; + display.device = _this; + display.internal = displaydata; + WIN_GetDisplayBounds(_this, &display, &displaydata->bounds); +#ifdef HAVE_DXGI1_6_H + WIN_GetHDRProperties(_this, hMonitor, &display.HDR); +#endif + SDL_AddVideoDisplay(&display, false); + SDL_free(display.name); + +done: + *display_index += 1; +} + +typedef struct _WIN_AddDisplaysData +{ + SDL_VideoDevice *video_device; + int display_index; + bool want_primary; +} WIN_AddDisplaysData; + +static BOOL CALLBACK WIN_AddDisplaysCallback(HMONITOR hMonitor, + HDC hdcMonitor, + LPRECT lprcMonitor, + LPARAM dwData) +{ + WIN_AddDisplaysData *data = (WIN_AddDisplaysData *)dwData; + MONITORINFOEXW info; + + SDL_zero(info); + info.cbSize = sizeof(info); + + if (GetMonitorInfoW(hMonitor, (LPMONITORINFO)&info) != 0) { + const bool is_primary = ((info.dwFlags & MONITORINFOF_PRIMARY) == MONITORINFOF_PRIMARY); + + if (is_primary == data->want_primary) { + WIN_AddDisplay(data->video_device, hMonitor, &info, &data->display_index); + } + } + + // continue enumeration + return TRUE; +} + +static void WIN_AddDisplays(SDL_VideoDevice *_this) +{ + WIN_AddDisplaysData callback_data; + callback_data.video_device = _this; + callback_data.display_index = 0; + + callback_data.want_primary = true; + EnumDisplayMonitors(NULL, NULL, WIN_AddDisplaysCallback, (LPARAM)&callback_data); + + callback_data.want_primary = false; + EnumDisplayMonitors(NULL, NULL, WIN_AddDisplaysCallback, (LPARAM)&callback_data); +} + +bool WIN_InitModes(SDL_VideoDevice *_this) +{ + WIN_AddDisplays(_this); + + if (_this->num_displays == 0) { + return SDL_SetError("No displays available"); + } + return true; +} + +bool WIN_GetDisplayBounds(SDL_VideoDevice *_this, SDL_VideoDisplay *display, SDL_Rect *rect) +{ + const SDL_DisplayData *data = display->internal; + MONITORINFO minfo; + BOOL rc; + + SDL_zero(minfo); + minfo.cbSize = sizeof(MONITORINFO); + rc = GetMonitorInfo(data->MonitorHandle, &minfo); + + if (!rc) { + return SDL_SetError("Couldn't find monitor data"); + } + + rect->x = minfo.rcMonitor.left; + rect->y = minfo.rcMonitor.top; + rect->w = minfo.rcMonitor.right - minfo.rcMonitor.left; + rect->h = minfo.rcMonitor.bottom - minfo.rcMonitor.top; + + return true; +} + +bool WIN_GetDisplayUsableBounds(SDL_VideoDevice *_this, SDL_VideoDisplay *display, SDL_Rect *rect) +{ + const SDL_DisplayData *data = display->internal; + MONITORINFO minfo; + BOOL rc; + + SDL_zero(minfo); + minfo.cbSize = sizeof(MONITORINFO); + rc = GetMonitorInfo(data->MonitorHandle, &minfo); + + if (!rc) { + return SDL_SetError("Couldn't find monitor data"); + } + + rect->x = minfo.rcWork.left; + rect->y = minfo.rcWork.top; + rect->w = minfo.rcWork.right - minfo.rcWork.left; + rect->h = minfo.rcWork.bottom - minfo.rcWork.top; + + return true; +} + +bool WIN_GetDisplayModes(SDL_VideoDevice *_this, SDL_VideoDisplay *display) +{ + SDL_DisplayData *data = display->internal; + void *dxgi_output; + DWORD i; + SDL_DisplayMode mode; + + dxgi_output = WIN_GetDXGIOutput(_this, data->DeviceName); + + for (i = 0;; ++i) { + if (!WIN_GetDisplayMode(_this, dxgi_output, data->MonitorHandle, data->DeviceName, i, &mode, NULL, NULL)) { + break; + } + if (SDL_ISPIXELFORMAT_INDEXED(mode.format)) { + // We don't support palettized modes now + SDL_free(mode.internal); + continue; + } + if (mode.format != SDL_PIXELFORMAT_UNKNOWN) { + if (!SDL_AddFullscreenDisplayMode(display, &mode)) { + SDL_free(mode.internal); + } + } else { + SDL_free(mode.internal); + } + } + + WIN_ReleaseDXGIOutput(dxgi_output); + + return true; +} + +#ifdef DEBUG_MODES +static void WIN_LogMonitor(SDL_VideoDevice *_this, HMONITOR mon) +{ + const SDL_VideoData *vid_data = (const SDL_VideoData *)_this->internal; + MONITORINFOEX minfo; + UINT xdpi = 0, ydpi = 0; + char *name_utf8; + + if (vid_data->GetDpiForMonitor) { + vid_data->GetDpiForMonitor(mon, MDT_EFFECTIVE_DPI, &xdpi, &ydpi); + } + + SDL_zero(minfo); + minfo.cbSize = sizeof(minfo); + GetMonitorInfo(mon, (LPMONITORINFO)&minfo); + + name_utf8 = WIN_StringToUTF8(minfo.szDevice); + + SDL_Log("WIN_LogMonitor: monitor \"%s\": dpi: %d windows screen coordinates: %d, %d, %dx%d", + name_utf8, + xdpi, + minfo.rcMonitor.left, + minfo.rcMonitor.top, + minfo.rcMonitor.right - minfo.rcMonitor.left, + minfo.rcMonitor.bottom - minfo.rcMonitor.top); + + SDL_free(name_utf8); +} +#endif + +bool WIN_SetDisplayMode(SDL_VideoDevice *_this, SDL_VideoDisplay *display, SDL_DisplayMode *mode) +{ + SDL_DisplayData *displaydata = display->internal; + SDL_DisplayModeData *data = (SDL_DisplayModeData *)mode->internal; + LONG status; + +#ifdef DEBUG_MODES + SDL_Log("WIN_SetDisplayMode: monitor state before mode change:"); + WIN_LogMonitor(_this, displaydata->MonitorHandle); +#endif + + /* High-DPI notes: + + - ChangeDisplaySettingsEx always takes pixels. + - e.g. if the display is set to 2880x1800 with 200% scaling in Display Settings + - calling ChangeDisplaySettingsEx with a dmPelsWidth/Height other than 2880x1800 will + change the monitor DPI to 96. (100% scaling) + - calling ChangeDisplaySettingsEx with a dmPelsWidth/Height of 2880x1800 (or a NULL DEVMODE*) will + reset the monitor DPI to 192. (200% scaling) + + NOTE: these are temporary changes in DPI, not modifications to the Control Panel setting. */ + if (mode->internal == display->desktop_mode.internal) { +#ifdef DEBUG_MODES + SDL_Log("WIN_SetDisplayMode: resetting to original resolution"); +#endif + status = ChangeDisplaySettingsExW(displaydata->DeviceName, NULL, NULL, CDS_FULLSCREEN, NULL); + } else { +#ifdef DEBUG_MODES + SDL_Log("WIN_SetDisplayMode: changing to %dx%d pixels", data->DeviceMode.dmPelsWidth, data->DeviceMode.dmPelsHeight); +#endif + status = ChangeDisplaySettingsExW(displaydata->DeviceName, &data->DeviceMode, NULL, CDS_FULLSCREEN, NULL); + } + if (status != DISP_CHANGE_SUCCESSFUL) { + const char *reason = "Unknown reason"; + switch (status) { + case DISP_CHANGE_BADFLAGS: + reason = "DISP_CHANGE_BADFLAGS"; + break; + case DISP_CHANGE_BADMODE: + reason = "DISP_CHANGE_BADMODE"; + break; + case DISP_CHANGE_BADPARAM: + reason = "DISP_CHANGE_BADPARAM"; + break; + case DISP_CHANGE_FAILED: + reason = "DISP_CHANGE_FAILED"; + break; + } + return SDL_SetError("ChangeDisplaySettingsEx() failed: %s", reason); + } + +#ifdef DEBUG_MODES + SDL_Log("WIN_SetDisplayMode: monitor state after mode change:"); + WIN_LogMonitor(_this, displaydata->MonitorHandle); +#endif + + EnumDisplaySettingsW(displaydata->DeviceName, ENUM_CURRENT_SETTINGS, &data->DeviceMode); + WIN_UpdateDisplayMode(_this, displaydata->DeviceName, ENUM_CURRENT_SETTINGS, mode); + return true; +} + +void WIN_RefreshDisplays(SDL_VideoDevice *_this) +{ + int i; + + // Mark all displays as potentially invalid to detect + // entries that have actually been removed + for (i = 0; i < _this->num_displays; ++i) { + SDL_DisplayData *internal = _this->displays[i]->internal; + internal->state = DisplayRemoved; + } + + // Enumerate displays to add any new ones and mark still + // connected entries as valid + WIN_AddDisplays(_this); + + // Delete any entries still marked as invalid, iterate + // in reverse as each delete takes effect immediately + for (i = _this->num_displays - 1; i >= 0; --i) { + SDL_VideoDisplay *display = _this->displays[i]; + SDL_DisplayData *internal = display->internal; + if (internal->state == DisplayRemoved) { + SDL_DelVideoDisplay(display->id, true); + } + } + + // Send events for any newly added displays + for (i = 0; i < _this->num_displays; ++i) { + SDL_VideoDisplay *display = _this->displays[i]; + SDL_DisplayData *internal = display->internal; + if (internal->state == DisplayAdded) { + SDL_SendDisplayEvent(display, SDL_EVENT_DISPLAY_ADDED, 0, 0); + } + } +} + +void WIN_QuitModes(SDL_VideoDevice *_this) +{ + // All fullscreen windows should have restored modes by now +} + +#endif // SDL_VIDEO_DRIVER_WINDOWS diff --git a/contrib/SDL-3.2.8/src/video/windows/SDL_windowsmodes.h b/contrib/SDL-3.2.8/src/video/windows/SDL_windowsmodes.h new file mode 100644 index 0000000..3d294c3 --- /dev/null +++ b/contrib/SDL-3.2.8/src/video/windows/SDL_windowsmodes.h @@ -0,0 +1,55 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include "SDL_internal.h" + +#ifndef SDL_windowsmodes_h_ +#define SDL_windowsmodes_h_ + +typedef enum +{ + DisplayUnchanged, + DisplayAdded, + DisplayRemoved, + +} WIN_DisplayState; + +struct SDL_DisplayData +{ + WCHAR DeviceName[32]; + HMONITOR MonitorHandle; + WIN_DisplayState state; + SDL_Rect bounds; +}; + +struct SDL_DisplayModeData +{ + DEVMODE DeviceMode; +}; + +extern bool WIN_InitModes(SDL_VideoDevice *_this); +extern bool WIN_GetDisplayBounds(SDL_VideoDevice *_this, SDL_VideoDisplay *display, SDL_Rect *rect); +extern bool WIN_GetDisplayUsableBounds(SDL_VideoDevice *_this, SDL_VideoDisplay *display, SDL_Rect *rect); +extern bool WIN_GetDisplayModes(SDL_VideoDevice *_this, SDL_VideoDisplay *display); +extern bool WIN_SetDisplayMode(SDL_VideoDevice *_this, SDL_VideoDisplay *display, SDL_DisplayMode *mode); +extern void WIN_RefreshDisplays(SDL_VideoDevice *_this); +extern void WIN_QuitModes(SDL_VideoDevice *_this); + +#endif // SDL_windowsmodes_h_ diff --git a/contrib/SDL-3.2.8/src/video/windows/SDL_windowsmouse.c b/contrib/SDL-3.2.8/src/video/windows/SDL_windowsmouse.c new file mode 100644 index 0000000..3d6bcc4 --- /dev/null +++ b/contrib/SDL-3.2.8/src/video/windows/SDL_windowsmouse.c @@ -0,0 +1,745 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include "SDL_internal.h" + +#if defined(SDL_VIDEO_DRIVER_WINDOWS) && !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) + +#include "SDL_windowsvideo.h" +#include "SDL_windowsevents.h" +#include "SDL_windowsrawinput.h" + +#include "../SDL_video_c.h" +#include "../../events/SDL_mouse_c.h" +#include "../../joystick/usb_ids.h" +#include "../../core/windows/SDL_windows.h" // for checking windows version + + +typedef struct CachedCursor +{ + float scale; + HCURSOR cursor; + struct CachedCursor *next; +} CachedCursor; + +struct SDL_CursorData +{ + SDL_Surface *surface; + int hot_x; + int hot_y; + CachedCursor *cache; + HCURSOR cursor; +}; + +typedef struct +{ + Uint64 xs[5]; + Uint64 ys[5]; + Sint64 residual[2]; + Uint32 dpiscale; + Uint32 dpidenom; + int last_node; + bool enhanced; + bool dpiaware; +} WIN_MouseData; + +DWORD SDL_last_warp_time = 0; +HCURSOR SDL_cursor = NULL; +static SDL_Cursor *SDL_blank_cursor = NULL; +static WIN_MouseData WIN_system_scale_data; + +static SDL_Cursor *WIN_CreateCursorAndData(HCURSOR hcursor) +{ + if (!hcursor) { + return NULL; + } + + SDL_Cursor *cursor = (SDL_Cursor *)SDL_calloc(1, sizeof(*cursor)); + if (!cursor) { + return NULL; + } + + SDL_CursorData *data = (SDL_CursorData *)SDL_calloc(1, sizeof(*data)); + if (!data) { + SDL_free(cursor); + return NULL; + } + + data->cursor = hcursor; + cursor->internal = data; + return cursor; +} + + +static bool IsMonochromeSurface(SDL_Surface *surface) +{ + int x, y; + Uint8 r, g, b, a; + + SDL_assert(surface->format == SDL_PIXELFORMAT_ARGB8888); + + for (y = 0; y < surface->h; y++) { + for (x = 0; x < surface->w; x++) { + SDL_ReadSurfacePixel(surface, x, y, &r, &g, &b, &a); + + // Black or white pixel. + if (!((r == 0x00 && g == 0x00 && b == 0x00) || (r == 0xff && g == 0xff && b == 0xff))) { + return false; + } + + // Transparent or opaque pixel. + if (!(a == 0x00 || a == 0xff)) { + return false; + } + } + } + + return true; +} + +static HBITMAP CreateColorBitmap(SDL_Surface *surface) +{ + HBITMAP bitmap; + BITMAPINFO bi; + void *pixels; + + SDL_assert(surface->format == SDL_PIXELFORMAT_ARGB8888); + + SDL_zero(bi); + bi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + bi.bmiHeader.biWidth = surface->w; + bi.bmiHeader.biHeight = -surface->h; // Invert height to make the top-down DIB. + bi.bmiHeader.biPlanes = 1; + bi.bmiHeader.biBitCount = 32; + bi.bmiHeader.biCompression = BI_RGB; + + bitmap = CreateDIBSection(NULL, &bi, DIB_RGB_COLORS, &pixels, NULL, 0); + if (!bitmap || !pixels) { + WIN_SetError("CreateDIBSection()"); + if (bitmap) { + DeleteObject(bitmap); + } + return NULL; + } + + SDL_memcpy(pixels, surface->pixels, surface->pitch * surface->h); + + return bitmap; +} + +/* Generate bitmap with a mask and optional monochrome image data. + * + * For info on the expected mask format see: + * https://devblogs.microsoft.com/oldnewthing/20101018-00/?p=12513 + */ +static HBITMAP CreateMaskBitmap(SDL_Surface *surface, bool is_monochrome) +{ + HBITMAP bitmap; + bool isstack; + void *pixels; + int x, y; + Uint8 r, g, b, a; + Uint8 *dst; + const int pitch = ((surface->w + 15) & ~15) / 8; + const int size = pitch * surface->h; + static const unsigned char masks[] = { 0x80, 0x40, 0x20, 0x10, 0x8, 0x4, 0x2, 0x1 }; + + SDL_assert(surface->format == SDL_PIXELFORMAT_ARGB8888); + + pixels = SDL_small_alloc(Uint8, size * (is_monochrome ? 2 : 1), &isstack); + if (!pixels) { + SDL_OutOfMemory(); + return NULL; + } + + dst = (Uint8 *)pixels; + + // Make the mask completely transparent. + SDL_memset(dst, 0xff, size); + if (is_monochrome) { + SDL_memset(dst + size, 0x00, size); + } + + for (y = 0; y < surface->h; y++, dst += pitch) { + for (x = 0; x < surface->w; x++) { + SDL_ReadSurfacePixel(surface, x, y, &r, &g, &b, &a); + + if (a != 0) { + // Reset bit of an opaque pixel. + dst[x >> 3] &= ~masks[x & 7]; + } + + if (is_monochrome && !(r == 0x00 && g == 0x00 && b == 0x00)) { + // Set bit of white or inverted pixel. + dst[size + (x >> 3)] |= masks[x & 7]; + } + } + } + + bitmap = CreateBitmap(surface->w, surface->h * (is_monochrome ? 2 : 1), 1, 1, pixels); + SDL_small_free(pixels, isstack); + if (!bitmap) { + WIN_SetError("CreateBitmap()"); + return NULL; + } + + return bitmap; +} + +static HCURSOR WIN_CreateHCursor(SDL_Surface *surface, int hot_x, int hot_y) +{ + HCURSOR hcursor; + ICONINFO ii; + bool is_monochrome = IsMonochromeSurface(surface); + + SDL_zero(ii); + ii.fIcon = FALSE; + ii.xHotspot = (DWORD)hot_x; + ii.yHotspot = (DWORD)hot_y; + ii.hbmMask = CreateMaskBitmap(surface, is_monochrome); + ii.hbmColor = is_monochrome ? NULL : CreateColorBitmap(surface); + + if (!ii.hbmMask || (!is_monochrome && !ii.hbmColor)) { + SDL_SetError("Couldn't create cursor bitmaps"); + if (ii.hbmMask) { + DeleteObject(ii.hbmMask); + } + if (ii.hbmColor) { + DeleteObject(ii.hbmColor); + } + return NULL; + } + + hcursor = CreateIconIndirect(&ii); + if (!hcursor) { + WIN_SetError("CreateIconIndirect()"); + DeleteObject(ii.hbmMask); + if (ii.hbmColor) { + DeleteObject(ii.hbmColor); + } + return NULL; + } + + DeleteObject(ii.hbmMask); + if (ii.hbmColor) { + DeleteObject(ii.hbmColor); + } + + return hcursor; +} + +static SDL_Cursor *WIN_CreateCursor(SDL_Surface *surface, int hot_x, int hot_y) +{ + if (!SDL_SurfaceHasAlternateImages(surface)) { + HCURSOR hcursor = WIN_CreateHCursor(surface, hot_x, hot_y); + if (!hcursor) { + return NULL; + } + return WIN_CreateCursorAndData(hcursor); + } + + // Dynamically generate cursors at the appropriate DPI + SDL_Cursor *cursor = (SDL_Cursor *)SDL_calloc(1, sizeof(*cursor)); + if (cursor) { + SDL_CursorData *data = (SDL_CursorData *)SDL_calloc(1, sizeof(*data)); + if (!data) { + SDL_free(cursor); + return NULL; + } + data->hot_x = hot_x; + data->hot_y = hot_y; + data->surface = surface; + ++surface->refcount; + cursor->internal = data; + } + return cursor; +} + +static SDL_Cursor *WIN_CreateBlankCursor(void) +{ + SDL_Cursor *cursor = NULL; + SDL_Surface *surface = SDL_CreateSurface(32, 32, SDL_PIXELFORMAT_ARGB8888); + if (surface) { + cursor = WIN_CreateCursor(surface, 0, 0); + SDL_DestroySurface(surface); + } + return cursor; +} + +static SDL_Cursor *WIN_CreateSystemCursor(SDL_SystemCursor id) +{ + LPCTSTR name; + + switch (id) { + default: + SDL_assert(!"Unknown system cursor ID"); + return NULL; + case SDL_SYSTEM_CURSOR_DEFAULT: + name = IDC_ARROW; + break; + case SDL_SYSTEM_CURSOR_TEXT: + name = IDC_IBEAM; + break; + case SDL_SYSTEM_CURSOR_WAIT: + name = IDC_WAIT; + break; + case SDL_SYSTEM_CURSOR_CROSSHAIR: + name = IDC_CROSS; + break; + case SDL_SYSTEM_CURSOR_PROGRESS: + name = IDC_APPSTARTING; + break; + case SDL_SYSTEM_CURSOR_NWSE_RESIZE: + name = IDC_SIZENWSE; + break; + case SDL_SYSTEM_CURSOR_NESW_RESIZE: + name = IDC_SIZENESW; + break; + case SDL_SYSTEM_CURSOR_EW_RESIZE: + name = IDC_SIZEWE; + break; + case SDL_SYSTEM_CURSOR_NS_RESIZE: + name = IDC_SIZENS; + break; + case SDL_SYSTEM_CURSOR_MOVE: + name = IDC_SIZEALL; + break; + case SDL_SYSTEM_CURSOR_NOT_ALLOWED: + name = IDC_NO; + break; + case SDL_SYSTEM_CURSOR_POINTER: + name = IDC_HAND; + break; + case SDL_SYSTEM_CURSOR_NW_RESIZE: + name = IDC_SIZENWSE; + break; + case SDL_SYSTEM_CURSOR_N_RESIZE: + name = IDC_SIZENS; + break; + case SDL_SYSTEM_CURSOR_NE_RESIZE: + name = IDC_SIZENESW; + break; + case SDL_SYSTEM_CURSOR_E_RESIZE: + name = IDC_SIZEWE; + break; + case SDL_SYSTEM_CURSOR_SE_RESIZE: + name = IDC_SIZENWSE; + break; + case SDL_SYSTEM_CURSOR_S_RESIZE: + name = IDC_SIZENS; + break; + case SDL_SYSTEM_CURSOR_SW_RESIZE: + name = IDC_SIZENESW; + break; + case SDL_SYSTEM_CURSOR_W_RESIZE: + name = IDC_SIZEWE; + break; + } + return WIN_CreateCursorAndData(LoadCursor(NULL, name)); +} + +static SDL_Cursor *WIN_CreateDefaultCursor(void) +{ + SDL_SystemCursor id = SDL_GetDefaultSystemCursor(); + return WIN_CreateSystemCursor(id); +} + +static void WIN_FreeCursor(SDL_Cursor *cursor) +{ + SDL_CursorData *data = cursor->internal; + + if (data->surface) { + SDL_DestroySurface(data->surface); + } + while (data->cache) { + CachedCursor *entry = data->cache; + data->cache = entry->next; + if (entry->cursor) { + DestroyCursor(entry->cursor); + } + SDL_free(entry); + } + if (data->cursor) { + DestroyCursor(data->cursor); + } + SDL_free(data); + SDL_free(cursor); +} + +static HCURSOR GetCachedCursor(SDL_Cursor *cursor) +{ + SDL_CursorData *data = cursor->internal; + + SDL_Window *focus = SDL_GetMouseFocus(); + if (!focus) { + return NULL; + } + + float scale = SDL_GetDisplayContentScale(SDL_GetDisplayForWindow(focus)); + for (CachedCursor *entry = data->cache; entry; entry = entry->next) { + if (scale == entry->scale) { + return entry->cursor; + } + } + + // Need to create a cursor for this content scale + SDL_Surface *surface = NULL; + HCURSOR hcursor = NULL; + CachedCursor *entry = NULL; + + surface = SDL_GetSurfaceImage(data->surface, scale); + if (!surface) { + goto error; + } + + int hot_x = (int)SDL_round(data->hot_x * scale); + int hot_y = (int)SDL_round(data->hot_y * scale); + hcursor = WIN_CreateHCursor(surface, hot_x, hot_y); + if (!hcursor) { + goto error; + } + + entry = (CachedCursor *)SDL_malloc(sizeof(*entry)); + if (!entry) { + goto error; + } + entry->cursor = hcursor; + entry->scale = scale; + entry->next = data->cache; + data->cache = entry; + + SDL_DestroySurface(surface); + + return hcursor; + +error: + if (surface) { + SDL_DestroySurface(surface); + } + if (hcursor) { + DestroyCursor(hcursor); + } + SDL_free(entry); + return NULL; +} + +static bool WIN_ShowCursor(SDL_Cursor *cursor) +{ + if (!cursor) { + cursor = SDL_blank_cursor; + } + if (cursor) { + if (cursor->internal->surface) { + SDL_cursor = GetCachedCursor(cursor); + } else { + SDL_cursor = cursor->internal->cursor; + } + } else { + SDL_cursor = NULL; + } + if (SDL_GetMouseFocus() != NULL) { + SetCursor(SDL_cursor); + } + return true; +} + +void WIN_SetCursorPos(int x, int y) +{ + // We need to jitter the value because otherwise Windows will occasionally inexplicably ignore the SetCursorPos() or SendInput() + SetCursorPos(x, y); + SetCursorPos(x + 1, y); + SetCursorPos(x, y); + + // Flush any mouse motion prior to or associated with this warp +#ifdef _MSC_VER // We explicitly want to use GetTickCount(), not GetTickCount64() +#pragma warning(push) +#pragma warning(disable : 28159) +#endif + SDL_last_warp_time = GetTickCount(); + if (!SDL_last_warp_time) { + SDL_last_warp_time = 1; + } +#ifdef _MSC_VER +#pragma warning(pop) +#endif +} + +static bool WIN_WarpMouse(SDL_Window *window, float x, float y) +{ + SDL_WindowData *data = window->internal; + HWND hwnd = data->hwnd; + POINT pt; + + // Don't warp the mouse while we're doing a modal interaction + if (data->in_title_click || data->focus_click_pending) { + return true; + } + + pt.x = (int)SDL_roundf(x); + pt.y = (int)SDL_roundf(y); + ClientToScreen(hwnd, &pt); + WIN_SetCursorPos(pt.x, pt.y); + + // Send the exact mouse motion associated with this warp + SDL_SendMouseMotion(0, window, SDL_GLOBAL_MOUSE_ID, false, x, y); + return true; +} + +static bool WIN_WarpMouseGlobal(float x, float y) +{ + POINT pt; + + pt.x = (int)SDL_roundf(x); + pt.y = (int)SDL_roundf(y); + SetCursorPos(pt.x, pt.y); + return true; +} + +static bool WIN_SetRelativeMouseMode(bool enabled) +{ + return WIN_SetRawMouseEnabled(SDL_GetVideoDevice(), enabled); +} + +static bool WIN_CaptureMouse(SDL_Window *window) +{ + if (window) { + SDL_WindowData *data = window->internal; + SetCapture(data->hwnd); + } else { + SDL_Window *focus_window = SDL_GetMouseFocus(); + + if (focus_window) { + SDL_WindowData *data = focus_window->internal; + if (!data->mouse_tracked) { + SDL_SetMouseFocus(NULL); + } + } + ReleaseCapture(); + } + + return true; +} + +static SDL_MouseButtonFlags WIN_GetGlobalMouseState(float *x, float *y) +{ + SDL_MouseButtonFlags result = 0; + POINT pt = { 0, 0 }; + bool swapButtons = GetSystemMetrics(SM_SWAPBUTTON) != 0; + + GetCursorPos(&pt); + *x = (float)pt.x; + *y = (float)pt.y; + + result |= GetAsyncKeyState(!swapButtons ? VK_LBUTTON : VK_RBUTTON) & 0x8000 ? SDL_BUTTON_LMASK : 0; + result |= GetAsyncKeyState(!swapButtons ? VK_RBUTTON : VK_LBUTTON) & 0x8000 ? SDL_BUTTON_RMASK : 0; + result |= GetAsyncKeyState(VK_MBUTTON) & 0x8000 ? SDL_BUTTON_MMASK : 0; + result |= GetAsyncKeyState(VK_XBUTTON1) & 0x8000 ? SDL_BUTTON_X1MASK : 0; + result |= GetAsyncKeyState(VK_XBUTTON2) & 0x8000 ? SDL_BUTTON_X2MASK : 0; + + return result; +} + +static void WIN_ApplySystemScale(void *internal, Uint64 timestamp, SDL_Window *window, SDL_MouseID mouseID, float *x, float *y) +{ + if (!internal) { + return; + } + WIN_MouseData *data = (WIN_MouseData *)internal; + + SDL_VideoDisplay *display = window ? SDL_GetVideoDisplayForWindow(window) : SDL_GetVideoDisplay(SDL_GetPrimaryDisplay()); + + Sint64 ix = (Sint64)*x * 65536; + Sint64 iy = (Sint64)*y * 65536; + Uint32 dpi = display ? (Uint32)(display->content_scale * USER_DEFAULT_SCREEN_DPI) : USER_DEFAULT_SCREEN_DPI; + + if (!data->enhanced) { // early return if flat scale + dpi = data->dpiscale * (data->dpiaware ? dpi : USER_DEFAULT_SCREEN_DPI); + ix *= dpi; + iy *= dpi; + ix /= USER_DEFAULT_SCREEN_DPI; + iy /= USER_DEFAULT_SCREEN_DPI; + ix /= 32; + iy /= 32; + // data->residual[0] += ix; + // data->residual[1] += iy; + // ix = 65536 * (data->residual[0] / 65536); + // iy = 65536 * (data->residual[1] / 65536); + // data->residual[0] -= ix; + // data->residual[1] -= iy; + *x = (float)ix / 65536.0f; + *y = (float)iy / 65536.0f; + return; + } + + Uint64 *xs = data->xs; + Uint64 *ys = data->ys; + Uint64 absx = SDL_abs(ix); + Uint64 absy = SDL_abs(iy); + Uint64 speed = SDL_min(absx, absy) + (SDL_max(absx, absy) << 1); // super cursed approximation used by Windows + if (speed == 0) { + return; + } + + int i, j, k; + for (i = 1; i < 5; i++) { + j = i; + if (speed < xs[j]) { + break; + } + } + i -= 1; + j -= 1; + k = data->last_node; + data->last_node = j; + + Uint32 denom = data->dpidenom; + Sint64 scale = 0; + Sint64 xdiff = xs[j+1] - xs[j]; + Sint64 ydiff = ys[j+1] - ys[j]; + if (xdiff != 0) { + Sint64 slope = ydiff / xdiff; + Sint64 inter = slope * xs[i] - ys[i]; + scale += slope - inter / speed; + } + + if (j > k) { + denom <<= 1; + xdiff = xs[k+1] - xs[k]; + ydiff = ys[k+1] - ys[k]; + if (xdiff != 0) { + Sint64 slope = ydiff / xdiff; + Sint64 inter = slope * xs[k] - ys[k]; + scale += slope - inter / speed; + } + } + + scale *= dpi; + ix *= scale; + iy *= scale; + ix /= denom; + iy /= denom; + // data->residual[0] += ix; + // data->residual[1] += iy; + // ix = 65536 * (data->residual[0] / 65536); + // iy = 65536 * (data->residual[1] / 65536); + // data->residual[0] -= ix; + // data->residual[1] -= iy; + *x = (float)ix / 65536.0f; + *y = (float)iy / 65536.0f; +} + +void WIN_InitMouse(SDL_VideoDevice *_this) +{ + SDL_Mouse *mouse = SDL_GetMouse(); + + mouse->CreateCursor = WIN_CreateCursor; + mouse->CreateSystemCursor = WIN_CreateSystemCursor; + mouse->ShowCursor = WIN_ShowCursor; + mouse->FreeCursor = WIN_FreeCursor; + mouse->WarpMouse = WIN_WarpMouse; + mouse->WarpMouseGlobal = WIN_WarpMouseGlobal; + mouse->SetRelativeMouseMode = WIN_SetRelativeMouseMode; + mouse->CaptureMouse = WIN_CaptureMouse; + mouse->GetGlobalMouseState = WIN_GetGlobalMouseState; + mouse->ApplySystemScale = WIN_ApplySystemScale; + mouse->system_scale_data = &WIN_system_scale_data; + + SDL_SetDefaultCursor(WIN_CreateDefaultCursor()); + + SDL_blank_cursor = WIN_CreateBlankCursor(); + + WIN_UpdateMouseSystemScale(); +} + +void WIN_QuitMouse(SDL_VideoDevice *_this) +{ + if (SDL_blank_cursor) { + WIN_FreeCursor(SDL_blank_cursor); + SDL_blank_cursor = NULL; + } +} + +static void ReadMouseCurve(int v, Uint64 xs[5], Uint64 ys[5]) +{ + bool win8 = WIN_IsWindows8OrGreater(); + DWORD xbuff[10] = { + 0x00000000, 0, + 0x00006e15, 0, + 0x00014000, 0, + 0x0003dc29, 0, + 0x00280000, 0 + }; + DWORD ybuff[10] = { + 0x00000000, 0, + win8 ? 0x000111fd : 0x00015eb8, 0, + win8 ? 0x00042400 : 0x00054ccd, 0, + win8 ? 0x0012fc00 : 0x00184ccd, 0, + win8 ? 0x01bbc000 : 0x02380000, 0 + }; + DWORD xsize = sizeof(xbuff); + DWORD ysize = sizeof(ybuff); + HKEY open_handle; + if (RegOpenKeyExW(HKEY_CURRENT_USER, L"Control Panel\\Mouse", 0, KEY_READ, &open_handle) == ERROR_SUCCESS) { + RegQueryValueExW(open_handle, L"SmoothMouseXCurve", NULL, NULL, (BYTE*)xbuff, &xsize); + RegQueryValueExW(open_handle, L"SmoothMouseYCurve", NULL, NULL, (BYTE*)ybuff, &ysize); + RegCloseKey(open_handle); + } + xs[0] = 0; // first node must always be origin + ys[0] = 0; // first node must always be origin + int i; + for (i = 1; i < 5; i++) { + xs[i] = (7 * (Uint64)xbuff[i*2]); + ys[i] = (v * (Uint64)ybuff[i*2]) << 17; + } +} + +void WIN_UpdateMouseSystemScale(void) +{ + SDL_Mouse *mouse = SDL_GetMouse(); + + if (mouse->ApplySystemScale == WIN_ApplySystemScale) { + mouse->system_scale_data = &WIN_system_scale_data; + } + + // always reinitialize to valid defaults, whether fetch was successful or not. + WIN_MouseData *data = &WIN_system_scale_data; + data->residual[0] = 0; + data->residual[1] = 0; + data->dpiscale = 32; + data->dpidenom = (10 * (WIN_IsWindows8OrGreater() ? 120 : 150)) << 16; + data->dpiaware = WIN_IsPerMonitorV2DPIAware(SDL_GetVideoDevice()); + data->enhanced = false; + + int v = 10; + if (SystemParametersInfo(SPI_GETMOUSESPEED, 0, &v, 0)) { + v = SDL_max(1, SDL_min(v, 20)); + data->dpiscale = SDL_max(SDL_max(v, (v - 2) * 4), (v - 6) * 8); + } + + int params[3]; + if (SystemParametersInfo(SPI_GETMOUSE, 0, ¶ms, 0)) { + data->enhanced = params[2] ? true : false; + if (params[2]) { + ReadMouseCurve(v, data->xs, data->ys); + } + } +} + +#endif // SDL_VIDEO_DRIVER_WINDOWS diff --git a/contrib/SDL-3.2.8/src/video/windows/SDL_windowsmouse.h b/contrib/SDL-3.2.8/src/video/windows/SDL_windowsmouse.h new file mode 100644 index 0000000..8fa6d7e --- /dev/null +++ b/contrib/SDL-3.2.8/src/video/windows/SDL_windowsmouse.h @@ -0,0 +1,34 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include "SDL_internal.h" + +#ifndef SDL_windowsmouse_h_ +#define SDL_windowsmouse_h_ + +extern DWORD SDL_last_warp_time; +extern HCURSOR SDL_cursor; + +extern void WIN_InitMouse(SDL_VideoDevice *_this); +extern void WIN_QuitMouse(SDL_VideoDevice *_this); +extern void WIN_SetCursorPos(int x, int y); +extern void WIN_UpdateMouseSystemScale(void); + +#endif // SDL_windowsmouse_h_ diff --git a/contrib/SDL-3.2.8/src/video/windows/SDL_windowsopengl.c b/contrib/SDL-3.2.8/src/video/windows/SDL_windowsopengl.c new file mode 100644 index 0000000..c458796 --- /dev/null +++ b/contrib/SDL-3.2.8/src/video/windows/SDL_windowsopengl.c @@ -0,0 +1,906 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include "SDL_internal.h" + +#ifdef SDL_VIDEO_DRIVER_WINDOWS + +#include "SDL_windowsvideo.h" +#include "SDL_windowsopengles.h" + +// WGL implementation of SDL OpenGL support + +#ifdef SDL_VIDEO_OPENGL_WGL +#include + +#define DEFAULT_OPENGL "OPENGL32.DLL" + +#ifndef WGL_ARB_create_context +#define WGL_ARB_create_context +#define WGL_CONTEXT_MAJOR_VERSION_ARB 0x2091 +#define WGL_CONTEXT_MINOR_VERSION_ARB 0x2092 +#define WGL_CONTEXT_LAYER_PLANE_ARB 0x2093 +#define WGL_CONTEXT_FLAGS_ARB 0x2094 +#define WGL_CONTEXT_DEBUG_BIT_ARB 0x0001 +#define WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB 0x0002 + +#ifndef WGL_ARB_create_context_profile +#define WGL_ARB_create_context_profile +#define WGL_CONTEXT_PROFILE_MASK_ARB 0x9126 +#define WGL_CONTEXT_CORE_PROFILE_BIT_ARB 0x00000001 +#define WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB 0x00000002 +#endif + +#ifndef WGL_ARB_create_context_robustness +#define WGL_ARB_create_context_robustness +#define WGL_CONTEXT_ROBUST_ACCESS_BIT_ARB 0x00000004 +#define WGL_CONTEXT_RESET_NOTIFICATION_STRATEGY_ARB 0x8256 +#define WGL_NO_RESET_NOTIFICATION_ARB 0x8261 +#define WGL_LOSE_CONTEXT_ON_RESET_ARB 0x8252 +#endif +#endif + +#ifndef WGL_EXT_create_context_es2_profile +#define WGL_EXT_create_context_es2_profile +#define WGL_CONTEXT_ES2_PROFILE_BIT_EXT 0x00000004 +#endif + +#ifndef WGL_EXT_create_context_es_profile +#define WGL_EXT_create_context_es_profile +#define WGL_CONTEXT_ES_PROFILE_BIT_EXT 0x00000004 +#endif + +#ifndef WGL_ARB_framebuffer_sRGB +#define WGL_ARB_framebuffer_sRGB +#define WGL_FRAMEBUFFER_SRGB_CAPABLE_ARB 0x20A9 +#endif + +#ifndef WGL_ARB_pixel_format_float +#define WGL_ARB_pixel_format_float +#define WGL_TYPE_RGBA_FLOAT_ARB 0x21A0 +#endif + +#ifndef WGL_ARB_context_flush_control +#define WGL_ARB_context_flush_control +#define WGL_CONTEXT_RELEASE_BEHAVIOR_ARB 0x2097 +#define WGL_CONTEXT_RELEASE_BEHAVIOR_NONE_ARB 0x0000 +#define WGL_CONTEXT_RELEASE_BEHAVIOR_FLUSH_ARB 0x2098 +#endif + +#ifndef WGL_ARB_create_context_no_error +#define WGL_ARB_create_context_no_error +#define WGL_CONTEXT_OPENGL_NO_ERROR_ARB 0x31B3 +#endif + +typedef HGLRC(APIENTRYP PFNWGLCREATECONTEXTATTRIBSARBPROC)(HDC hDC, + HGLRC + hShareContext, + const int + *attribList); + +#if defined(SDL_PLATFORM_XBOXONE) || defined(SDL_PLATFORM_XBOXSERIES) +#define GetDC(hwnd) (HDC) hwnd +#define ReleaseDC(hwnd, hdc) 1 +#define SwapBuffers _this->gl_data->wglSwapBuffers +#define DescribePixelFormat _this->gl_data->wglDescribePixelFormat +#define ChoosePixelFormat _this->gl_data->wglChoosePixelFormat +#define GetPixelFormat _this->gl_data->wglGetPixelFormat +#define SetPixelFormat _this->gl_data->wglSetPixelFormat +#endif + +bool WIN_GL_LoadLibrary(SDL_VideoDevice *_this, const char *path) +{ + void *handle; + + if (path == NULL) { + path = SDL_GetHint(SDL_HINT_OPENGL_LIBRARY); + } + if (path == NULL) { + path = DEFAULT_OPENGL; + } + _this->gl_config.dll_handle = SDL_LoadObject(path); + if (!_this->gl_config.dll_handle) { + return false; + } + SDL_strlcpy(_this->gl_config.driver_path, path, + SDL_arraysize(_this->gl_config.driver_path)); + + // Allocate OpenGL memory + _this->gl_data = (struct SDL_GLDriverData *)SDL_calloc(1, sizeof(struct SDL_GLDriverData)); + if (!_this->gl_data) { + return false; + } + + // Load function pointers + handle = _this->gl_config.dll_handle; + /* *INDENT-OFF* */ // clang-format off + _this->gl_data->wglGetProcAddress = (PROC (WINAPI *)(const char *)) + SDL_LoadFunction(handle, "wglGetProcAddress"); + _this->gl_data->wglCreateContext = (HGLRC (WINAPI *)(HDC)) + SDL_LoadFunction(handle, "wglCreateContext"); + _this->gl_data->wglDeleteContext = (BOOL (WINAPI *)(HGLRC)) + SDL_LoadFunction(handle, "wglDeleteContext"); + _this->gl_data->wglMakeCurrent = (BOOL (WINAPI *)(HDC, HGLRC)) + SDL_LoadFunction(handle, "wglMakeCurrent"); + _this->gl_data->wglShareLists = (BOOL (WINAPI *)(HGLRC, HGLRC)) + SDL_LoadFunction(handle, "wglShareLists"); + /* *INDENT-ON* */ // clang-format on + +#if defined(SDL_PLATFORM_XBOXONE) || defined(SDL_PLATFORM_XBOXSERIES) + _this->gl_data->wglSwapBuffers = (BOOL(WINAPI *)(HDC)) + SDL_LoadFunction(handle, "wglSwapBuffers"); + _this->gl_data->wglDescribePixelFormat = (int(WINAPI *)(HDC, int, UINT, LPPIXELFORMATDESCRIPTOR)) + SDL_LoadFunction(handle, "wglDescribePixelFormat"); + _this->gl_data->wglChoosePixelFormat = (int(WINAPI *)(HDC, const PIXELFORMATDESCRIPTOR *)) + SDL_LoadFunction(handle, "wglChoosePixelFormat"); + _this->gl_data->wglSetPixelFormat = (BOOL(WINAPI *)(HDC, int, const PIXELFORMATDESCRIPTOR *)) + SDL_LoadFunction(handle, "wglSetPixelFormat"); + _this->gl_data->wglGetPixelFormat = (int(WINAPI *)(HDC hdc)) + SDL_LoadFunction(handle, "wglGetPixelFormat"); +#endif + + if (!_this->gl_data->wglGetProcAddress || + !_this->gl_data->wglCreateContext || + !_this->gl_data->wglDeleteContext || + !_this->gl_data->wglMakeCurrent +#if defined(SDL_PLATFORM_XBOXONE) || defined(SDL_PLATFORM_XBOXSERIES) + || !_this->gl_data->wglSwapBuffers || + !_this->gl_data->wglDescribePixelFormat || + !_this->gl_data->wglChoosePixelFormat || + !_this->gl_data->wglGetPixelFormat || + !_this->gl_data->wglSetPixelFormat +#endif + ) { + return SDL_SetError("Could not retrieve OpenGL functions"); + } + + /* XXX Too sleazy? WIN_GL_InitExtensions looks for certain OpenGL + extensions via SDL_GL_DeduceMaxSupportedESProfile. This uses + SDL_GL_ExtensionSupported which in turn calls SDL_GL_GetProcAddress. + However SDL_GL_GetProcAddress will fail if the library is not + loaded; it checks for gl_config.driver_loaded > 0. To avoid this + test failing, increment driver_loaded around the call to + WIN_GLInitExtensions. + + Successful loading of the library is normally indicated by + SDL_GL_LoadLibrary incrementing driver_loaded immediately after + this function returns 0 to it. + + Alternatives to this are: + - moving SDL_GL_DeduceMaxSupportedESProfile to both the WIN and + X11 platforms while adding a function equivalent to + SDL_GL_ExtensionSupported but which directly calls + glGetProcAddress(). Having 3 copies of the + SDL_GL_ExtensionSupported makes this alternative unattractive. + - moving SDL_GL_DeduceMaxSupportedESProfile to a new file shared + by the WIN and X11 platforms while adding a function equivalent + to SDL_GL_ExtensionSupported. This is unattractive due to the + number of project files that will need updating, plus there + will be 2 copies of the SDL_GL_ExtensionSupported code. + - Add a private equivalent of SDL_GL_ExtensionSupported to + SDL_video.c. + - Move the call to WIN_GL_InitExtensions back to WIN_CreateWindow + and add a flag to gl_data to avoid multiple calls to this + expensive function. This is probably the least objectionable + alternative if this increment/decrement trick is unacceptable. + + Note that the driver_loaded > 0 check needs to remain in + SDL_GL_ExtensionSupported and SDL_GL_GetProcAddress as they are + public API functions. + */ + ++_this->gl_config.driver_loaded; + WIN_GL_InitExtensions(_this); + --_this->gl_config.driver_loaded; + + return true; +} + +SDL_FunctionPointer WIN_GL_GetProcAddress(SDL_VideoDevice *_this, const char *proc) +{ + SDL_FunctionPointer func; + + // This is to pick up extensions + func = (SDL_FunctionPointer)_this->gl_data->wglGetProcAddress(proc); + if (!func) { + // This is probably a normal GL function + func = (SDL_FunctionPointer)GetProcAddress((HMODULE)_this->gl_config.dll_handle, proc); + } + return func; +} + +void WIN_GL_UnloadLibrary(SDL_VideoDevice *_this) +{ + SDL_UnloadObject(_this->gl_config.dll_handle); + _this->gl_config.dll_handle = NULL; + + // Free OpenGL memory + SDL_free(_this->gl_data); + _this->gl_data = NULL; +} + +static void WIN_GL_SetupPixelFormat(SDL_VideoDevice *_this, PIXELFORMATDESCRIPTOR *pfd) +{ + SDL_zerop(pfd); + pfd->nSize = sizeof(*pfd); + pfd->nVersion = 1; + pfd->dwFlags = (PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL); + if (_this->gl_config.double_buffer) { + pfd->dwFlags |= PFD_DOUBLEBUFFER; + } + if (_this->gl_config.stereo) { + pfd->dwFlags |= PFD_STEREO; + } + pfd->iLayerType = PFD_MAIN_PLANE; + pfd->iPixelType = PFD_TYPE_RGBA; + pfd->cRedBits = (BYTE)_this->gl_config.red_size; + pfd->cGreenBits = (BYTE)_this->gl_config.green_size; + pfd->cBlueBits = (BYTE)_this->gl_config.blue_size; + pfd->cAlphaBits = (BYTE)_this->gl_config.alpha_size; + if (_this->gl_config.buffer_size) { + pfd->cColorBits = (BYTE)(_this->gl_config.buffer_size - _this->gl_config.alpha_size); + } else { + pfd->cColorBits = (pfd->cRedBits + pfd->cGreenBits + pfd->cBlueBits); + } + pfd->cAccumRedBits = (BYTE)_this->gl_config.accum_red_size; + pfd->cAccumGreenBits = (BYTE)_this->gl_config.accum_green_size; + pfd->cAccumBlueBits = (BYTE)_this->gl_config.accum_blue_size; + pfd->cAccumAlphaBits = (BYTE)_this->gl_config.accum_alpha_size; + pfd->cAccumBits = + (pfd->cAccumRedBits + pfd->cAccumGreenBits + pfd->cAccumBlueBits + + pfd->cAccumAlphaBits); + pfd->cDepthBits = (BYTE)_this->gl_config.depth_size; + pfd->cStencilBits = (BYTE)_this->gl_config.stencil_size; +} + +/* Choose the closest pixel format that meets or exceeds the target. + FIXME: Should we weight any particular attribute over any other? +*/ +static bool WIN_GL_ChoosePixelFormat(SDL_VideoDevice *_this, HDC hdc, PIXELFORMATDESCRIPTOR *target) +{ + PIXELFORMATDESCRIPTOR pfd; + int count, index, best = 0; + unsigned int dist, best_dist = ~0U; + + count = DescribePixelFormat(hdc, 1, sizeof(pfd), NULL); + + for (index = 1; index <= count; index++) { + + if (!DescribePixelFormat(hdc, index, sizeof(pfd), &pfd)) { + continue; + } + + if ((pfd.dwFlags & target->dwFlags) != target->dwFlags) { + continue; + } + + if (pfd.iLayerType != target->iLayerType) { + continue; + } + if (pfd.iPixelType != target->iPixelType) { + continue; + } + + dist = 0; + + if (pfd.cColorBits < target->cColorBits) { + continue; + } else { + dist += (pfd.cColorBits - target->cColorBits); + } + if (pfd.cRedBits < target->cRedBits) { + continue; + } else { + dist += (pfd.cRedBits - target->cRedBits); + } + if (pfd.cGreenBits < target->cGreenBits) { + continue; + } else { + dist += (pfd.cGreenBits - target->cGreenBits); + } + if (pfd.cBlueBits < target->cBlueBits) { + continue; + } else { + dist += (pfd.cBlueBits - target->cBlueBits); + } + if (pfd.cAlphaBits < target->cAlphaBits) { + continue; + } else { + dist += (pfd.cAlphaBits - target->cAlphaBits); + } + if (pfd.cAccumBits < target->cAccumBits) { + continue; + } else { + dist += (pfd.cAccumBits - target->cAccumBits); + } + if (pfd.cAccumRedBits < target->cAccumRedBits) { + continue; + } else { + dist += (pfd.cAccumRedBits - target->cAccumRedBits); + } + if (pfd.cAccumGreenBits < target->cAccumGreenBits) { + continue; + } else { + dist += (pfd.cAccumGreenBits - target->cAccumGreenBits); + } + if (pfd.cAccumBlueBits < target->cAccumBlueBits) { + continue; + } else { + dist += (pfd.cAccumBlueBits - target->cAccumBlueBits); + } + if (pfd.cAccumAlphaBits < target->cAccumAlphaBits) { + continue; + } else { + dist += (pfd.cAccumAlphaBits - target->cAccumAlphaBits); + } + if (pfd.cDepthBits < target->cDepthBits) { + continue; + } else { + dist += (pfd.cDepthBits - target->cDepthBits); + } + if (pfd.cStencilBits < target->cStencilBits) { + continue; + } else { + dist += (pfd.cStencilBits - target->cStencilBits); + } + + if (dist < best_dist) { + best = index; + best_dist = dist; + } + } + + return best; +} + +static bool HasExtension(const char *extension, const char *extensions) +{ + const char *start; + const char *where, *terminator; + + // Extension names should not have spaces. + where = SDL_strchr(extension, ' '); + if (where || *extension == '\0') { + return false; + } + + if (!extensions) { + return false; + } + + /* It takes a bit of care to be fool-proof about parsing the + * OpenGL extensions string. Don't be fooled by sub-strings, + * etc. */ + + start = extensions; + + for (;;) { + where = SDL_strstr(start, extension); + if (!where) { + break; + } + + terminator = where + SDL_strlen(extension); + if (where == start || *(where - 1) == ' ') { + if (*terminator == ' ' || *terminator == '\0') { + return true; + } + } + + start = terminator; + } + return false; +} + +void WIN_GL_InitExtensions(SDL_VideoDevice *_this) +{ + /* *INDENT-OFF* */ // clang-format off + const char *(WINAPI * wglGetExtensionsStringARB)(HDC) = 0; + /* *INDENT-ON* */ // clang-format on + const char *extensions; + HWND hwnd; + HDC hdc; + HGLRC hglrc; + PIXELFORMATDESCRIPTOR pfd; + + if (!_this->gl_data) { + return; + } + + hwnd = + CreateWindow(SDL_Appname, SDL_Appname, (WS_POPUP | WS_DISABLED), 0, 0, + 10, 10, NULL, NULL, SDL_Instance, NULL); + if (!hwnd) { + return; + } + WIN_PumpEvents(_this); + + hdc = GetDC(hwnd); + + WIN_GL_SetupPixelFormat(_this, &pfd); + + SetPixelFormat(hdc, ChoosePixelFormat(hdc, &pfd), &pfd); + + hglrc = _this->gl_data->wglCreateContext(hdc); + if (!hglrc) { + return; + } + _this->gl_data->wglMakeCurrent(hdc, hglrc); + + /* *INDENT-OFF* */ // clang-format off + wglGetExtensionsStringARB = (const char *(WINAPI *)(HDC)) + _this->gl_data->wglGetProcAddress("wglGetExtensionsStringARB"); + /* *INDENT-ON* */ // clang-format on + if (wglGetExtensionsStringARB) { + extensions = wglGetExtensionsStringARB(hdc); + } else { + extensions = NULL; + } + + // Check for WGL_ARB_pixel_format + _this->gl_data->HAS_WGL_ARB_pixel_format = false; + if (HasExtension("WGL_ARB_pixel_format", extensions)) { + /* *INDENT-OFF* */ // clang-format off + _this->gl_data->wglChoosePixelFormatARB = + (BOOL (WINAPI *)(HDC, const int *, const FLOAT *, UINT, int *, UINT *)) + WIN_GL_GetProcAddress(_this, "wglChoosePixelFormatARB"); + _this->gl_data->wglGetPixelFormatAttribivARB = + (BOOL (WINAPI *)(HDC, int, int, UINT, const int *, int *)) + WIN_GL_GetProcAddress(_this, "wglGetPixelFormatAttribivARB"); + /* *INDENT-ON* */ // clang-format on + + if ((_this->gl_data->wglChoosePixelFormatARB != NULL) && + (_this->gl_data->wglGetPixelFormatAttribivARB != NULL)) { + _this->gl_data->HAS_WGL_ARB_pixel_format = true; + } + } + + // Check for WGL_EXT_swap_control + _this->gl_data->HAS_WGL_EXT_swap_control_tear = false; + if (HasExtension("WGL_EXT_swap_control", extensions)) { + _this->gl_data->wglSwapIntervalEXT = + (BOOL (WINAPI *)(int)) + WIN_GL_GetProcAddress(_this, "wglSwapIntervalEXT"); + _this->gl_data->wglGetSwapIntervalEXT = + (int (WINAPI *)(void)) + WIN_GL_GetProcAddress(_this, "wglGetSwapIntervalEXT"); + if (HasExtension("WGL_EXT_swap_control_tear", extensions)) { + _this->gl_data->HAS_WGL_EXT_swap_control_tear = true; + } + } else { + _this->gl_data->wglSwapIntervalEXT = NULL; + _this->gl_data->wglGetSwapIntervalEXT = NULL; + } + + // Check for WGL_EXT_create_context_es2_profile + if (HasExtension("WGL_EXT_create_context_es2_profile", extensions)) { + SDL_GL_DeduceMaxSupportedESProfile( + &_this->gl_data->es_profile_max_supported_version.major, + &_this->gl_data->es_profile_max_supported_version.minor); + } + + // Check for WGL_ARB_context_flush_control + if (HasExtension("WGL_ARB_context_flush_control", extensions)) { + _this->gl_data->HAS_WGL_ARB_context_flush_control = true; + } + + // Check for WGL_ARB_create_context_robustness + if (HasExtension("WGL_ARB_create_context_robustness", extensions)) { + _this->gl_data->HAS_WGL_ARB_create_context_robustness = true; + } + + // Check for WGL_ARB_create_context_no_error + if (HasExtension("WGL_ARB_create_context_no_error", extensions)) { + _this->gl_data->HAS_WGL_ARB_create_context_no_error = true; + } + + _this->gl_data->wglMakeCurrent(hdc, NULL); + _this->gl_data->wglDeleteContext(hglrc); + ReleaseDC(hwnd, hdc); + DestroyWindow(hwnd); + WIN_PumpEvents(_this); +} + +static int WIN_GL_ChoosePixelFormatARB(SDL_VideoDevice *_this, int *iAttribs, float *fAttribs) +{ + HWND hwnd; + HDC hdc; + PIXELFORMATDESCRIPTOR pfd; + HGLRC hglrc; + int pixel_format = 0; + unsigned int matching; + + int qAttrib = WGL_FRAMEBUFFER_SRGB_CAPABLE_ARB; + int srgb = 0; + + hwnd = + CreateWindow(SDL_Appname, SDL_Appname, (WS_POPUP | WS_DISABLED), 0, 0, + 10, 10, NULL, NULL, SDL_Instance, NULL); + WIN_PumpEvents(_this); + + hdc = GetDC(hwnd); + + WIN_GL_SetupPixelFormat(_this, &pfd); + + SetPixelFormat(hdc, ChoosePixelFormat(hdc, &pfd), &pfd); + + hglrc = _this->gl_data->wglCreateContext(hdc); + if (hglrc) { + _this->gl_data->wglMakeCurrent(hdc, hglrc); + + if (_this->gl_data->HAS_WGL_ARB_pixel_format) { + _this->gl_data->wglChoosePixelFormatARB(hdc, iAttribs, fAttribs, + 1, &pixel_format, + &matching); + + // Check whether we actually got an SRGB capable buffer + _this->gl_data->wglGetPixelFormatAttribivARB(hdc, pixel_format, 0, 1, &qAttrib, &srgb); + _this->gl_config.framebuffer_srgb_capable = srgb; + } + + _this->gl_data->wglMakeCurrent(hdc, NULL); + _this->gl_data->wglDeleteContext(hglrc); + } + ReleaseDC(hwnd, hdc); + DestroyWindow(hwnd); + WIN_PumpEvents(_this); + + return pixel_format; +} + +// actual work of WIN_GL_SetupWindow() happens here. +static bool WIN_GL_SetupWindowInternal(SDL_VideoDevice *_this, SDL_Window *window) +{ + HDC hdc = window->internal->hdc; + PIXELFORMATDESCRIPTOR pfd; + int pixel_format = 0; + int iAttribs[64]; + int *iAttr; + int *iAccelAttr; + float fAttribs[1] = { 0 }; + + WIN_GL_SetupPixelFormat(_this, &pfd); + + // setup WGL_ARB_pixel_format attribs + iAttr = &iAttribs[0]; + + *iAttr++ = WGL_DRAW_TO_WINDOW_ARB; + *iAttr++ = GL_TRUE; + *iAttr++ = WGL_RED_BITS_ARB; + *iAttr++ = _this->gl_config.red_size; + *iAttr++ = WGL_GREEN_BITS_ARB; + *iAttr++ = _this->gl_config.green_size; + *iAttr++ = WGL_BLUE_BITS_ARB; + *iAttr++ = _this->gl_config.blue_size; + + if (_this->gl_config.alpha_size) { + *iAttr++ = WGL_ALPHA_BITS_ARB; + *iAttr++ = _this->gl_config.alpha_size; + } + + *iAttr++ = WGL_DOUBLE_BUFFER_ARB; + *iAttr++ = _this->gl_config.double_buffer; + + *iAttr++ = WGL_DEPTH_BITS_ARB; + *iAttr++ = _this->gl_config.depth_size; + + if (_this->gl_config.stencil_size) { + *iAttr++ = WGL_STENCIL_BITS_ARB; + *iAttr++ = _this->gl_config.stencil_size; + } + + if (_this->gl_config.accum_red_size) { + *iAttr++ = WGL_ACCUM_RED_BITS_ARB; + *iAttr++ = _this->gl_config.accum_red_size; + } + + if (_this->gl_config.accum_green_size) { + *iAttr++ = WGL_ACCUM_GREEN_BITS_ARB; + *iAttr++ = _this->gl_config.accum_green_size; + } + + if (_this->gl_config.accum_blue_size) { + *iAttr++ = WGL_ACCUM_BLUE_BITS_ARB; + *iAttr++ = _this->gl_config.accum_blue_size; + } + + if (_this->gl_config.accum_alpha_size) { + *iAttr++ = WGL_ACCUM_ALPHA_BITS_ARB; + *iAttr++ = _this->gl_config.accum_alpha_size; + } + + if (_this->gl_config.stereo) { + *iAttr++ = WGL_STEREO_ARB; + *iAttr++ = GL_TRUE; + } + + if (_this->gl_config.multisamplebuffers) { + *iAttr++ = WGL_SAMPLE_BUFFERS_ARB; + *iAttr++ = _this->gl_config.multisamplebuffers; + } + + if (_this->gl_config.multisamplesamples) { + *iAttr++ = WGL_SAMPLES_ARB; + *iAttr++ = _this->gl_config.multisamplesamples; + } + + if (_this->gl_config.floatbuffers) { + *iAttr++ = WGL_PIXEL_TYPE_ARB; + *iAttr++ = WGL_TYPE_RGBA_FLOAT_ARB; + } + + if (_this->gl_config.framebuffer_srgb_capable) { + *iAttr++ = WGL_FRAMEBUFFER_SRGB_CAPABLE_ARB; + *iAttr++ = _this->gl_config.framebuffer_srgb_capable; + } + + /* We always choose either FULL or NO accel on Windows, because of flaky + drivers. If the app didn't specify, we use FULL, because that's + probably what they wanted (and if you didn't care and got FULL, that's + a perfectly valid result in any case). */ + *iAttr++ = WGL_ACCELERATION_ARB; + iAccelAttr = iAttr; + if (_this->gl_config.accelerated) { + *iAttr++ = WGL_FULL_ACCELERATION_ARB; + } else { + *iAttr++ = WGL_NO_ACCELERATION_ARB; + } + + *iAttr = 0; + + // Choose and set the closest available pixel format + pixel_format = WIN_GL_ChoosePixelFormatARB(_this, iAttribs, fAttribs); + + // App said "don't care about accel" and FULL accel failed. Try NO. + if ((!pixel_format) && (_this->gl_config.accelerated < 0)) { + *iAccelAttr = WGL_NO_ACCELERATION_ARB; + pixel_format = WIN_GL_ChoosePixelFormatARB(_this, iAttribs, fAttribs); + *iAccelAttr = WGL_FULL_ACCELERATION_ARB; // if we try again. + } + if (!pixel_format) { + pixel_format = WIN_GL_ChoosePixelFormat(_this, hdc, &pfd); + } + if (!pixel_format) { + return SDL_SetError("No matching GL pixel format available"); + } + if (!SetPixelFormat(hdc, pixel_format, &pfd)) { + return WIN_SetError("SetPixelFormat()"); + } + return true; +} + +bool WIN_GL_SetupWindow(SDL_VideoDevice *_this, SDL_Window *window) +{ + // The current context is lost in here; save it and reset it. + SDL_Window *current_win = SDL_GL_GetCurrentWindow(); + SDL_GLContext current_ctx = SDL_GL_GetCurrentContext(); + const int result = WIN_GL_SetupWindowInternal(_this, window); + WIN_GL_MakeCurrent(_this, current_win, current_ctx); + return result; +} + +bool WIN_GL_UseEGL(SDL_VideoDevice *_this) +{ + SDL_assert(_this->gl_data != NULL); + SDL_assert(_this->gl_config.profile_mask == SDL_GL_CONTEXT_PROFILE_ES); + + return SDL_GetHintBoolean(SDL_HINT_OPENGL_ES_DRIVER, false) || _this->gl_config.major_version == 1 || _this->gl_config.major_version > _this->gl_data->es_profile_max_supported_version.major || (_this->gl_config.major_version == _this->gl_data->es_profile_max_supported_version.major && _this->gl_config.minor_version > _this->gl_data->es_profile_max_supported_version.minor); // No WGL extension for OpenGL ES 1.x profiles. +} + +SDL_GLContext WIN_GL_CreateContext(SDL_VideoDevice *_this, SDL_Window *window) +{ + HDC hdc = window->internal->hdc; + HGLRC context, share_context; + + if (_this->gl_config.profile_mask == SDL_GL_CONTEXT_PROFILE_ES && WIN_GL_UseEGL(_this)) { +#ifdef SDL_VIDEO_OPENGL_EGL + // Switch to EGL based functions + WIN_GL_UnloadLibrary(_this); + _this->GL_LoadLibrary = WIN_GLES_LoadLibrary; + _this->GL_GetProcAddress = WIN_GLES_GetProcAddress; + _this->GL_UnloadLibrary = WIN_GLES_UnloadLibrary; + _this->GL_CreateContext = WIN_GLES_CreateContext; + _this->GL_MakeCurrent = WIN_GLES_MakeCurrent; + _this->GL_SetSwapInterval = WIN_GLES_SetSwapInterval; + _this->GL_GetSwapInterval = WIN_GLES_GetSwapInterval; + _this->GL_SwapWindow = WIN_GLES_SwapWindow; + _this->GL_DestroyContext = WIN_GLES_DestroyContext; + _this->GL_GetEGLSurface = WIN_GLES_GetEGLSurface; + + if (!WIN_GLES_LoadLibrary(_this, NULL)) { + return NULL; + } + + return WIN_GLES_CreateContext(_this, window); +#else + SDL_SetError("SDL not configured with EGL support"); + return NULL; +#endif + } + + if (_this->gl_config.share_with_current_context) { + share_context = (HGLRC)SDL_GL_GetCurrentContext(); + } else { + share_context = 0; + } + + if (_this->gl_config.major_version < 3 && + _this->gl_config.profile_mask == 0 && + _this->gl_config.flags == 0) { + // Create legacy context + context = _this->gl_data->wglCreateContext(hdc); + if (share_context != 0) { + _this->gl_data->wglShareLists(share_context, context); + } + } else { + PFNWGLCREATECONTEXTATTRIBSARBPROC wglCreateContextAttribsARB; + HGLRC temp_context = _this->gl_data->wglCreateContext(hdc); + if (!temp_context) { + SDL_SetError("Could not create GL context"); + return NULL; + } + + // Make the context current + if (!WIN_GL_MakeCurrent(_this, window, (SDL_GLContext)temp_context)) { + WIN_GL_DestroyContext(_this, (SDL_GLContext)temp_context); + return NULL; + } + + wglCreateContextAttribsARB = + (PFNWGLCREATECONTEXTATTRIBSARBPROC)_this->gl_data->wglGetProcAddress("wglCreateContextAttribsARB"); + if (!wglCreateContextAttribsARB) { + SDL_SetError("GL 3.x is not supported"); + context = temp_context; + } else { + int attribs[15]; // max 14 attributes plus terminator + int iattr = 0; + + attribs[iattr++] = WGL_CONTEXT_MAJOR_VERSION_ARB; + attribs[iattr++] = _this->gl_config.major_version; + attribs[iattr++] = WGL_CONTEXT_MINOR_VERSION_ARB; + attribs[iattr++] = _this->gl_config.minor_version; + + // SDL profile bits match WGL profile bits + if (_this->gl_config.profile_mask != 0) { + attribs[iattr++] = WGL_CONTEXT_PROFILE_MASK_ARB; + attribs[iattr++] = _this->gl_config.profile_mask; + } + + // SDL flags match WGL flags + if (_this->gl_config.flags != 0) { + attribs[iattr++] = WGL_CONTEXT_FLAGS_ARB; + attribs[iattr++] = _this->gl_config.flags; + } + + // only set if wgl extension is available and not the default setting + if ((_this->gl_data->HAS_WGL_ARB_context_flush_control) && (_this->gl_config.release_behavior == 0)) { + attribs[iattr++] = WGL_CONTEXT_RELEASE_BEHAVIOR_ARB; + attribs[iattr++] = _this->gl_config.release_behavior ? WGL_CONTEXT_RELEASE_BEHAVIOR_FLUSH_ARB : WGL_CONTEXT_RELEASE_BEHAVIOR_NONE_ARB; + } + + // only set if wgl extension is available and not the default setting + if ((_this->gl_data->HAS_WGL_ARB_create_context_robustness) && (_this->gl_config.reset_notification != 0)) { + attribs[iattr++] = WGL_CONTEXT_RESET_NOTIFICATION_STRATEGY_ARB; + attribs[iattr++] = _this->gl_config.reset_notification ? WGL_LOSE_CONTEXT_ON_RESET_ARB : WGL_NO_RESET_NOTIFICATION_ARB; + } + + // only set if wgl extension is available and not the default setting + if ((_this->gl_data->HAS_WGL_ARB_create_context_no_error) && (_this->gl_config.no_error != 0)) { + attribs[iattr++] = WGL_CONTEXT_OPENGL_NO_ERROR_ARB; + attribs[iattr++] = _this->gl_config.no_error; + } + + attribs[iattr++] = 0; + + // Create the GL 3.x context + context = wglCreateContextAttribsARB(hdc, share_context, attribs); + // Delete the GL 2.x context + _this->gl_data->wglDeleteContext(temp_context); + } + } + + if (!context) { + WIN_SetError("Could not create GL context"); + return NULL; + } + + if (!WIN_GL_MakeCurrent(_this, window, (SDL_GLContext)context)) { + WIN_GL_DestroyContext(_this, (SDL_GLContext)context); + return NULL; + } + + return (SDL_GLContext)context; +} + +bool WIN_GL_MakeCurrent(SDL_VideoDevice *_this, SDL_Window *window, SDL_GLContext context) +{ + HDC hdc; + + if (!_this->gl_data) { + return SDL_SetError("OpenGL not initialized"); + } + + // sanity check that higher level handled this. + SDL_assert(window || (window == NULL && !context)); + + /* Some Windows drivers freak out if hdc is NULL, even when context is + NULL, against spec. Since hdc is _supposed_ to be ignored if context + is NULL, we either use the current GL window, or do nothing if we + already have no current context. */ + if (!window) { + window = SDL_GL_GetCurrentWindow(); + if (!window) { + SDL_assert(SDL_GL_GetCurrentContext() == NULL); + return true; // already done. + } + } + + hdc = window->internal->hdc; + if (!_this->gl_data->wglMakeCurrent(hdc, (HGLRC)context)) { + return WIN_SetError("wglMakeCurrent()"); + } + return true; +} + +bool WIN_GL_SetSwapInterval(SDL_VideoDevice *_this, int interval) +{ + if ((interval < 0) && (!_this->gl_data->HAS_WGL_EXT_swap_control_tear)) { + return SDL_SetError("Negative swap interval unsupported in this GL"); + } else if (_this->gl_data->wglSwapIntervalEXT) { + if (!_this->gl_data->wglSwapIntervalEXT(interval)) { + return WIN_SetError("wglSwapIntervalEXT()"); + } + } else { + return SDL_Unsupported(); + } + return true; +} + +bool WIN_GL_GetSwapInterval(SDL_VideoDevice *_this, int *interval) +{ + if (_this->gl_data->wglGetSwapIntervalEXT) { + *interval = _this->gl_data->wglGetSwapIntervalEXT(); + return true; + } else { + return false; + } +} + +bool WIN_GL_SwapWindow(SDL_VideoDevice *_this, SDL_Window *window) +{ + HDC hdc = window->internal->hdc; + + if (!SwapBuffers(hdc)) { + return WIN_SetError("SwapBuffers()"); + } + return true; +} + +bool WIN_GL_DestroyContext(SDL_VideoDevice *_this, SDL_GLContext context) +{ + if (!_this->gl_data) { + return true; + } + _this->gl_data->wglDeleteContext((HGLRC)context); + return true; +} + +#endif // SDL_VIDEO_OPENGL_WGL + +#endif // SDL_VIDEO_DRIVER_WINDOWS diff --git a/contrib/SDL-3.2.8/src/video/windows/SDL_windowsopengl.h b/contrib/SDL-3.2.8/src/video/windows/SDL_windowsopengl.h new file mode 100644 index 0000000..23e2f3a --- /dev/null +++ b/contrib/SDL-3.2.8/src/video/windows/SDL_windowsopengl.h @@ -0,0 +1,178 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include "SDL_internal.h" + +#ifndef SDL_windowsopengl_h_ +#define SDL_windowsopengl_h_ + +#ifdef SDL_VIDEO_OPENGL_WGL + +#if defined(SDL_PLATFORM_XBOXONE) || defined(SDL_PLATFORM_XBOXSERIES) +typedef struct tagPIXELFORMATDESCRIPTOR +{ + WORD nSize; + WORD nVersion; + DWORD dwFlags; + BYTE iPixelType; + BYTE cColorBits; + BYTE cRedBits; + BYTE cRedShift; + BYTE cGreenBits; + BYTE cGreenShift; + BYTE cBlueBits; + BYTE cBlueShift; + BYTE cAlphaBits; + BYTE cAlphaShift; + BYTE cAccumBits; + BYTE cAccumRedBits; + BYTE cAccumGreenBits; + BYTE cAccumBlueBits; + BYTE cAccumAlphaBits; + BYTE cDepthBits; + BYTE cStencilBits; + BYTE cAuxBuffers; + BYTE iLayerType; + BYTE bReserved; + DWORD dwLayerMask; + DWORD dwVisibleMask; + DWORD dwDamageMask; +} PIXELFORMATDESCRIPTOR, *PPIXELFORMATDESCRIPTOR, *LPPIXELFORMATDESCRIPTOR; +#endif + +struct SDL_GLDriverData +{ + bool HAS_WGL_ARB_pixel_format; + bool HAS_WGL_EXT_swap_control_tear; + bool HAS_WGL_ARB_context_flush_control; + bool HAS_WGL_ARB_create_context_robustness; + bool HAS_WGL_ARB_create_context_no_error; + + /* Max version of OpenGL ES context that can be created if the + implementation supports WGL_EXT_create_context_es2_profile. + major = minor = 0 when unsupported. + */ + struct + { + int major; + int minor; + } es_profile_max_supported_version; + + /* *INDENT-OFF* */ // clang-format off + PROC (WINAPI *wglGetProcAddress)(const char *proc); + HGLRC (WINAPI *wglCreateContext)(HDC hdc); + BOOL (WINAPI *wglDeleteContext)(HGLRC hglrc); + BOOL (WINAPI *wglMakeCurrent)(HDC hdc, HGLRC hglrc); + BOOL (WINAPI *wglShareLists)(HGLRC hglrc1, HGLRC hglrc2); + BOOL (WINAPI *wglChoosePixelFormatARB)(HDC hdc, const int *piAttribIList, const FLOAT * pfAttribFList, UINT nMaxFormats, int *piFormats, UINT * nNumFormats); + BOOL (WINAPI *wglGetPixelFormatAttribivARB)(HDC hdc, int iPixelFormat, int iLayerPlane, UINT nAttributes, const int *piAttributes, int *piValues); + BOOL (WINAPI *wglSwapIntervalEXT)(int interval); + int (WINAPI *wglGetSwapIntervalEXT)(void); +#if defined(SDL_PLATFORM_XBOXONE) || defined(SDL_PLATFORM_XBOXSERIES) + BOOL (WINAPI *wglSwapBuffers)(HDC hdc); + int (WINAPI *wglDescribePixelFormat)(HDC hdc, + int iPixelFormat, + UINT nBytes, + LPPIXELFORMATDESCRIPTOR ppfd); + int (WINAPI *wglChoosePixelFormat)(HDC hdc, + const PIXELFORMATDESCRIPTOR *ppfd); + BOOL (WINAPI *wglSetPixelFormat)(HDC hdc, + int format, + const PIXELFORMATDESCRIPTOR *ppfd); + int (WINAPI *wglGetPixelFormat)(HDC hdc); +#endif + /* *INDENT-ON* */ // clang-format on +}; + +// OpenGL functions +extern bool WIN_GL_LoadLibrary(SDL_VideoDevice *_this, const char *path); +extern SDL_FunctionPointer WIN_GL_GetProcAddress(SDL_VideoDevice *_this, const char *proc); +extern void WIN_GL_UnloadLibrary(SDL_VideoDevice *_this); +extern bool WIN_GL_UseEGL(SDL_VideoDevice *_this); +extern bool WIN_GL_SetupWindow(SDL_VideoDevice *_this, SDL_Window *window); +extern SDL_GLContext WIN_GL_CreateContext(SDL_VideoDevice *_this, SDL_Window *window); +extern bool WIN_GL_MakeCurrent(SDL_VideoDevice *_this, SDL_Window *window, + SDL_GLContext context); +extern bool WIN_GL_SetSwapInterval(SDL_VideoDevice *_this, int interval); +extern bool WIN_GL_GetSwapInterval(SDL_VideoDevice *_this, int *interval); +extern bool WIN_GL_SwapWindow(SDL_VideoDevice *_this, SDL_Window *window); +extern bool WIN_GL_DestroyContext(SDL_VideoDevice *_this, SDL_GLContext context); +extern void WIN_GL_InitExtensions(SDL_VideoDevice *_this); + +#ifndef WGL_ARB_pixel_format +#define WGL_NUMBER_PIXEL_FORMATS_ARB 0x2000 +#define WGL_DRAW_TO_WINDOW_ARB 0x2001 +#define WGL_DRAW_TO_BITMAP_ARB 0x2002 +#define WGL_ACCELERATION_ARB 0x2003 +#define WGL_NEED_PALETTE_ARB 0x2004 +#define WGL_NEED_SYSTEM_PALETTE_ARB 0x2005 +#define WGL_SWAP_LAYER_BUFFERS_ARB 0x2006 +#define WGL_SWAP_METHOD_ARB 0x2007 +#define WGL_NUMBER_OVERLAYS_ARB 0x2008 +#define WGL_NUMBER_UNDERLAYS_ARB 0x2009 +#define WGL_TRANSPARENT_ARB 0x200A +#define WGL_TRANSPARENT_RED_VALUE_ARB 0x2037 +#define WGL_TRANSPARENT_GREEN_VALUE_ARB 0x2038 +#define WGL_TRANSPARENT_BLUE_VALUE_ARB 0x2039 +#define WGL_TRANSPARENT_ALPHA_VALUE_ARB 0x203A +#define WGL_TRANSPARENT_INDEX_VALUE_ARB 0x203B +#define WGL_SHARE_DEPTH_ARB 0x200C +#define WGL_SHARE_STENCIL_ARB 0x200D +#define WGL_SHARE_ACCUM_ARB 0x200E +#define WGL_SUPPORT_GDI_ARB 0x200F +#define WGL_SUPPORT_OPENGL_ARB 0x2010 +#define WGL_DOUBLE_BUFFER_ARB 0x2011 +#define WGL_STEREO_ARB 0x2012 +#define WGL_PIXEL_TYPE_ARB 0x2013 +#define WGL_COLOR_BITS_ARB 0x2014 +#define WGL_RED_BITS_ARB 0x2015 +#define WGL_RED_SHIFT_ARB 0x2016 +#define WGL_GREEN_BITS_ARB 0x2017 +#define WGL_GREEN_SHIFT_ARB 0x2018 +#define WGL_BLUE_BITS_ARB 0x2019 +#define WGL_BLUE_SHIFT_ARB 0x201A +#define WGL_ALPHA_BITS_ARB 0x201B +#define WGL_ALPHA_SHIFT_ARB 0x201C +#define WGL_ACCUM_BITS_ARB 0x201D +#define WGL_ACCUM_RED_BITS_ARB 0x201E +#define WGL_ACCUM_GREEN_BITS_ARB 0x201F +#define WGL_ACCUM_BLUE_BITS_ARB 0x2020 +#define WGL_ACCUM_ALPHA_BITS_ARB 0x2021 +#define WGL_DEPTH_BITS_ARB 0x2022 +#define WGL_STENCIL_BITS_ARB 0x2023 +#define WGL_AUX_BUFFERS_ARB 0x2024 +#define WGL_NO_ACCELERATION_ARB 0x2025 +#define WGL_GENERIC_ACCELERATION_ARB 0x2026 +#define WGL_FULL_ACCELERATION_ARB 0x2027 +#define WGL_SWAP_EXCHANGE_ARB 0x2028 +#define WGL_SWAP_COPY_ARB 0x2029 +#define WGL_SWAP_UNDEFINED_ARB 0x202A +#define WGL_TYPE_RGBA_ARB 0x202B +#define WGL_TYPE_COLORINDEX_ARB 0x202C +#endif + +#ifndef WGL_ARB_multisample +#define WGL_SAMPLE_BUFFERS_ARB 0x2041 +#define WGL_SAMPLES_ARB 0x2042 +#endif + +#endif // SDL_VIDEO_OPENGL_WGL + +#endif // SDL_windowsopengl_h_ diff --git a/contrib/SDL-3.2.8/src/video/windows/SDL_windowsopengles.c b/contrib/SDL-3.2.8/src/video/windows/SDL_windowsopengles.c new file mode 100644 index 0000000..46f8ea3 --- /dev/null +++ b/contrib/SDL-3.2.8/src/video/windows/SDL_windowsopengles.c @@ -0,0 +1,142 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include "SDL_internal.h" + +#if defined(SDL_VIDEO_DRIVER_WINDOWS) && defined(SDL_VIDEO_OPENGL_EGL) && !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) + +#include "SDL_windowsvideo.h" +#include "SDL_windowsopengles.h" +#include "SDL_windowsopengl.h" +#include "SDL_windowswindow.h" + +// EGL implementation of SDL OpenGL support + +bool WIN_GLES_LoadLibrary(SDL_VideoDevice *_this, const char *path) +{ + + // If the profile requested is not GL ES, switch over to WIN_GL functions + if (_this->gl_config.profile_mask != SDL_GL_CONTEXT_PROFILE_ES && + !SDL_GetHintBoolean(SDL_HINT_VIDEO_FORCE_EGL, false)) { +#ifdef SDL_VIDEO_OPENGL_WGL + WIN_GLES_UnloadLibrary(_this); + _this->GL_LoadLibrary = WIN_GL_LoadLibrary; + _this->GL_GetProcAddress = WIN_GL_GetProcAddress; + _this->GL_UnloadLibrary = WIN_GL_UnloadLibrary; + _this->GL_CreateContext = WIN_GL_CreateContext; + _this->GL_MakeCurrent = WIN_GL_MakeCurrent; + _this->GL_SetSwapInterval = WIN_GL_SetSwapInterval; + _this->GL_GetSwapInterval = WIN_GL_GetSwapInterval; + _this->GL_SwapWindow = WIN_GL_SwapWindow; + _this->GL_DestroyContext = WIN_GL_DestroyContext; + _this->GL_GetEGLSurface = NULL; + return WIN_GL_LoadLibrary(_this, path); +#else + return SDL_SetError("SDL not configured with OpenGL/WGL support"); +#endif + } + + if (!_this->egl_data) { + return SDL_EGL_LoadLibrary(_this, NULL, EGL_DEFAULT_DISPLAY, _this->gl_config.egl_platform); + } + + return true; +} + +SDL_GLContext WIN_GLES_CreateContext(SDL_VideoDevice *_this, SDL_Window *window) +{ + SDL_GLContext context; + SDL_WindowData *data = window->internal; + +#ifdef SDL_VIDEO_OPENGL_WGL + if (_this->gl_config.profile_mask != SDL_GL_CONTEXT_PROFILE_ES && + !SDL_GetHintBoolean(SDL_HINT_VIDEO_FORCE_EGL, false)) { + // Switch to WGL based functions + WIN_GLES_UnloadLibrary(_this); + _this->GL_LoadLibrary = WIN_GL_LoadLibrary; + _this->GL_GetProcAddress = WIN_GL_GetProcAddress; + _this->GL_UnloadLibrary = WIN_GL_UnloadLibrary; + _this->GL_CreateContext = WIN_GL_CreateContext; + _this->GL_MakeCurrent = WIN_GL_MakeCurrent; + _this->GL_SetSwapInterval = WIN_GL_SetSwapInterval; + _this->GL_GetSwapInterval = WIN_GL_GetSwapInterval; + _this->GL_SwapWindow = WIN_GL_SwapWindow; + _this->GL_DestroyContext = WIN_GL_DestroyContext; + _this->GL_GetEGLSurface = NULL; + + if (!WIN_GL_LoadLibrary(_this, NULL)) { + return NULL; + } + + return WIN_GL_CreateContext(_this, window); + } +#endif + + context = SDL_EGL_CreateContext(_this, data->egl_surface); + return context; +} + +bool WIN_GLES_DestroyContext(SDL_VideoDevice *_this, SDL_GLContext context) +{ + return SDL_EGL_DestroyContext(_this, context); +} + +/* *INDENT-OFF* */ // clang-format off +SDL_EGL_SwapWindow_impl(WIN) +SDL_EGL_MakeCurrent_impl(WIN) +/* *INDENT-ON* */ // clang-format on + +bool WIN_GLES_SetupWindow(SDL_VideoDevice *_this, SDL_Window *window) +{ + // The current context is lost in here; save it and reset it. + SDL_WindowData *windowdata = window->internal; + SDL_Window *current_win = SDL_GL_GetCurrentWindow(); + SDL_GLContext current_ctx = SDL_GL_GetCurrentContext(); + + if (!_this->egl_data) { +// !!! FIXME: commenting out this assertion is (I think) incorrect; figure out why driver_loaded is wrong for ANGLE instead. --ryan. +#if 0 // When hint SDL_HINT_OPENGL_ES_DRIVER is set to "1" (e.g. for ANGLE support), _this->gl_config.driver_loaded can be 1, while the below lines function. + SDL_assert(!_this->gl_config.driver_loaded); +#endif + if (!SDL_EGL_LoadLibrary(_this, NULL, EGL_DEFAULT_DISPLAY, _this->gl_config.egl_platform)) { + SDL_EGL_UnloadLibrary(_this); + return false; + } + _this->gl_config.driver_loaded = 1; + } + + // Create the GLES window surface + windowdata->egl_surface = SDL_EGL_CreateSurface(_this, window, (NativeWindowType)windowdata->hwnd); + + if (windowdata->egl_surface == EGL_NO_SURFACE) { + return SDL_SetError("Could not create GLES window surface"); + } + + return WIN_GLES_MakeCurrent(_this, current_win, current_ctx); +} + +EGLSurface WIN_GLES_GetEGLSurface(SDL_VideoDevice *_this, SDL_Window *window) +{ + SDL_WindowData *windowdata = window->internal; + + return windowdata->egl_surface; +} + +#endif // SDL_VIDEO_DRIVER_WINDOWS && SDL_VIDEO_OPENGL_EGL diff --git a/contrib/SDL-3.2.8/src/video/windows/SDL_windowsopengles.h b/contrib/SDL-3.2.8/src/video/windows/SDL_windowsopengles.h new file mode 100644 index 0000000..07cc8d4 --- /dev/null +++ b/contrib/SDL-3.2.8/src/video/windows/SDL_windowsopengles.h @@ -0,0 +1,48 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include "SDL_internal.h" + +#ifndef SDL_winopengles_h_ +#define SDL_winopengles_h_ + +#ifdef SDL_VIDEO_OPENGL_EGL + +#include "../SDL_sysvideo.h" +#include "../SDL_egl_c.h" + +// OpenGLES functions +#define WIN_GLES_GetAttribute SDL_EGL_GetAttribute +#define WIN_GLES_GetProcAddress SDL_EGL_GetProcAddressInternal +#define WIN_GLES_UnloadLibrary SDL_EGL_UnloadLibrary +#define WIN_GLES_GetSwapInterval SDL_EGL_GetSwapInterval +#define WIN_GLES_SetSwapInterval SDL_EGL_SetSwapInterval + +extern bool WIN_GLES_LoadLibrary(SDL_VideoDevice *_this, const char *path); +extern SDL_GLContext WIN_GLES_CreateContext(SDL_VideoDevice *_this, SDL_Window *window); +extern bool WIN_GLES_SwapWindow(SDL_VideoDevice *_this, SDL_Window *window); +extern bool WIN_GLES_MakeCurrent(SDL_VideoDevice *_this, SDL_Window *window, SDL_GLContext context); +extern bool WIN_GLES_DestroyContext(SDL_VideoDevice *_this, SDL_GLContext context); +extern bool WIN_GLES_SetupWindow(SDL_VideoDevice *_this, SDL_Window *window); +extern SDL_EGLSurface WIN_GLES_GetEGLSurface(SDL_VideoDevice *_this, SDL_Window *window); + +#endif // SDL_VIDEO_OPENGL_EGL + +#endif // SDL_winopengles_h_ diff --git a/contrib/SDL-3.2.8/src/video/windows/SDL_windowsrawinput.c b/contrib/SDL-3.2.8/src/video/windows/SDL_windowsrawinput.c new file mode 100644 index 0000000..fa24991 --- /dev/null +++ b/contrib/SDL-3.2.8/src/video/windows/SDL_windowsrawinput.c @@ -0,0 +1,262 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include "SDL_internal.h" + +#if defined(SDL_VIDEO_DRIVER_WINDOWS) + +#include "SDL_windowsvideo.h" + +#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) + +#include "SDL_windowsevents.h" + +#include "../../joystick/usb_ids.h" +#include "../../events/SDL_events_c.h" + +#define ENABLE_RAW_MOUSE_INPUT 0x01 +#define ENABLE_RAW_KEYBOARD_INPUT 0x02 + +typedef struct +{ + bool done; + Uint32 flags; + HANDLE ready_event; + HANDLE done_event; + HANDLE thread; +} RawInputThreadData; + +static RawInputThreadData thread_data = { + false, + 0, + INVALID_HANDLE_VALUE, + INVALID_HANDLE_VALUE, + INVALID_HANDLE_VALUE +}; + +static DWORD WINAPI WIN_RawInputThread(LPVOID param) +{ + SDL_VideoDevice *_this = SDL_GetVideoDevice(); + RawInputThreadData *data = (RawInputThreadData *)param; + RAWINPUTDEVICE devices[2]; + HWND window; + UINT count = 0; + + window = CreateWindowEx(0, TEXT("Message"), NULL, 0, 0, 0, 0, 0, HWND_MESSAGE, NULL, NULL, NULL); + if (!window) { + return 0; + } + + SDL_zeroa(devices); + + if (data->flags & ENABLE_RAW_MOUSE_INPUT) { + devices[count].usUsagePage = USB_USAGEPAGE_GENERIC_DESKTOP; + devices[count].usUsage = USB_USAGE_GENERIC_MOUSE; + devices[count].dwFlags = 0; + devices[count].hwndTarget = window; + ++count; + } + + if (data->flags & ENABLE_RAW_KEYBOARD_INPUT) { + devices[count].usUsagePage = USB_USAGEPAGE_GENERIC_DESKTOP; + devices[count].usUsage = USB_USAGE_GENERIC_KEYBOARD; + devices[count].dwFlags = 0; + devices[count].hwndTarget = window; + ++count; + } + + if (!RegisterRawInputDevices(devices, count, sizeof(devices[0]))) { + DestroyWindow(window); + return 0; + } + + // Make sure we get events as soon as possible + SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL); + + // Tell the parent we're ready to go! + SetEvent(data->ready_event); + + while (!data->done) { + Uint64 idle_begin = SDL_GetTicksNS(); + DWORD result = MsgWaitForMultipleObjects(1, &data->done_event, FALSE, INFINITE, QS_RAWINPUT); + Uint64 idle_end = SDL_GetTicksNS(); + if (result != (WAIT_OBJECT_0 + 1)) { + break; + } + + // Clear the queue status so MsgWaitForMultipleObjects() will wait again + (void)GetQueueStatus(QS_RAWINPUT); + + Uint64 idle_time = idle_end - idle_begin; + Uint64 usb_8khz_interval = SDL_US_TO_NS(125); + Uint64 poll_start = idle_time < usb_8khz_interval ? _this->internal->last_rawinput_poll : idle_end; + + WIN_PollRawInput(_this, poll_start); + } + + devices[0].dwFlags |= RIDEV_REMOVE; + devices[1].dwFlags |= RIDEV_REMOVE; + RegisterRawInputDevices(devices, count, sizeof(devices[0])); + + DestroyWindow(window); + + return 0; +} + +static void CleanupRawInputThreadData(RawInputThreadData *data) +{ + if (data->thread != INVALID_HANDLE_VALUE) { + data->done = true; + SetEvent(data->done_event); + WaitForSingleObject(data->thread, 3000); + CloseHandle(data->thread); + data->thread = INVALID_HANDLE_VALUE; + } + + if (data->ready_event != INVALID_HANDLE_VALUE) { + CloseHandle(data->ready_event); + data->ready_event = INVALID_HANDLE_VALUE; + } + + if (data->done_event != INVALID_HANDLE_VALUE) { + CloseHandle(data->done_event); + data->done_event = INVALID_HANDLE_VALUE; + } +} + +static bool WIN_SetRawInputEnabled(SDL_VideoDevice *_this, Uint32 flags) +{ + bool result = false; + + CleanupRawInputThreadData(&thread_data); + + if (flags) { + HANDLE handles[2]; + + thread_data.flags = flags; + thread_data.ready_event = CreateEvent(NULL, FALSE, FALSE, NULL); + if (thread_data.ready_event == INVALID_HANDLE_VALUE) { + WIN_SetError("CreateEvent"); + goto done; + } + + thread_data.done = false; + thread_data.done_event = CreateEvent(NULL, FALSE, FALSE, NULL); + if (thread_data.done_event == INVALID_HANDLE_VALUE) { + WIN_SetError("CreateEvent"); + goto done; + } + + thread_data.thread = CreateThread(NULL, 0, WIN_RawInputThread, &thread_data, 0, NULL); + if (thread_data.thread == INVALID_HANDLE_VALUE) { + WIN_SetError("CreateThread"); + goto done; + } + + // Wait for the thread to signal ready or exit + handles[0] = thread_data.ready_event; + handles[1] = thread_data.thread; + if (WaitForMultipleObjects(2, handles, FALSE, INFINITE) != WAIT_OBJECT_0) { + SDL_SetError("Couldn't set up raw input handling"); + goto done; + } + result = true; + } else { + result = true; + } + +done: + if (!result) { + CleanupRawInputThreadData(&thread_data); + } + return result; +} + +static bool WIN_UpdateRawInputEnabled(SDL_VideoDevice *_this) +{ + SDL_VideoData *data = _this->internal; + Uint32 flags = 0; + if (data->raw_mouse_enabled) { + flags |= ENABLE_RAW_MOUSE_INPUT; + } + if (data->raw_keyboard_enabled) { + flags |= ENABLE_RAW_KEYBOARD_INPUT; + } + if (flags != data->raw_input_enabled) { + if (WIN_SetRawInputEnabled(_this, flags)) { + data->raw_input_enabled = flags; + } else { + return false; + } + } + return true; +} + +bool WIN_SetRawMouseEnabled(SDL_VideoDevice *_this, bool enabled) +{ + SDL_VideoData *data = _this->internal; + data->raw_mouse_enabled = enabled; + if (data->gameinput_context) { + if (!WIN_UpdateGameInputEnabled(_this)) { + data->raw_mouse_enabled = !enabled; + return false; + } + } else { + if (!WIN_UpdateRawInputEnabled(_this)) { + data->raw_mouse_enabled = !enabled; + return false; + } + } + return true; +} + +bool WIN_SetRawKeyboardEnabled(SDL_VideoDevice *_this, bool enabled) +{ + SDL_VideoData *data = _this->internal; + data->raw_keyboard_enabled = enabled; + if (data->gameinput_context) { + if (!WIN_UpdateGameInputEnabled(_this)) { + data->raw_keyboard_enabled = !enabled; + return false; + } + } else { + if (!WIN_UpdateRawInputEnabled(_this)) { + data->raw_keyboard_enabled = !enabled; + return false; + } + } + return true; +} + +#else + +bool WIN_SetRawMouseEnabled(SDL_VideoDevice *_this, bool enabled) +{ + return SDL_Unsupported(); +} + +bool WIN_SetRawKeyboardEnabled(SDL_VideoDevice *_this, bool enabled) +{ + return SDL_Unsupported(); +} + +#endif // !SDL_PLATFORM_XBOXONE && !SDL_PLATFORM_XBOXSERIES + +#endif // SDL_VIDEO_DRIVER_WINDOWS diff --git a/contrib/SDL-3.2.8/src/video/windows/SDL_windowsrawinput.h b/contrib/SDL-3.2.8/src/video/windows/SDL_windowsrawinput.h new file mode 100644 index 0000000..25ae705 --- /dev/null +++ b/contrib/SDL-3.2.8/src/video/windows/SDL_windowsrawinput.h @@ -0,0 +1,30 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include "SDL_internal.h" + +#ifndef SDL_windowsrawinput_h_ +#define SDL_windowsrawinput_h_ + +extern bool WIN_SetRawMouseEnabled(SDL_VideoDevice *_this, bool enabled); +extern bool WIN_SetRawKeyboardEnabled(SDL_VideoDevice *_this, bool enabled); +extern bool WIN_RefreshRawInputEnabled(SDL_VideoDevice *_this); + +#endif // SDL_windowsrawinput_h_ diff --git a/contrib/SDL-3.2.8/src/video/windows/SDL_windowsshape.c b/contrib/SDL-3.2.8/src/video/windows/SDL_windowsshape.c new file mode 100644 index 0000000..db4e4a0 --- /dev/null +++ b/contrib/SDL-3.2.8/src/video/windows/SDL_windowsshape.c @@ -0,0 +1,125 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include "SDL_internal.h" + +#if defined(SDL_VIDEO_DRIVER_WINDOWS) && !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) + +#include "SDL_windowsvideo.h" +#include "SDL_windowsshape.h" + + +static void AddRegion(HRGN *mask, int x1, int y1, int x2, int y2) +{ + HRGN region = CreateRectRgn(x1, y1, x2, y2); + if (*mask) { + CombineRgn(*mask, *mask, region, RGN_OR); + DeleteObject(region); + } else { + *mask = region; + } +} + +static HRGN GenerateSpanListRegion(SDL_Surface *shape, int offset_x, int offset_y) +{ + HRGN mask = NULL; + int x, y; + int span_start = -1; + + for (y = 0; y < shape->h; ++y) { + const Uint8 *a = (const Uint8 *)shape->pixels + y * shape->pitch; + for (x = 0; x < shape->w; ++x) { + if (*a == SDL_ALPHA_TRANSPARENT) { + if (span_start != -1) { + AddRegion(&mask, offset_x + span_start, offset_y + y, offset_x + x, offset_y + y + 1); + span_start = -1; + } + } else { + if (span_start == -1) { + span_start = x; + } + } + a += 4; + } + if (span_start != -1) { + // Add the final span + AddRegion(&mask, offset_x + span_start, offset_y + y, offset_x + x, offset_y + y + 1); + span_start = -1; + } + } + return mask; +} + +bool WIN_UpdateWindowShape(SDL_VideoDevice *_this, SDL_Window *window, SDL_Surface *shape) +{ + SDL_WindowData *data = window->internal; + HRGN mask = NULL; + + // Generate a set of spans for the region + if (shape) { + SDL_Surface *stretched = NULL; + RECT rect; + + if (shape->w != window->w || shape->h != window->h) { + stretched = SDL_CreateSurface(window->w, window->h, SDL_PIXELFORMAT_ARGB32); + if (!stretched) { + return false; + } + if (!SDL_StretchSurface(shape, NULL, stretched, NULL, SDL_SCALEMODE_LINEAR)) { + SDL_DestroySurface(stretched); + return false; + } + shape = stretched; + } + + rect.top = 0; + rect.left = 0; + rect.bottom = 0; + rect.right = 0; + if (!(SDL_GetWindowFlags(data->window) & SDL_WINDOW_BORDERLESS)) { + WIN_AdjustWindowRectForHWND(data->hwnd, &rect, 0); + } + + mask = GenerateSpanListRegion(shape, -rect.left, -rect.top); + + if (!(SDL_GetWindowFlags(data->window) & SDL_WINDOW_BORDERLESS)) { + // Add the window borders + // top + AddRegion(&mask, 0, 0, -rect.left + shape->w + rect.right + 1, -rect.top + 1); + // left + AddRegion(&mask, 0, -rect.top, -rect.left + 1, -rect.top + shape->h + 1); + // right + AddRegion(&mask, -rect.left + shape->w, -rect.top, -rect.left + shape->w + rect.right + 1, -rect.top + shape->h + 1); + // bottom + AddRegion(&mask, 0, -rect.top + shape->h, -rect.left + shape->w + rect.right + 1, -rect.top + shape->h + rect.bottom + 1); + } + + if (stretched) { + SDL_DestroySurface(stretched); + } + } + if (!SetWindowRgn(data->hwnd, mask, TRUE)) { + DeleteObject(mask); + return WIN_SetError("SetWindowRgn failed"); + } + return true; +} + +#endif // defined(SDL_VIDEO_DRIVER_WINDOWS) && !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) diff --git a/contrib/SDL-3.2.8/src/video/windows/SDL_windowsshape.h b/contrib/SDL-3.2.8/src/video/windows/SDL_windowsshape.h new file mode 100644 index 0000000..fae61d5 --- /dev/null +++ b/contrib/SDL-3.2.8/src/video/windows/SDL_windowsshape.h @@ -0,0 +1,28 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include "SDL_internal.h" + +#ifndef SDL_windowsshape_h_ +#define SDL_windowsshape_h_ + +extern bool WIN_UpdateWindowShape(SDL_VideoDevice *_this, SDL_Window *window, SDL_Surface *shape); + +#endif // SDL_windowsshape_h_ diff --git a/contrib/SDL-3.2.8/src/video/windows/SDL_windowsvideo.c b/contrib/SDL-3.2.8/src/video/windows/SDL_windowsvideo.c new file mode 100644 index 0000000..04415b5 --- /dev/null +++ b/contrib/SDL-3.2.8/src/video/windows/SDL_windowsvideo.c @@ -0,0 +1,773 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include "SDL_internal.h" + +#ifdef SDL_VIDEO_DRIVER_WINDOWS + +#ifdef SDL_VIDEO_VULKAN +#include "../SDL_vulkan_internal.h" +#endif +#include "../SDL_sysvideo.h" +#include "../SDL_pixels_c.h" +#include "../../SDL_hints_c.h" +#include "../../core/windows/SDL_hid.h" + +#include "SDL_windowsvideo.h" +#include "SDL_windowsframebuffer.h" +#include "SDL_windowsmessagebox.h" +#include "SDL_windowsrawinput.h" +#include "SDL_windowsvulkan.h" + +#ifdef SDL_GDK_TEXTINPUT +#include "../gdk/SDL_gdktextinput.h" +#endif + +// #define HIGHDPI_DEBUG + +// Initialization/Query functions +static bool WIN_VideoInit(SDL_VideoDevice *_this); +static void WIN_VideoQuit(SDL_VideoDevice *_this); + +// Hints +bool g_WindowsEnableMessageLoop = true; +bool g_WindowsEnableMenuMnemonics = false; +bool g_WindowFrameUsableWhileCursorHidden = true; + +static void SDLCALL UpdateWindowsRawKeyboard(void *userdata, const char *name, const char *oldValue, const char *newValue) +{ + SDL_VideoDevice *_this = (SDL_VideoDevice *)userdata; + bool enabled = SDL_GetStringBoolean(newValue, false); + WIN_SetRawKeyboardEnabled(_this, enabled); +} + +static void SDLCALL UpdateWindowsEnableMessageLoop(void *userdata, const char *name, const char *oldValue, const char *newValue) +{ + g_WindowsEnableMessageLoop = SDL_GetStringBoolean(newValue, true); +} + +static void SDLCALL UpdateWindowsEnableMenuMnemonics(void *userdata, const char *name, const char *oldValue, const char *newValue) +{ + g_WindowsEnableMenuMnemonics = SDL_GetStringBoolean(newValue, false); +} + +static void SDLCALL UpdateWindowFrameUsableWhileCursorHidden(void *userdata, const char *name, const char *oldValue, const char *newValue) +{ + g_WindowFrameUsableWhileCursorHidden = SDL_GetStringBoolean(newValue, true); +} + +#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) +static bool WIN_SuspendScreenSaver(SDL_VideoDevice *_this) +{ + DWORD result; + if (_this->suspend_screensaver) { + result = SetThreadExecutionState(ES_CONTINUOUS | ES_DISPLAY_REQUIRED); + } else { + result = SetThreadExecutionState(ES_CONTINUOUS); + } + if (result == 0) { + SDL_SetError("SetThreadExecutionState() failed"); + return false; + } + return true; +} +#endif + +#if defined(SDL_PLATFORM_XBOXONE) || defined(SDL_PLATFORM_XBOXSERIES) +extern void D3D12_XBOX_GetResolution(Uint32 *width, Uint32 *height); +#endif + +// Windows driver bootstrap functions + +static void WIN_DeleteDevice(SDL_VideoDevice *device) +{ + SDL_VideoData *data = device->internal; + + SDL_UnregisterApp(); +#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) + if (data->userDLL) { + SDL_UnloadObject(data->userDLL); + } + if (data->shcoreDLL) { + SDL_UnloadObject(data->shcoreDLL); + } +#endif +#ifdef HAVE_DXGI_H + if (data->pDXGIFactory) { + IDXGIFactory_Release(data->pDXGIFactory); + } + if (data->dxgiDLL) { + SDL_UnloadObject(data->dxgiDLL); + } +#endif + if (device->wakeup_lock) { + SDL_DestroyMutex(device->wakeup_lock); + } + SDL_free(device->internal->rawinput); + SDL_free(device->internal); + SDL_free(device); +} + +static SDL_VideoDevice *WIN_CreateDevice(void) +{ + SDL_VideoDevice *device; + SDL_VideoData *data; + + SDL_RegisterApp(NULL, 0, NULL); + + // Initialize all variables that we clean on shutdown + device = (SDL_VideoDevice *)SDL_calloc(1, sizeof(SDL_VideoDevice)); + if (device) { + data = (SDL_VideoData *)SDL_calloc(1, sizeof(SDL_VideoData)); + } else { + data = NULL; + } + if (!data) { + SDL_UnregisterApp(); + SDL_free(device); + return NULL; + } + device->internal = data; + device->wakeup_lock = SDL_CreateMutex(); + device->system_theme = WIN_GetSystemTheme(); + +#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) + data->userDLL = SDL_LoadObject("USER32.DLL"); + if (data->userDLL) { + /* *INDENT-OFF* */ // clang-format off + data->CloseTouchInputHandle = (BOOL (WINAPI *)(HTOUCHINPUT))SDL_LoadFunction(data->userDLL, "CloseTouchInputHandle"); + data->GetTouchInputInfo = (BOOL (WINAPI *)(HTOUCHINPUT, UINT, PTOUCHINPUT, int)) SDL_LoadFunction(data->userDLL, "GetTouchInputInfo"); + data->RegisterTouchWindow = (BOOL (WINAPI *)(HWND, ULONG))SDL_LoadFunction(data->userDLL, "RegisterTouchWindow"); + data->SetProcessDPIAware = (BOOL (WINAPI *)(void))SDL_LoadFunction(data->userDLL, "SetProcessDPIAware"); + data->SetProcessDpiAwarenessContext = (BOOL (WINAPI *)(DPI_AWARENESS_CONTEXT))SDL_LoadFunction(data->userDLL, "SetProcessDpiAwarenessContext"); + data->SetThreadDpiAwarenessContext = (DPI_AWARENESS_CONTEXT (WINAPI *)(DPI_AWARENESS_CONTEXT))SDL_LoadFunction(data->userDLL, "SetThreadDpiAwarenessContext"); + data->GetThreadDpiAwarenessContext = (DPI_AWARENESS_CONTEXT (WINAPI *)(void))SDL_LoadFunction(data->userDLL, "GetThreadDpiAwarenessContext"); + data->GetAwarenessFromDpiAwarenessContext = (DPI_AWARENESS (WINAPI *)(DPI_AWARENESS_CONTEXT))SDL_LoadFunction(data->userDLL, "GetAwarenessFromDpiAwarenessContext"); + data->EnableNonClientDpiScaling = (BOOL (WINAPI *)(HWND))SDL_LoadFunction(data->userDLL, "EnableNonClientDpiScaling"); + data->AdjustWindowRectExForDpi = (BOOL (WINAPI *)(LPRECT, DWORD, BOOL, DWORD, UINT))SDL_LoadFunction(data->userDLL, "AdjustWindowRectExForDpi"); + data->GetDpiForWindow = (UINT (WINAPI *)(HWND))SDL_LoadFunction(data->userDLL, "GetDpiForWindow"); + data->AreDpiAwarenessContextsEqual = (BOOL (WINAPI *)(DPI_AWARENESS_CONTEXT, DPI_AWARENESS_CONTEXT))SDL_LoadFunction(data->userDLL, "AreDpiAwarenessContextsEqual"); + data->IsValidDpiAwarenessContext = (BOOL (WINAPI *)(DPI_AWARENESS_CONTEXT))SDL_LoadFunction(data->userDLL, "IsValidDpiAwarenessContext"); + data->GetDisplayConfigBufferSizes = (LONG (WINAPI *)(UINT32,UINT32*,UINT32* ))SDL_LoadFunction(data->userDLL, "GetDisplayConfigBufferSizes"); + data->QueryDisplayConfig = (LONG (WINAPI *)(UINT32,UINT32*,DISPLAYCONFIG_PATH_INFO*,UINT32*,DISPLAYCONFIG_MODE_INFO*,DISPLAYCONFIG_TOPOLOGY_ID*))SDL_LoadFunction(data->userDLL, "QueryDisplayConfig"); + data->DisplayConfigGetDeviceInfo = (LONG (WINAPI *)(DISPLAYCONFIG_DEVICE_INFO_HEADER*))SDL_LoadFunction(data->userDLL, "DisplayConfigGetDeviceInfo"); + data->GetPointerType = (BOOL (WINAPI *)(UINT32, POINTER_INPUT_TYPE *))SDL_LoadFunction(data->userDLL, "GetPointerType"); + data->GetPointerPenInfo = (BOOL (WINAPI *)(UINT32, POINTER_PEN_INFO *))SDL_LoadFunction(data->userDLL, "GetPointerPenInfo"); + /* *INDENT-ON* */ // clang-format on + } else { + SDL_ClearError(); + } + + data->shcoreDLL = SDL_LoadObject("SHCORE.DLL"); + if (data->shcoreDLL) { + /* *INDENT-OFF* */ // clang-format off + data->GetDpiForMonitor = (HRESULT (WINAPI *)(HMONITOR, MONITOR_DPI_TYPE, UINT *, UINT *))SDL_LoadFunction(data->shcoreDLL, "GetDpiForMonitor"); + data->SetProcessDpiAwareness = (HRESULT (WINAPI *)(PROCESS_DPI_AWARENESS))SDL_LoadFunction(data->shcoreDLL, "SetProcessDpiAwareness"); + /* *INDENT-ON* */ // clang-format on + } else { + SDL_ClearError(); + } +#endif // #if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) + +#ifdef HAVE_DXGI_H + data->dxgiDLL = SDL_LoadObject("DXGI.DLL"); + if (data->dxgiDLL) { + /* *INDENT-OFF* */ // clang-format off + typedef HRESULT (WINAPI *CreateDXGI_t)(REFIID riid, void **ppFactory); + /* *INDENT-ON* */ // clang-format on + CreateDXGI_t CreateDXGI; + + CreateDXGI = (CreateDXGI_t)SDL_LoadFunction(data->dxgiDLL, "CreateDXGIFactory"); + if (CreateDXGI) { + GUID dxgiGUID = { 0x7b7166ec, 0x21c7, 0x44ae, { 0xb2, 0x1a, 0xc9, 0xae, 0x32, 0x1a, 0xe3, 0x69 } }; + CreateDXGI(&dxgiGUID, (void **)&data->pDXGIFactory); + } + } +#endif + + // Set the function pointers + device->VideoInit = WIN_VideoInit; + device->VideoQuit = WIN_VideoQuit; +#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) + device->RefreshDisplays = WIN_RefreshDisplays; + device->GetDisplayBounds = WIN_GetDisplayBounds; + device->GetDisplayUsableBounds = WIN_GetDisplayUsableBounds; + device->GetDisplayModes = WIN_GetDisplayModes; + device->SetDisplayMode = WIN_SetDisplayMode; +#endif + device->PumpEvents = WIN_PumpEvents; + device->WaitEventTimeout = WIN_WaitEventTimeout; +#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) + device->SendWakeupEvent = WIN_SendWakeupEvent; + device->SuspendScreenSaver = WIN_SuspendScreenSaver; +#endif + + device->CreateSDLWindow = WIN_CreateWindow; + device->SetWindowTitle = WIN_SetWindowTitle; + device->SetWindowIcon = WIN_SetWindowIcon; + device->SetWindowPosition = WIN_SetWindowPosition; + device->SetWindowSize = WIN_SetWindowSize; + device->GetWindowBordersSize = WIN_GetWindowBordersSize; + device->GetWindowSizeInPixels = WIN_GetWindowSizeInPixels; + device->SetWindowOpacity = WIN_SetWindowOpacity; + device->ShowWindow = WIN_ShowWindow; + device->HideWindow = WIN_HideWindow; + device->RaiseWindow = WIN_RaiseWindow; + device->MaximizeWindow = WIN_MaximizeWindow; + device->MinimizeWindow = WIN_MinimizeWindow; + device->RestoreWindow = WIN_RestoreWindow; + device->SetWindowBordered = WIN_SetWindowBordered; + device->SetWindowResizable = WIN_SetWindowResizable; + device->SetWindowAlwaysOnTop = WIN_SetWindowAlwaysOnTop; + device->SetWindowFullscreen = WIN_SetWindowFullscreen; + device->SetWindowParent = WIN_SetWindowParent; + device->SetWindowModal = WIN_SetWindowModal; +#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) + device->GetWindowICCProfile = WIN_GetWindowICCProfile; + device->SetWindowMouseRect = WIN_SetWindowMouseRect; + device->SetWindowMouseGrab = WIN_SetWindowMouseGrab; + device->SetWindowKeyboardGrab = WIN_SetWindowKeyboardGrab; +#endif + device->DestroyWindow = WIN_DestroyWindow; +#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) + device->CreateWindowFramebuffer = WIN_CreateWindowFramebuffer; + device->UpdateWindowFramebuffer = WIN_UpdateWindowFramebuffer; + device->DestroyWindowFramebuffer = WIN_DestroyWindowFramebuffer; + device->OnWindowEnter = WIN_OnWindowEnter; + device->SetWindowHitTest = WIN_SetWindowHitTest; + device->AcceptDragAndDrop = WIN_AcceptDragAndDrop; + device->FlashWindow = WIN_FlashWindow; + device->ShowWindowSystemMenu = WIN_ShowWindowSystemMenu; + device->SetWindowFocusable = WIN_SetWindowFocusable; + device->UpdateWindowShape = WIN_UpdateWindowShape; +#endif + +#ifdef SDL_VIDEO_OPENGL_WGL + device->GL_LoadLibrary = WIN_GL_LoadLibrary; + device->GL_GetProcAddress = WIN_GL_GetProcAddress; + device->GL_UnloadLibrary = WIN_GL_UnloadLibrary; + device->GL_CreateContext = WIN_GL_CreateContext; + device->GL_MakeCurrent = WIN_GL_MakeCurrent; + device->GL_SetSwapInterval = WIN_GL_SetSwapInterval; + device->GL_GetSwapInterval = WIN_GL_GetSwapInterval; + device->GL_SwapWindow = WIN_GL_SwapWindow; + device->GL_DestroyContext = WIN_GL_DestroyContext; + device->GL_GetEGLSurface = NULL; +#endif +#ifdef SDL_VIDEO_OPENGL_EGL +#ifdef SDL_VIDEO_OPENGL_WGL + if (SDL_GetHintBoolean(SDL_HINT_VIDEO_FORCE_EGL, false)) { +#endif + // Use EGL based functions + device->GL_LoadLibrary = WIN_GLES_LoadLibrary; + device->GL_GetProcAddress = WIN_GLES_GetProcAddress; + device->GL_UnloadLibrary = WIN_GLES_UnloadLibrary; + device->GL_CreateContext = WIN_GLES_CreateContext; + device->GL_MakeCurrent = WIN_GLES_MakeCurrent; + device->GL_SetSwapInterval = WIN_GLES_SetSwapInterval; + device->GL_GetSwapInterval = WIN_GLES_GetSwapInterval; + device->GL_SwapWindow = WIN_GLES_SwapWindow; + device->GL_DestroyContext = WIN_GLES_DestroyContext; + device->GL_GetEGLSurface = WIN_GLES_GetEGLSurface; +#ifdef SDL_VIDEO_OPENGL_WGL + } +#endif +#endif +#ifdef SDL_VIDEO_VULKAN + device->Vulkan_LoadLibrary = WIN_Vulkan_LoadLibrary; + device->Vulkan_UnloadLibrary = WIN_Vulkan_UnloadLibrary; + device->Vulkan_GetInstanceExtensions = WIN_Vulkan_GetInstanceExtensions; + device->Vulkan_CreateSurface = WIN_Vulkan_CreateSurface; + device->Vulkan_DestroySurface = WIN_Vulkan_DestroySurface; + device->Vulkan_GetPresentationSupport = WIN_Vulkan_GetPresentationSupport; +#endif + +#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) + device->StartTextInput = WIN_StartTextInput; + device->StopTextInput = WIN_StopTextInput; + device->UpdateTextInputArea = WIN_UpdateTextInputArea; + device->ClearComposition = WIN_ClearComposition; + + device->SetClipboardData = WIN_SetClipboardData; + device->GetClipboardData = WIN_GetClipboardData; + device->HasClipboardData = WIN_HasClipboardData; +#endif + +#ifdef SDL_GDK_TEXTINPUT + GDK_EnsureHints(); + + device->StartTextInput = GDK_StartTextInput; + device->StopTextInput = GDK_StopTextInput; + device->UpdateTextInputArea = GDK_UpdateTextInputArea; + device->ClearComposition = GDK_ClearComposition; + + device->HasScreenKeyboardSupport = GDK_HasScreenKeyboardSupport; + device->ShowScreenKeyboard = GDK_ShowScreenKeyboard; + device->HideScreenKeyboard = GDK_HideScreenKeyboard; + device->IsScreenKeyboardShown = GDK_IsScreenKeyboardShown; +#endif + + device->free = WIN_DeleteDevice; + + device->device_caps = VIDEO_DEVICE_CAPS_HAS_POPUP_WINDOW_SUPPORT | + VIDEO_DEVICE_CAPS_SENDS_FULLSCREEN_DIMENSIONS; + + return device; +} + +VideoBootStrap WINDOWS_bootstrap = { + "windows", "SDL Windows video driver", WIN_CreateDevice, + #if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) + WIN_ShowMessageBox, + #else + NULL, + #endif + false +}; + +static BOOL WIN_DeclareDPIAwareUnaware(SDL_VideoDevice *_this) +{ +#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) + SDL_VideoData *data = _this->internal; + + if (data->SetProcessDpiAwarenessContext) { + return data->SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_UNAWARE); + } else if (data->SetProcessDpiAwareness) { + // Windows 8.1 + return SUCCEEDED(data->SetProcessDpiAwareness(PROCESS_DPI_UNAWARE)); + } +#endif + return FALSE; +} + +static BOOL WIN_DeclareDPIAwareSystem(SDL_VideoDevice *_this) +{ +#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) + SDL_VideoData *data = _this->internal; + + if (data->SetProcessDpiAwarenessContext) { + // Windows 10, version 1607 + return data->SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_SYSTEM_AWARE); + } else if (data->SetProcessDpiAwareness) { + // Windows 8.1 + return SUCCEEDED(data->SetProcessDpiAwareness(PROCESS_SYSTEM_DPI_AWARE)); + } else if (data->SetProcessDPIAware) { + // Windows Vista + return data->SetProcessDPIAware(); + } +#endif + return FALSE; +} + +static BOOL WIN_DeclareDPIAwarePerMonitor(SDL_VideoDevice *_this) +{ +#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) + SDL_VideoData *data = _this->internal; + + if (data->SetProcessDpiAwarenessContext) { + // Windows 10, version 1607 + return data->SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE); + } else if (data->SetProcessDpiAwareness) { + // Windows 8.1 + return SUCCEEDED(data->SetProcessDpiAwareness(PROCESS_PER_MONITOR_DPI_AWARE)); + } else { + // Older OS: fall back to system DPI aware + return WIN_DeclareDPIAwareSystem(_this); + } +#else + return FALSE; +#endif +} + +static BOOL WIN_DeclareDPIAwarePerMonitorV2(SDL_VideoDevice *_this) +{ +#if defined(SDL_PLATFORM_XBOXONE) || defined(SDL_PLATFORM_XBOXSERIES) + return FALSE; +#else + SDL_VideoData *data = _this->internal; + + // Declare DPI aware (may have been done in external code or a manifest, as well) + if (data->SetProcessDpiAwarenessContext) { + // Windows 10, version 1607 + + /* NOTE: SetThreadDpiAwarenessContext doesn't work here with OpenGL - the OpenGL contents + end up still getting OS scaled. (tested on Windows 10 21H1 19043.1348, NVIDIA 496.49) + + NOTE: Enabling DPI awareness through Windows Explorer + (right click .exe -> Properties -> Compatibility -> High DPI Settings -> + check "Override high DPI Scaling behaviour", select Application) gives + a DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE context (at least on Windows 10 21H1), and + setting DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 will fail. + + NOTE: Entering exclusive fullscreen in a DPI_AWARENESS_CONTEXT_UNAWARE process + appears to cause Windows to change the .exe manifest to DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE + on future launches. This means attempting to use DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 + will fail in the future until you manually clear the "Override high DPI Scaling behaviour" + setting in Windows Explorer (tested on Windows 10 21H2). + */ + if (data->SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2)) { + return TRUE; + } else { + return WIN_DeclareDPIAwarePerMonitor(_this); + } + } else { + // Older OS: fall back to per-monitor (or system) + return WIN_DeclareDPIAwarePerMonitor(_this); + } +#endif +} + +#ifdef HIGHDPI_DEBUG +static const char *WIN_GetDPIAwareness(SDL_VideoDevice *_this) +{ + SDL_VideoData *data = _this->internal; + + if (data->GetThreadDpiAwarenessContext && data->AreDpiAwarenessContextsEqual) { + DPI_AWARENESS_CONTEXT context = data->GetThreadDpiAwarenessContext(); + + if (data->AreDpiAwarenessContextsEqual(context, DPI_AWARENESS_CONTEXT_UNAWARE)) { + return "unaware"; + } else if (data->AreDpiAwarenessContextsEqual(context, DPI_AWARENESS_CONTEXT_SYSTEM_AWARE)) { + return "system"; + } else if (data->AreDpiAwarenessContextsEqual(context, DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE)) { + return "permonitor"; + } else if (data->AreDpiAwarenessContextsEqual(context, DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2)) { + return "permonitorv2"; + } else if (data->AreDpiAwarenessContextsEqual(context, DPI_AWARENESS_CONTEXT_UNAWARE_GDISCALED)) { + return "unaware_gdiscaled"; + } + } + + return ""; +} +#endif + +static void WIN_InitDPIAwareness(SDL_VideoDevice *_this) +{ + const char *hint = SDL_GetHint("SDL_WINDOWS_DPI_AWARENESS"); + + if (!hint || SDL_strcmp(hint, "permonitorv2") == 0) { + WIN_DeclareDPIAwarePerMonitorV2(_this); + } else if (SDL_strcmp(hint, "permonitor") == 0) { + WIN_DeclareDPIAwarePerMonitor(_this); + } else if (SDL_strcmp(hint, "system") == 0) { + WIN_DeclareDPIAwareSystem(_this); + } else if (SDL_strcmp(hint, "unaware") == 0) { + WIN_DeclareDPIAwareUnaware(_this); + } +} + +static bool WIN_VideoInit(SDL_VideoDevice *_this) +{ + SDL_VideoData *data = _this->internal; + HRESULT hr; + + hr = WIN_CoInitialize(); + if (SUCCEEDED(hr)) { + data->coinitialized = true; + +#if !(defined(SDL_PLATFORM_XBOXONE) || defined(SDL_PLATFORM_XBOXSERIES)) + hr = OleInitialize(NULL); + if (SUCCEEDED(hr)) { + data->oleinitialized = true; + } else { + SDL_LogInfo(SDL_LOG_CATEGORY_VIDEO, "OleInitialize() failed: 0x%.8x, using fallback drag-n-drop functionality", (unsigned int)hr); + } +#endif // !(defined(SDL_PLATFORM_XBOXONE) || defined(SDL_PLATFORM_XBOXSERIES)) + } else { + SDL_LogInfo(SDL_LOG_CATEGORY_VIDEO, "CoInitialize() failed: 0x%.8x, using fallback drag-n-drop functionality", (unsigned int)hr); + } + + WIN_InitDPIAwareness(_this); + +#ifdef HIGHDPI_DEBUG + SDL_Log("DPI awareness: %s", WIN_GetDPIAwareness(_this)); +#endif + + if (SDL_GetHintBoolean(SDL_HINT_WINDOWS_GAMEINPUT, true)) { + WIN_InitGameInput(_this); + } + +#if defined(SDL_PLATFORM_XBOXONE) || defined(SDL_PLATFORM_XBOXSERIES) + // For Xbox, we just need to create the single display + { + SDL_DisplayMode mode; + + SDL_zero(mode); + D3D12_XBOX_GetResolution(&mode.w, &mode.h); + mode.refresh_rate = 60.0f; + mode.format = SDL_PIXELFORMAT_ARGB8888; + + SDL_AddBasicVideoDisplay(&mode); + } +#else // !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) + if (!WIN_InitModes(_this)) { + return false; + } + + WIN_InitKeyboard(_this); + WIN_InitMouse(_this); + WIN_InitDeviceNotification(); + if (!_this->internal->gameinput_context) { + WIN_CheckKeyboardAndMouseHotplug(_this, true); + } +#endif + + SDL_AddHintCallback(SDL_HINT_WINDOWS_RAW_KEYBOARD, UpdateWindowsRawKeyboard, _this); + SDL_AddHintCallback(SDL_HINT_WINDOWS_ENABLE_MESSAGELOOP, UpdateWindowsEnableMessageLoop, NULL); + SDL_AddHintCallback(SDL_HINT_WINDOWS_ENABLE_MENU_MNEMONICS, UpdateWindowsEnableMenuMnemonics, NULL); + SDL_AddHintCallback(SDL_HINT_WINDOW_FRAME_USABLE_WHILE_CURSOR_HIDDEN, UpdateWindowFrameUsableWhileCursorHidden, NULL); + +#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) + data->_SDL_WAKEUP = RegisterWindowMessageA("_SDL_WAKEUP"); +#endif + + return true; +} + +void WIN_VideoQuit(SDL_VideoDevice *_this) +{ + SDL_VideoData *data = _this->internal; + + SDL_RemoveHintCallback(SDL_HINT_WINDOWS_RAW_KEYBOARD, UpdateWindowsRawKeyboard, _this); + SDL_RemoveHintCallback(SDL_HINT_WINDOWS_ENABLE_MESSAGELOOP, UpdateWindowsEnableMessageLoop, NULL); + SDL_RemoveHintCallback(SDL_HINT_WINDOWS_ENABLE_MENU_MNEMONICS, UpdateWindowsEnableMenuMnemonics, NULL); + SDL_RemoveHintCallback(SDL_HINT_WINDOW_FRAME_USABLE_WHILE_CURSOR_HIDDEN, UpdateWindowFrameUsableWhileCursorHidden, NULL); + + WIN_SetRawMouseEnabled(_this, false); + WIN_SetRawKeyboardEnabled(_this, false); + WIN_QuitGameInput(_this); + +#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) + WIN_QuitModes(_this); + WIN_QuitDeviceNotification(); + WIN_QuitKeyboard(_this); + WIN_QuitMouse(_this); + + if (data->oleinitialized) { + OleUninitialize(); + data->oleinitialized = false; + } +#endif // !(defined(SDL_PLATFORM_XBOXONE) || defined(SDL_PLATFORM_XBOXSERIES)) + + if (data->coinitialized) { + WIN_CoUninitialize(); + data->coinitialized = false; + } +} + +#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) +#define D3D_DEBUG_INFO +#include + +#ifdef D3D_DEBUG_INFO +#ifndef D3D_SDK_VERSION +#define D3D_SDK_VERSION (32 | 0x80000000) +#endif +#ifndef D3D9b_SDK_VERSION +#define D3D9b_SDK_VERSION (31 | 0x80000000) +#endif +#else // +#ifndef D3D_SDK_VERSION +#define D3D_SDK_VERSION 32 +#endif +#ifndef D3D9b_SDK_VERSION +#define D3D9b_SDK_VERSION 31 +#endif +#endif + +bool D3D_LoadDLL(void **pD3DDLL, IDirect3D9 **pDirect3D9Interface) +{ + *pD3DDLL = SDL_LoadObject("D3D9.DLL"); + if (*pD3DDLL) { + /* *INDENT-OFF* */ // clang-format off + typedef IDirect3D9 *(WINAPI *Direct3DCreate9_t)(UINT SDKVersion); + typedef HRESULT (WINAPI* Direct3DCreate9Ex_t)(UINT SDKVersion, IDirect3D9Ex** ppD3D); + /* *INDENT-ON* */ // clang-format on + Direct3DCreate9_t Direct3DCreate9Func; + + if (SDL_GetHintBoolean(SDL_HINT_WINDOWS_USE_D3D9EX, false)) { + Direct3DCreate9Ex_t Direct3DCreate9ExFunc; + + Direct3DCreate9ExFunc = (Direct3DCreate9Ex_t)SDL_LoadFunction(*pD3DDLL, "Direct3DCreate9Ex"); + if (Direct3DCreate9ExFunc) { + IDirect3D9Ex *pDirect3D9ExInterface; + HRESULT hr = Direct3DCreate9ExFunc(D3D_SDK_VERSION, &pDirect3D9ExInterface); + if (SUCCEEDED(hr)) { + const GUID IDirect3D9_GUID = { 0x81bdcbca, 0x64d4, 0x426d, { 0xae, 0x8d, 0xad, 0x1, 0x47, 0xf4, 0x27, 0x5c } }; + hr = IDirect3D9Ex_QueryInterface(pDirect3D9ExInterface, &IDirect3D9_GUID, (void **)pDirect3D9Interface); + IDirect3D9Ex_Release(pDirect3D9ExInterface); + if (SUCCEEDED(hr)) { + return true; + } + } + } + } + + Direct3DCreate9Func = (Direct3DCreate9_t)SDL_LoadFunction(*pD3DDLL, "Direct3DCreate9"); + if (Direct3DCreate9Func) { + *pDirect3D9Interface = Direct3DCreate9Func(D3D_SDK_VERSION); + if (*pDirect3D9Interface) { + return true; + } + } + + SDL_UnloadObject(*pD3DDLL); + *pD3DDLL = NULL; + } + *pDirect3D9Interface = NULL; + return false; +} + +int SDL_GetDirect3D9AdapterIndex(SDL_DisplayID displayID) +{ + void *pD3DDLL; + IDirect3D9 *pD3D; + if (!D3D_LoadDLL(&pD3DDLL, &pD3D)) { + SDL_SetError("Unable to create Direct3D interface"); + return -1; + } else { + SDL_DisplayData *pData = SDL_GetDisplayDriverData(displayID); + int adapterIndex = D3DADAPTER_DEFAULT; + + if (!pData) { + SDL_SetError("Invalid display index"); + adapterIndex = -1; // make sure we return something invalid + } else { + char *displayName = WIN_StringToUTF8W(pData->DeviceName); + unsigned int count = IDirect3D9_GetAdapterCount(pD3D); + unsigned int i; + for (i = 0; i < count; i++) { + D3DADAPTER_IDENTIFIER9 id; + IDirect3D9_GetAdapterIdentifier(pD3D, i, 0, &id); + + if (SDL_strcmp(id.DeviceName, displayName) == 0) { + adapterIndex = i; + break; + } + } + SDL_free(displayName); + } + + // free up the D3D stuff we inited + IDirect3D9_Release(pD3D); + SDL_UnloadObject(pD3DDLL); + + return adapterIndex; + } +} +#endif // !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) + +bool SDL_GetDXGIOutputInfo(SDL_DisplayID displayID, int *adapterIndex, int *outputIndex) +{ +#ifndef HAVE_DXGI_H + if (adapterIndex) { + *adapterIndex = -1; + } + if (outputIndex) { + *outputIndex = -1; + } + return SDL_SetError("SDL was compiled without DXGI support due to missing dxgi.h header"); +#else + const SDL_VideoDevice *videodevice = SDL_GetVideoDevice(); + const SDL_VideoData *videodata = videodevice ? videodevice->internal : NULL; + SDL_DisplayData *pData = SDL_GetDisplayDriverData(displayID); + int nAdapter, nOutput; + IDXGIAdapter *pDXGIAdapter; + IDXGIOutput *pDXGIOutput; + + if (!adapterIndex) { + return SDL_InvalidParamError("adapterIndex"); + } + + if (!outputIndex) { + return SDL_InvalidParamError("outputIndex"); + } + + *adapterIndex = -1; + *outputIndex = -1; + + if (!pData) { + return SDL_SetError("Invalid display index"); + } + + if (!videodata || !videodata->pDXGIFactory) { + return SDL_SetError("Unable to create DXGI interface"); + } + + nAdapter = 0; + while (*adapterIndex == -1 && SUCCEEDED(IDXGIFactory_EnumAdapters(videodata->pDXGIFactory, nAdapter, &pDXGIAdapter))) { + nOutput = 0; + while (*adapterIndex == -1 && SUCCEEDED(IDXGIAdapter_EnumOutputs(pDXGIAdapter, nOutput, &pDXGIOutput))) { + DXGI_OUTPUT_DESC outputDesc; + if (SUCCEEDED(IDXGIOutput_GetDesc(pDXGIOutput, &outputDesc))) { + if (SDL_wcscmp(outputDesc.DeviceName, pData->DeviceName) == 0) { + *adapterIndex = nAdapter; + *outputIndex = nOutput; + } + } + IDXGIOutput_Release(pDXGIOutput); + nOutput++; + } + IDXGIAdapter_Release(pDXGIAdapter); + nAdapter++; + } + + if (*adapterIndex == -1) { + return SDL_SetError("Couldn't find matching adapter"); + } + return true; +#endif +} + +SDL_SystemTheme WIN_GetSystemTheme(void) +{ + SDL_SystemTheme theme = SDL_SYSTEM_THEME_LIGHT; + HKEY hKey; + DWORD dwType = REG_DWORD; + DWORD value = ~0U; + DWORD length = sizeof(value); + + // Technically this isn't the system theme, but it's the preference for applications + if (RegOpenKeyExW(HKEY_CURRENT_USER, L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize", 0, KEY_READ, &hKey) == ERROR_SUCCESS) { + if (RegQueryValueExW(hKey, L"AppsUseLightTheme", 0, &dwType, (LPBYTE)&value, &length) == ERROR_SUCCESS) { + if (value == 0) { + theme = SDL_SYSTEM_THEME_DARK; + } + } + RegCloseKey(hKey); + } + return theme; +} + +bool WIN_IsPerMonitorV2DPIAware(SDL_VideoDevice *_this) +{ +#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) + SDL_VideoData *data = _this->internal; + + if (data->AreDpiAwarenessContextsEqual && data->GetThreadDpiAwarenessContext) { + // Windows 10, version 1607 + return data->AreDpiAwarenessContextsEqual(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2, data->GetThreadDpiAwarenessContext()); + } +#endif + return false; +} + +#endif // SDL_VIDEO_DRIVER_WINDOWS diff --git a/contrib/SDL-3.2.8/src/video/windows/SDL_windowsvideo.h b/contrib/SDL-3.2.8/src/video/windows/SDL_windowsvideo.h new file mode 100644 index 0000000..53cfbad --- /dev/null +++ b/contrib/SDL-3.2.8/src/video/windows/SDL_windowsvideo.h @@ -0,0 +1,507 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include "SDL_internal.h" + +#ifndef SDL_windowsvideo_h_ +#define SDL_windowsvideo_h_ + +#include "../../core/windows/SDL_windows.h" + +#include "../SDL_sysvideo.h" + +#ifdef HAVE_DXGI_H +#define CINTERFACE +#define COBJMACROS +#include +#endif + +#if defined(_MSC_VER) && (_MSC_VER >= 1500) && !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) +#include +#else +#include "SDL_msctf.h" +#endif + +#include + +#define MAX_CANDLIST 10 +#define MAX_CANDLENGTH 256 +#define MAX_CANDSIZE (sizeof(WCHAR) * MAX_CANDLIST * MAX_CANDLENGTH) + +#include "SDL_windowsclipboard.h" +#include "SDL_windowsevents.h" +#include "SDL_windowsgameinput.h" +#include "SDL_windowsopengl.h" + +#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) +#include "SDL_windowsshape.h" +#include "SDL_windowskeyboard.h" +#include "SDL_windowsmodes.h" +#include "SDL_windowsmouse.h" +#include "SDL_windowsopengles.h" +#endif + +#include "SDL_windowswindow.h" + +#ifndef USER_DEFAULT_SCREEN_DPI +#define USER_DEFAULT_SCREEN_DPI 96 +#endif + +#if WINVER < 0x0601 +// Touch input definitions +#define TWF_FINETOUCH 1 +#define TWF_WANTPALM 2 + +#define TOUCHEVENTF_MOVE 0x0001 +#define TOUCHEVENTF_DOWN 0x0002 +#define TOUCHEVENTF_UP 0x0004 + +DECLARE_HANDLE(HTOUCHINPUT); + +typedef struct _TOUCHINPUT +{ + LONG x; + LONG y; + HANDLE hSource; + DWORD dwID; + DWORD dwFlags; + DWORD dwMask; + DWORD dwTime; + ULONG_PTR dwExtraInfo; + DWORD cxContact; + DWORD cyContact; +} TOUCHINPUT, *PTOUCHINPUT; + +// More-robust display information in Vista... +// This is a huge amount of data to be stuffing into three API calls. :( +typedef struct DISPLAYCONFIG_PATH_SOURCE_INFO +{ + LUID adapterId; + UINT32 id; + union + { + UINT32 modeInfoIdx; + struct + { + UINT32 cloneGroupId : 16; + UINT32 sourceModeInfoIdx : 16; + } DUMMYSTRUCTNAME; + } DUMMYUNIONNAME; + + UINT32 statusFlags; +} DISPLAYCONFIG_PATH_SOURCE_INFO; + +typedef struct DISPLAYCONFIG_RATIONAL +{ + UINT32 Numerator; + UINT32 Denominator; +} DISPLAYCONFIG_RATIONAL; + +typedef struct DISPLAYCONFIG_PATH_TARGET_INFO +{ + LUID adapterId; + UINT32 id; + union + { + UINT32 modeInfoIdx; + struct + { + UINT32 desktopModeInfoIdx : 16; + UINT32 targetModeInfoIdx : 16; + } DUMMYSTRUCTNAME; + } DUMMYUNIONNAME; + UINT32 /*DISPLAYCONFIG_VIDEO_OUTPUT_TECHNOLOGY*/ outputTechnology; + UINT32 /*DISPLAYCONFIG_ROTATION*/ rotation; + UINT32 /*DISPLAYCONFIG_SCALING*/ scaling; + DISPLAYCONFIG_RATIONAL refreshRate; + UINT32 /*DISPLAYCONFIG_SCANLINE_ORDERING*/ scanLineOrdering; + BOOL targetAvailable; + UINT32 statusFlags; +} DISPLAYCONFIG_PATH_TARGET_INFO; + +typedef struct DISPLAYCONFIG_PATH_INFO +{ + DISPLAYCONFIG_PATH_SOURCE_INFO sourceInfo; + DISPLAYCONFIG_PATH_TARGET_INFO targetInfo; + UINT32 flags; +} DISPLAYCONFIG_PATH_INFO; + +typedef enum +{ + DISPLAYCONFIG_MODE_INFO_TYPE_SOURCE = 1, + DISPLAYCONFIG_MODE_INFO_TYPE_TARGET = 2, + DISPLAYCONFIG_MODE_INFO_TYPE_DESKTOP_IMAGE = 3, + DISPLAYCONFIG_MODE_INFO_TYPE_FORCE_UINT32 = 0xFFFFFFFF +} DISPLAYCONFIG_MODE_INFO_TYPE; + +typedef struct DISPLAYCONFIG_2DREGION +{ + UINT32 cx; + UINT32 cy; +} DISPLAYCONFIG_2DREGION; + +typedef struct DISPLAYCONFIG_VIDEO_SIGNAL_INFO +{ + UINT64 pixelRate; + DISPLAYCONFIG_RATIONAL hSyncFreq; + DISPLAYCONFIG_RATIONAL vSyncFreq; + DISPLAYCONFIG_2DREGION activeSize; + DISPLAYCONFIG_2DREGION totalSize; + + union + { + struct + { + UINT32 videoStandard : 16; + + // Vertical refresh frequency divider + UINT32 vSyncFreqDivider : 6; + + UINT32 reserved : 10; + } AdditionalSignalInfo; + + UINT32 videoStandard; + } DUMMYUNIONNAME; + + // Scan line ordering (e.g. progressive, interlaced). + UINT32 /*DISPLAYCONFIG_SCANLINE_ORDERING*/ scanLineOrdering; +} DISPLAYCONFIG_VIDEO_SIGNAL_INFO; + +typedef struct DISPLAYCONFIG_SOURCE_MODE +{ + UINT32 width; + UINT32 height; + UINT32 /*DISPLAYCONFIG_PIXELFORMAT*/ pixelFormat; + POINTL position; +} DISPLAYCONFIG_SOURCE_MODE; + +typedef struct DISPLAYCONFIG_TARGET_MODE +{ + DISPLAYCONFIG_VIDEO_SIGNAL_INFO targetVideoSignalInfo; +} DISPLAYCONFIG_TARGET_MODE; + +typedef struct DISPLAYCONFIG_DESKTOP_IMAGE_INFO +{ + POINTL PathSourceSize; + RECTL DesktopImageRegion; + RECTL DesktopImageClip; +} DISPLAYCONFIG_DESKTOP_IMAGE_INFO; + +typedef struct DISPLAYCONFIG_MODE_INFO +{ + DISPLAYCONFIG_MODE_INFO_TYPE infoType; + UINT32 id; + LUID adapterId; + union + { + DISPLAYCONFIG_TARGET_MODE targetMode; + DISPLAYCONFIG_SOURCE_MODE sourceMode; + DISPLAYCONFIG_DESKTOP_IMAGE_INFO desktopImageInfo; + } DUMMYUNIONNAME; +} DISPLAYCONFIG_MODE_INFO; + +typedef enum DISPLAYCONFIG_TOPOLOGY_ID +{ + DISPLAYCONFIG_TOPOLOGY_INTERNAL = 0x00000001, + DISPLAYCONFIG_TOPOLOGY_CLONE = 0x00000002, + DISPLAYCONFIG_TOPOLOGY_EXTEND = 0x00000004, + DISPLAYCONFIG_TOPOLOGY_EXTERNAL = 0x00000008, + DISPLAYCONFIG_TOPOLOGY_FORCE_UINT32 = 0xFFFFFFFF +} DISPLAYCONFIG_TOPOLOGY_ID; + +typedef enum +{ + DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_NAME = 1, + DISPLAYCONFIG_DEVICE_INFO_GET_TARGET_NAME = 2, + DISPLAYCONFIG_DEVICE_INFO_GET_TARGET_PREFERRED_MODE = 3, + DISPLAYCONFIG_DEVICE_INFO_GET_ADAPTER_NAME = 4, + DISPLAYCONFIG_DEVICE_INFO_SET_TARGET_PERSISTENCE = 5, + DISPLAYCONFIG_DEVICE_INFO_GET_TARGET_BASE_TYPE = 6, + DISPLAYCONFIG_DEVICE_INFO_GET_SUPPORT_VIRTUAL_RESOLUTION = 7, + DISPLAYCONFIG_DEVICE_INFO_SET_SUPPORT_VIRTUAL_RESOLUTION = 8, + DISPLAYCONFIG_DEVICE_INFO_GET_ADVANCED_COLOR_INFO = 9, + DISPLAYCONFIG_DEVICE_INFO_SET_ADVANCED_COLOR_STATE = 10, + DISPLAYCONFIG_DEVICE_INFO_GET_SDR_WHITE_LEVEL = 11, + DISPLAYCONFIG_DEVICE_INFO_FORCE_UINT32 = 0xFFFFFFFF +} DISPLAYCONFIG_DEVICE_INFO_TYPE; + +typedef struct DISPLAYCONFIG_DEVICE_INFO_HEADER +{ + DISPLAYCONFIG_DEVICE_INFO_TYPE type; + UINT32 size; + LUID adapterId; + UINT32 id; +} DISPLAYCONFIG_DEVICE_INFO_HEADER; + +typedef struct DISPLAYCONFIG_SOURCE_DEVICE_NAME +{ + DISPLAYCONFIG_DEVICE_INFO_HEADER header; + WCHAR viewGdiDeviceName[CCHDEVICENAME]; +} DISPLAYCONFIG_SOURCE_DEVICE_NAME; + +typedef struct DISPLAYCONFIG_TARGET_DEVICE_NAME_FLAGS +{ + union + { + struct + { + UINT32 friendlyNameFromEdid : 1; + UINT32 friendlyNameForced : 1; + UINT32 edidIdsValid : 1; + UINT32 reserved : 29; + } DUMMYSTRUCTNAME; + UINT32 value; + } DUMMYUNIONNAME; +} DISPLAYCONFIG_TARGET_DEVICE_NAME_FLAGS; + +typedef struct DISPLAYCONFIG_TARGET_DEVICE_NAME +{ + DISPLAYCONFIG_DEVICE_INFO_HEADER header; + DISPLAYCONFIG_TARGET_DEVICE_NAME_FLAGS flags; + UINT32 /*DISPLAYCONFIG_VIDEO_OUTPUT_TECHNOLOGY*/ outputTechnology; + UINT16 edidManufactureId; + UINT16 edidProductCodeId; + UINT32 connectorInstance; + WCHAR monitorFriendlyDeviceName[64]; + WCHAR monitorDevicePath[128]; +} DISPLAYCONFIG_TARGET_DEVICE_NAME; + +#define QDC_ONLY_ACTIVE_PATHS 0x00000002 + +#endif // WINVER < 0x0601 + +#ifndef HAVE_SHELLSCALINGAPI_H + +typedef enum MONITOR_DPI_TYPE +{ + MDT_EFFECTIVE_DPI = 0, + MDT_ANGULAR_DPI = 1, + MDT_RAW_DPI = 2, + MDT_DEFAULT = MDT_EFFECTIVE_DPI +} MONITOR_DPI_TYPE; + +typedef enum PROCESS_DPI_AWARENESS +{ + PROCESS_DPI_UNAWARE = 0, + PROCESS_SYSTEM_DPI_AWARE = 1, + PROCESS_PER_MONITOR_DPI_AWARE = 2 +} PROCESS_DPI_AWARENESS; + +#else +#include +#endif + +#ifndef _DPI_AWARENESS_CONTEXTS_ + +typedef enum DPI_AWARENESS +{ + DPI_AWARENESS_INVALID = -1, + DPI_AWARENESS_UNAWARE = 0, + DPI_AWARENESS_SYSTEM_AWARE = 1, + DPI_AWARENESS_PER_MONITOR_AWARE = 2 +} DPI_AWARENESS; + +DECLARE_HANDLE(DPI_AWARENESS_CONTEXT); + +#define DPI_AWARENESS_CONTEXT_UNAWARE ((DPI_AWARENESS_CONTEXT)-1) +#define DPI_AWARENESS_CONTEXT_SYSTEM_AWARE ((DPI_AWARENESS_CONTEXT)-2) +#define DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE ((DPI_AWARENESS_CONTEXT)-3) + +#endif // _DPI_AWARENESS_CONTEXTS_ + +// Windows 10 Creators Update +#if NTDDI_VERSION < 0x0A000003 +#define DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 ((DPI_AWARENESS_CONTEXT)-4) +#endif // NTDDI_VERSION < 0x0A000003 + +// Windows 10 version 1809 +#if NTDDI_VERSION < 0x0A000006 +#define DPI_AWARENESS_CONTEXT_UNAWARE_GDISCALED ((DPI_AWARENESS_CONTEXT)-5) +#endif // NTDDI_VERSION < 0x0A000006 + +typedef BOOL (*PFNSHFullScreen)(HWND, DWORD); +typedef void (*PFCoordTransform)(SDL_Window *, POINT *); + +typedef struct +{ + void **lpVtbl; + int refcount; + void *data; +} TSFSink; + +#ifndef SDL_DISABLE_WINDOWS_IME +// Definition from Win98DDK version of IMM.H +typedef struct tagINPUTCONTEXT2 +{ + HWND hWnd; + BOOL fOpen; + POINT ptStatusWndPos; + POINT ptSoftKbdPos; + DWORD fdwConversion; + DWORD fdwSentence; + union + { + LOGFONTA A; + LOGFONTW W; + } lfFont; + COMPOSITIONFORM cfCompForm; + CANDIDATEFORM cfCandForm[4]; + HIMCC hCompStr; + HIMCC hCandInfo; + HIMCC hGuideLine; + HIMCC hPrivate; + DWORD dwNumMsgBuf; + HIMCC hMsgBuf; + DWORD fdwInit; + DWORD dwReserve[3]; +} INPUTCONTEXT2, *PINPUTCONTEXT2, NEAR *NPINPUTCONTEXT2, FAR *LPINPUTCONTEXT2; +#endif + +// Private display data + +struct SDL_VideoData +{ + int render; + + bool coinitialized; +#if !(defined(SDL_PLATFORM_XBOXONE) || defined(SDL_PLATFORM_XBOXSERIES)) + bool oleinitialized; +#endif // !(defined(SDL_PLATFORM_XBOXONE) || defined(SDL_PLATFORM_XBOXSERIES)) + + DWORD clipboard_count; + +#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) // Xbox doesn't support user32/shcore + // Touch input functions + SDL_SharedObject *userDLL; + /* *INDENT-OFF* */ // clang-format off + BOOL (WINAPI *CloseTouchInputHandle)( HTOUCHINPUT ); + BOOL (WINAPI *GetTouchInputInfo)( HTOUCHINPUT, UINT, PTOUCHINPUT, int ); + BOOL (WINAPI *RegisterTouchWindow)( HWND, ULONG ); + BOOL (WINAPI *SetProcessDPIAware)( void ); + BOOL (WINAPI *SetProcessDpiAwarenessContext)( DPI_AWARENESS_CONTEXT ); + DPI_AWARENESS_CONTEXT (WINAPI *SetThreadDpiAwarenessContext)( DPI_AWARENESS_CONTEXT ); + DPI_AWARENESS_CONTEXT (WINAPI *GetThreadDpiAwarenessContext)( void ); + DPI_AWARENESS (WINAPI *GetAwarenessFromDpiAwarenessContext)( DPI_AWARENESS_CONTEXT ); + BOOL (WINAPI *EnableNonClientDpiScaling)( HWND ); + BOOL (WINAPI *AdjustWindowRectExForDpi)( LPRECT, DWORD, BOOL, DWORD, UINT ); + UINT (WINAPI *GetDpiForWindow)( HWND ); + BOOL (WINAPI *AreDpiAwarenessContextsEqual)(DPI_AWARENESS_CONTEXT, DPI_AWARENESS_CONTEXT); + BOOL (WINAPI *IsValidDpiAwarenessContext)(DPI_AWARENESS_CONTEXT); + // DisplayConfig functions + LONG (WINAPI *GetDisplayConfigBufferSizes)( UINT32, UINT32*, UINT32* ); + LONG (WINAPI *QueryDisplayConfig)( UINT32, UINT32*, DISPLAYCONFIG_PATH_INFO*, UINT32*, DISPLAYCONFIG_MODE_INFO*, DISPLAYCONFIG_TOPOLOGY_ID*); + LONG (WINAPI *DisplayConfigGetDeviceInfo)( DISPLAYCONFIG_DEVICE_INFO_HEADER*); + /* *INDENT-ON* */ // clang-format on + + SDL_SharedObject *shcoreDLL; + /* *INDENT-OFF* */ // clang-format off + HRESULT (WINAPI *GetDpiForMonitor)( HMONITOR hmonitor, + MONITOR_DPI_TYPE dpiType, + UINT *dpiX, + UINT *dpiY ); + HRESULT (WINAPI *SetProcessDpiAwareness)(PROCESS_DPI_AWARENESS dpiAwareness); + BOOL (WINAPI *GetPointerType)(UINT32 pointerId, POINTER_INPUT_TYPE *pointerType); + BOOL (WINAPI *GetPointerPenInfo)(UINT32 pointerId, POINTER_PEN_INFO *penInfo); + + /* *INDENT-ON* */ // clang-format on +#endif // !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) + +#ifdef HAVE_DXGI_H + SDL_SharedObject *dxgiDLL; + IDXGIFactory *pDXGIFactory; +#endif + + bool cleared; + + BYTE *rawinput; + UINT rawinput_offset; + UINT rawinput_size; + UINT rawinput_count; + Uint64 last_rawinput_poll; + SDL_Point last_raw_mouse_position; + bool raw_mouse_enabled; + bool raw_keyboard_enabled; + bool pending_E1_key_sequence; + Uint32 raw_input_enabled; + + WIN_GameInputData *gameinput_context; + +#ifndef SDL_DISABLE_WINDOWS_IME + bool ime_initialized; + bool ime_enabled; + bool ime_available; + bool ime_internal_composition; + bool ime_internal_candidates; + HWND ime_hwnd_main; + HWND ime_hwnd_current; + bool ime_needs_clear_composition; + HIMC ime_himc; + + WCHAR *ime_composition; + int ime_composition_length; + WCHAR ime_readingstring[16]; + int ime_cursor; + int ime_selected_start; + int ime_selected_length; + + bool ime_candidates_open; + bool ime_update_candidates; + char *ime_candidates[MAX_CANDLIST]; + int ime_candcount; + DWORD ime_candref; + DWORD ime_candsel; + int ime_candlistindexbase; + bool ime_horizontal_candidates; +#endif + +#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) + COMPOSITIONFORM ime_composition_area; + CANDIDATEFORM ime_candidate_area; +#endif // !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) + +#ifndef SDL_DISABLE_WINDOWS_IME + HKL ime_hkl; + SDL_SharedObject *ime_himm32; + /* *INDENT-OFF* */ // clang-format off + UINT (WINAPI *GetReadingString)(HIMC himc, UINT uReadingBufLen, LPWSTR lpwReadingBuf, PINT pnErrorIndex, BOOL *pfIsVertical, PUINT puMaxReadingLen); + BOOL (WINAPI *ShowReadingWindow)(HIMC himc, BOOL bShow); + LPINPUTCONTEXT2 (WINAPI *ImmLockIMC)(HIMC himc); + BOOL (WINAPI *ImmUnlockIMC)(HIMC himc); + LPVOID (WINAPI *ImmLockIMCC)(HIMCC himcc); + BOOL (WINAPI *ImmUnlockIMCC)(HIMCC himcc); + /* *INDENT-ON* */ // clang-format on + +#endif // !SDL_DISABLE_WINDOWS_IME + + BYTE pre_hook_key_state[256]; + UINT _SDL_WAKEUP; +}; + +extern bool g_WindowsEnableMessageLoop; +extern bool g_WindowsEnableMenuMnemonics; +extern bool g_WindowFrameUsableWhileCursorHidden; + +typedef struct IDirect3D9 IDirect3D9; +extern bool D3D_LoadDLL(void **pD3DDLL, IDirect3D9 **pDirect3D9Interface); + +extern SDL_SystemTheme WIN_GetSystemTheme(void); +extern bool WIN_IsPerMonitorV2DPIAware(SDL_VideoDevice *_this); + +#endif // SDL_windowsvideo_h_ diff --git a/contrib/SDL-3.2.8/src/video/windows/SDL_windowsvulkan.c b/contrib/SDL-3.2.8/src/video/windows/SDL_windowsvulkan.c new file mode 100644 index 0000000..0b16d55 --- /dev/null +++ b/contrib/SDL-3.2.8/src/video/windows/SDL_windowsvulkan.c @@ -0,0 +1,195 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +/* + * @author Mark Callow, www.edgewise-consulting.com. Based on Jacob Lifshay's + * SDL_x11vulkan.c. + */ + +#include "SDL_internal.h" + +#if defined(SDL_VIDEO_VULKAN) && defined(SDL_VIDEO_DRIVER_WINDOWS) + +#include "../SDL_vulkan_internal.h" + +#include "SDL_windowsvideo.h" +#include "SDL_windowswindow.h" + +#include "SDL_windowsvulkan.h" + +bool WIN_Vulkan_LoadLibrary(SDL_VideoDevice *_this, const char *path) +{ + VkExtensionProperties *extensions = NULL; + Uint32 extensionCount = 0; + Uint32 i; + bool hasSurfaceExtension = false; + bool hasWin32SurfaceExtension = false; + PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr = NULL; + if (_this->vulkan_config.loader_handle) { + return SDL_SetError("Vulkan already loaded"); + } + + // Load the Vulkan loader library + if (!path) { + path = SDL_GetHint(SDL_HINT_VULKAN_LIBRARY); + } + if (!path) { + path = "vulkan-1.dll"; + } + _this->vulkan_config.loader_handle = SDL_LoadObject(path); + if (!_this->vulkan_config.loader_handle) { + return false; + } + SDL_strlcpy(_this->vulkan_config.loader_path, path, + SDL_arraysize(_this->vulkan_config.loader_path)); + vkGetInstanceProcAddr = (PFN_vkGetInstanceProcAddr)SDL_LoadFunction( + _this->vulkan_config.loader_handle, "vkGetInstanceProcAddr"); + if (!vkGetInstanceProcAddr) { + goto fail; + } + _this->vulkan_config.vkGetInstanceProcAddr = (SDL_FunctionPointer)vkGetInstanceProcAddr; + _this->vulkan_config.vkEnumerateInstanceExtensionProperties = + (SDL_FunctionPointer)vkGetInstanceProcAddr( + VK_NULL_HANDLE, "vkEnumerateInstanceExtensionProperties"); + if (!_this->vulkan_config.vkEnumerateInstanceExtensionProperties) { + goto fail; + } + extensions = SDL_Vulkan_CreateInstanceExtensionsList( + (PFN_vkEnumerateInstanceExtensionProperties) + _this->vulkan_config.vkEnumerateInstanceExtensionProperties, + &extensionCount); + if (!extensions) { + goto fail; + } + for (i = 0; i < extensionCount; i++) { + if (SDL_strcmp(VK_KHR_SURFACE_EXTENSION_NAME, extensions[i].extensionName) == 0) { + hasSurfaceExtension = true; + } else if (SDL_strcmp(VK_KHR_WIN32_SURFACE_EXTENSION_NAME, extensions[i].extensionName) == 0) { + hasWin32SurfaceExtension = true; + } + } + SDL_free(extensions); + if (!hasSurfaceExtension) { + SDL_SetError("Installed Vulkan doesn't implement the " VK_KHR_SURFACE_EXTENSION_NAME " extension"); + goto fail; + } else if (!hasWin32SurfaceExtension) { + SDL_SetError("Installed Vulkan doesn't implement the " VK_KHR_WIN32_SURFACE_EXTENSION_NAME "extension"); + goto fail; + } + return true; + +fail: + SDL_UnloadObject(_this->vulkan_config.loader_handle); + _this->vulkan_config.loader_handle = NULL; + return false; +} + +void WIN_Vulkan_UnloadLibrary(SDL_VideoDevice *_this) +{ + if (_this->vulkan_config.loader_handle) { + SDL_UnloadObject(_this->vulkan_config.loader_handle); + _this->vulkan_config.loader_handle = NULL; + } +} + +char const* const* WIN_Vulkan_GetInstanceExtensions(SDL_VideoDevice *_this, + Uint32 *count) +{ + static const char *const extensionsForWin32[] = { + VK_KHR_SURFACE_EXTENSION_NAME, VK_KHR_WIN32_SURFACE_EXTENSION_NAME + }; + if (count) { + *count = SDL_arraysize(extensionsForWin32); + } + return extensionsForWin32; +} + +bool WIN_Vulkan_CreateSurface(SDL_VideoDevice *_this, + SDL_Window *window, + VkInstance instance, + const struct VkAllocationCallbacks *allocator, + VkSurfaceKHR *surface) +{ + SDL_WindowData *windowData = window->internal; + PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr = + (PFN_vkGetInstanceProcAddr)_this->vulkan_config.vkGetInstanceProcAddr; + PFN_vkCreateWin32SurfaceKHR vkCreateWin32SurfaceKHR = + (PFN_vkCreateWin32SurfaceKHR)vkGetInstanceProcAddr( + instance, + "vkCreateWin32SurfaceKHR"); + VkWin32SurfaceCreateInfoKHR createInfo; + VkResult result; + + if (!_this->vulkan_config.loader_handle) { + return SDL_SetError("Vulkan is not loaded"); + } + + if (!vkCreateWin32SurfaceKHR) { + return SDL_SetError(VK_KHR_WIN32_SURFACE_EXTENSION_NAME + " extension is not enabled in the Vulkan instance."); + } + createInfo.sType = VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR; + createInfo.pNext = NULL; + createInfo.flags = 0; + createInfo.hinstance = windowData->hinstance; + createInfo.hwnd = windowData->hwnd; + result = vkCreateWin32SurfaceKHR(instance, &createInfo, allocator, surface); + if (result != VK_SUCCESS) { + return SDL_SetError("vkCreateWin32SurfaceKHR failed: %s", SDL_Vulkan_GetResultString(result)); + } + return true; +} + +void WIN_Vulkan_DestroySurface(SDL_VideoDevice *_this, + VkInstance instance, + VkSurfaceKHR surface, + const struct VkAllocationCallbacks *allocator) +{ + if (_this->vulkan_config.loader_handle) { + SDL_Vulkan_DestroySurface_Internal(_this->vulkan_config.vkGetInstanceProcAddr, instance, surface, allocator); + } +} + +bool WIN_Vulkan_GetPresentationSupport(SDL_VideoDevice *_this, + VkInstance instance, + VkPhysicalDevice physicalDevice, + Uint32 queueFamilyIndex) +{ + PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr = + (PFN_vkGetInstanceProcAddr)_this->vulkan_config.vkGetInstanceProcAddr; + PFN_vkGetPhysicalDeviceWin32PresentationSupportKHR vkGetPhysicalDeviceWin32PresentationSupportKHR = + (PFN_vkGetPhysicalDeviceWin32PresentationSupportKHR)vkGetInstanceProcAddr( + instance, + "vkGetPhysicalDeviceWin32PresentationSupportKHR"); + + if (!_this->vulkan_config.loader_handle) { + return SDL_SetError("Vulkan is not loaded"); + } + + if (!vkGetPhysicalDeviceWin32PresentationSupportKHR) { + return SDL_SetError(VK_KHR_WIN32_SURFACE_EXTENSION_NAME " extension is not enabled in the Vulkan instance."); + } + + return vkGetPhysicalDeviceWin32PresentationSupportKHR(physicalDevice, + queueFamilyIndex); +} + +#endif diff --git a/contrib/SDL-3.2.8/src/video/windows/SDL_windowsvulkan.h b/contrib/SDL-3.2.8/src/video/windows/SDL_windowsvulkan.h new file mode 100644 index 0000000..34f3a66 --- /dev/null +++ b/contrib/SDL-3.2.8/src/video/windows/SDL_windowsvulkan.h @@ -0,0 +1,55 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +/* + * @author Mark Callow, www.edgewise-consulting.com. Based on Jacob Lifshay's + * SDL_x11vulkan.h. + */ + +#include "SDL_internal.h" + +#ifndef SDL_windowsvulkan_h_ +#define SDL_windowsvulkan_h_ + +#include + +#if defined(SDL_VIDEO_VULKAN) && defined(SDL_VIDEO_DRIVER_WINDOWS) + +extern bool WIN_Vulkan_LoadLibrary(SDL_VideoDevice *_this, const char *path); +extern void WIN_Vulkan_UnloadLibrary(SDL_VideoDevice *_this); +extern char const* const* WIN_Vulkan_GetInstanceExtensions(SDL_VideoDevice *_this, Uint32 *count); +extern bool WIN_Vulkan_CreateSurface(SDL_VideoDevice *_this, + SDL_Window *window, + VkInstance instance, + const struct VkAllocationCallbacks *allocator, + VkSurfaceKHR *surface); +extern void WIN_Vulkan_DestroySurface(SDL_VideoDevice *_this, + VkInstance instance, + VkSurfaceKHR surface, + const struct VkAllocationCallbacks *allocator); +bool WIN_Vulkan_GetPresentationSupport(SDL_VideoDevice *_this, + VkInstance instance, + VkPhysicalDevice physicalDevice, + Uint32 queueFamilyIndex); + +#endif + +#endif // SDL_windowsvulkan_h_ diff --git a/contrib/SDL-3.2.8/src/video/windows/SDL_windowswindow.c b/contrib/SDL-3.2.8/src/video/windows/SDL_windowswindow.c new file mode 100644 index 0000000..36c8de7 --- /dev/null +++ b/contrib/SDL-3.2.8/src/video/windows/SDL_windowswindow.c @@ -0,0 +1,2435 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include "SDL_internal.h" + +#ifdef SDL_VIDEO_DRIVER_WINDOWS + +#include "../../core/windows/SDL_windows.h" + +#include "../../SDL_hints_c.h" +#include "../../events/SDL_dropevents_c.h" +#include "../../events/SDL_keyboard_c.h" +#include "../../events/SDL_mouse_c.h" +#include "../../events/SDL_windowevents_c.h" +#include "../SDL_pixels_c.h" +#include "../SDL_sysvideo.h" + +#include "SDL_windowsvideo.h" +#include "SDL_windowswindow.h" + +// Dropfile support +#include + +// DWM setting support +typedef HRESULT (WINAPI *DwmSetWindowAttribute_t)(HWND hwnd, DWORD dwAttribute, LPCVOID pvAttribute, DWORD cbAttribute); +typedef HRESULT (WINAPI *DwmGetWindowAttribute_t)(HWND hwnd, DWORD dwAttribute, PVOID pvAttribute, DWORD cbAttribute); + +// Dark mode support +typedef enum { + UXTHEME_APPMODE_DEFAULT, + UXTHEME_APPMODE_ALLOW_DARK, + UXTHEME_APPMODE_FORCE_DARK, + UXTHEME_APPMODE_FORCE_LIGHT, + UXTHEME_APPMODE_MAX +} UxthemePreferredAppMode; + +typedef enum { + WCA_UNDEFINED = 0, + WCA_USEDARKMODECOLORS = 26, + WCA_LAST = 27 +} WINDOWCOMPOSITIONATTRIB; + +typedef struct { + WINDOWCOMPOSITIONATTRIB Attrib; + PVOID pvData; + SIZE_T cbData; +} WINDOWCOMPOSITIONATTRIBDATA; + +typedef struct { + ULONG dwOSVersionInfoSize; + ULONG dwMajorVersion; + ULONG dwMinorVersion; + ULONG dwBuildNumber; + ULONG dwPlatformId; + WCHAR szCSDVersion[128]; +} NT_OSVERSIONINFOW; + +typedef bool (WINAPI *ShouldAppsUseDarkMode_t)(void); +typedef void (WINAPI *AllowDarkModeForWindow_t)(HWND, bool); +typedef void (WINAPI *AllowDarkModeForApp_t)(bool); +typedef void (WINAPI *RefreshImmersiveColorPolicyState_t)(void); +typedef UxthemePreferredAppMode (WINAPI *SetPreferredAppMode_t)(UxthemePreferredAppMode); +typedef BOOL (WINAPI *SetWindowCompositionAttribute_t)(HWND, const WINDOWCOMPOSITIONATTRIBDATA *); +typedef void (NTAPI *RtlGetVersion_t)(NT_OSVERSIONINFOW *); + +// Corner rounding support (Win 11+) +#ifndef DWMWA_WINDOW_CORNER_PREFERENCE +#define DWMWA_WINDOW_CORNER_PREFERENCE 33 +#endif +typedef enum { + DWMWCP_DEFAULT = 0, + DWMWCP_DONOTROUND = 1, + DWMWCP_ROUND = 2, + DWMWCP_ROUNDSMALL = 3 +} DWM_WINDOW_CORNER_PREFERENCE; + +// Border Color support (Win 11+) +#ifndef DWMWA_BORDER_COLOR +#define DWMWA_BORDER_COLOR 34 +#endif + +#ifndef DWMWA_COLOR_DEFAULT +#define DWMWA_COLOR_DEFAULT 0xFFFFFFFF +#endif + +#ifndef DWMWA_COLOR_NONE +#define DWMWA_COLOR_NONE 0xFFFFFFFE +#endif + +// Transparent window support +#ifndef DWM_BB_ENABLE +#define DWM_BB_ENABLE 0x00000001 +#endif +#ifndef DWM_BB_BLURREGION +#define DWM_BB_BLURREGION 0x00000002 +#endif +typedef struct +{ + DWORD flags; + BOOL enable; + HRGN blur_region; + BOOL transition_on_maxed; +} DWM_BLURBEHIND; +typedef HRESULT(WINAPI *DwmEnableBlurBehindWindow_t)(HWND hwnd, const DWM_BLURBEHIND *pBlurBehind); + +// Windows CE compatibility +#ifndef SWP_NOCOPYBITS +#define SWP_NOCOPYBITS 0 +#endif + +/* An undocumented message to create a popup system menu + * - wParam is always 0 + * - lParam = MAKELONG(x, y) where x and y are the screen coordinates where the menu should be displayed + */ +#ifndef WM_POPUPSYSTEMMENU +#define WM_POPUPSYSTEMMENU 0x313 +#endif + +// #define HIGHDPI_DEBUG + +// Fake window to help with DirectInput events. +HWND SDL_HelperWindow = NULL; +static const TCHAR *SDL_HelperWindowClassName = TEXT("SDLHelperWindowInputCatcher"); +static const TCHAR *SDL_HelperWindowName = TEXT("SDLHelperWindowInputMsgWindow"); +static ATOM SDL_HelperWindowClass = 0; + +/* For borderless Windows, still want the following flag: + - WS_MINIMIZEBOX: window will respond to Windows minimize commands sent to all windows, such as windows key + m, shaking title bar, etc. + Additionally, non-fullscreen windows can add: + - WS_CAPTION: this seems to enable the Windows minimize animation + - WS_SYSMENU: enables system context menu on task bar + This will also cause the task bar to overlap the window and other windowed behaviors, so only use this for windows that shouldn't appear to be fullscreen + - WS_THICKFRAME: allows hit-testing to resize window (doesn't actually add a frame to a borderless window). + - WS_MAXIMIZEBOX: window will respond to Windows maximize commands sent to all windows, and the window will fill the usable desktop area rather than the whole screen + */ + +#define STYLE_BASIC (WS_CLIPSIBLINGS | WS_CLIPCHILDREN) +#define STYLE_FULLSCREEN (WS_POPUP | WS_MINIMIZEBOX) +#define STYLE_BORDERLESS (WS_POPUP | WS_MINIMIZEBOX) +#define STYLE_BORDERLESS_WINDOWED (WS_POPUP | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX) +#define STYLE_NORMAL (WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX) +#define STYLE_RESIZABLE (WS_THICKFRAME | WS_MAXIMIZEBOX) +#define STYLE_MASK (STYLE_FULLSCREEN | STYLE_BORDERLESS | STYLE_NORMAL | STYLE_RESIZABLE) + +static DWORD GetWindowStyle(SDL_Window *window) +{ + DWORD style = 0; + + if (SDL_WINDOW_IS_POPUP(window)) { + style |= WS_POPUP; + } else if (window->flags & SDL_WINDOW_FULLSCREEN) { + style |= STYLE_FULLSCREEN; + } else { + if (window->flags & SDL_WINDOW_BORDERLESS) { + /* This behavior more closely matches other platform where the window is borderless + but still interacts with the window manager (e.g. task bar shows above it, it can + be resized to fit within usable desktop area, etc.) + */ + if (SDL_GetHintBoolean("SDL_BORDERLESS_WINDOWED_STYLE", true)) { + style |= STYLE_BORDERLESS_WINDOWED; + } else { + style |= STYLE_BORDERLESS; + } + } else { + style |= STYLE_NORMAL; + } + + /* The WS_MAXIMIZEBOX style flag needs to be retained for as long as the window is maximized, + * or restoration from minimized can fail, and leaving maximized can result in an odd size. + */ + if (window->flags & SDL_WINDOW_RESIZABLE) { + /* You can have a borderless resizable window, but Windows doesn't always draw it correctly, + see https://bugzilla.libsdl.org/show_bug.cgi?id=4466 + */ + if (!(window->flags & SDL_WINDOW_BORDERLESS) || + SDL_GetHintBoolean("SDL_BORDERLESS_RESIZABLE_STYLE", true)) { + style |= STYLE_RESIZABLE; + } + } + + if (window->internal && window->internal->force_ws_maximizebox) { + /* Even if the resizable flag is cleared, WS_MAXIMIZEBOX is still needed as long + * as the window is maximized, or de-maximizing or minimizing and restoring the + * maximized window can result in the window disappearing or being the wrong size. + */ + style |= WS_MAXIMIZEBOX; + } + + // Need to set initialize minimize style, or when we call ShowWindow with WS_MINIMIZE it will activate a random window + if (window->flags & SDL_WINDOW_MINIMIZED) { + style |= WS_MINIMIZE; + } + } + return style; +} + +static DWORD GetWindowStyleEx(SDL_Window *window) +{ + DWORD style = 0; + + if (SDL_WINDOW_IS_POPUP(window) || (window->flags & SDL_WINDOW_UTILITY)) { + style |= WS_EX_TOOLWINDOW; + } + if (SDL_WINDOW_IS_POPUP(window) || (window->flags & SDL_WINDOW_NOT_FOCUSABLE)) { + style |= WS_EX_NOACTIVATE; + } + return style; +} + +/** + * Returns arguments to pass to SetWindowPos - the window rect, including frame, in Windows coordinates. + * Can be called before we have a HWND. + */ +static bool WIN_AdjustWindowRectWithStyle(SDL_Window *window, DWORD style, DWORD styleEx, BOOL menu, int *x, int *y, int *width, int *height, SDL_WindowRect rect_type) +{ + SDL_VideoData *videodata = SDL_GetVideoDevice() ? SDL_GetVideoDevice()->internal : NULL; + RECT rect; + + // Client rect, in points + switch (rect_type) { + case SDL_WINDOWRECT_CURRENT: + SDL_RelativeToGlobalForWindow(window, window->x, window->y, x, y); + *width = window->w; + *height = window->h; + break; + case SDL_WINDOWRECT_WINDOWED: + SDL_RelativeToGlobalForWindow(window, window->windowed.x, window->windowed.y, x, y); + *width = window->windowed.w; + *height = window->windowed.h; + break; + case SDL_WINDOWRECT_FLOATING: + SDL_RelativeToGlobalForWindow(window, window->floating.x, window->floating.y, x, y); + *width = window->floating.w; + *height = window->floating.h; + break; + case SDL_WINDOWRECT_PENDING: + SDL_RelativeToGlobalForWindow(window, window->pending.x, window->pending.y, x, y); + *width = window->pending.w; + *height = window->pending.h; + break; + default: + // Should never be here + SDL_assert_release(false); + *width = 0; + *height = 0; + break; + } + + /* Copy the client size in pixels into this rect structure, + which we'll then adjust with AdjustWindowRectEx */ + rect.left = 0; + rect.top = 0; + rect.right = *width; + rect.bottom = *height; + + /* borderless windows will have WM_NCCALCSIZE return 0 for the non-client area. When this happens, it looks like windows will send a resize message + expanding the window client area to the previous window + chrome size, so shouldn't need to adjust the window size for the set styles. + */ + if (!(window->flags & SDL_WINDOW_BORDERLESS) && !SDL_WINDOW_IS_POPUP(window)) { +#if defined(SDL_PLATFORM_XBOXONE) || defined(SDL_PLATFORM_XBOXSERIES) + AdjustWindowRectEx(&rect, style, menu, 0); +#else + if (WIN_IsPerMonitorV2DPIAware(SDL_GetVideoDevice())) { + /* With per-monitor v2, the window border/titlebar size depend on the DPI, so we need to call AdjustWindowRectExForDpi instead of + AdjustWindowRectEx. */ + if (videodata) { + UINT frame_dpi; + SDL_WindowData *data = window->internal; + frame_dpi = (data && videodata->GetDpiForWindow) ? videodata->GetDpiForWindow(data->hwnd) : USER_DEFAULT_SCREEN_DPI; + if (videodata->AdjustWindowRectExForDpi(&rect, style, menu, styleEx, frame_dpi) == 0) { + return WIN_SetError("AdjustWindowRectExForDpi()"); + } + } + } else { + if (AdjustWindowRectEx(&rect, style, menu, styleEx) == 0) { + return WIN_SetError("AdjustWindowRectEx()"); + } + } +#endif + } + + // Final rect in Windows screen space, including the frame + *x += rect.left; + *y += rect.top; + *width = (rect.right - rect.left); + *height = (rect.bottom - rect.top); + +#ifdef HIGHDPI_DEBUG + SDL_Log("WIN_AdjustWindowRectWithStyle: in: %d, %d, %dx%d, returning: %d, %d, %dx%d, used dpi %d for frame calculation", + (rect_type == SDL_WINDOWRECT_FLOATING ? window->floating.x : rect_type == SDL_WINDOWRECT_WINDOWED ? window->windowed.x : window->x), + (rect_type == SDL_WINDOWRECT_FLOATING ? window->floating.y : rect_type == SDL_WINDOWRECT_WINDOWED ? window->windowed.y : window->y), + (rect_type == SDL_WINDOWRECT_FLOATING ? window->floating.w : rect_type == SDL_WINDOWRECT_WINDOWED ? window->windowed.w : window->w), + (rect_type == SDL_WINDOWRECT_FLOATING ? window->floating.h : rect_type == SDL_WINDOWRECT_WINDOWED ? window->windowed.h : window->h), + *x, *y, *width, *height, frame_dpi); +#endif + return true; +} + +bool WIN_AdjustWindowRect(SDL_Window *window, int *x, int *y, int *width, int *height, SDL_WindowRect rect_type) +{ + SDL_WindowData *data = window->internal; + HWND hwnd = data->hwnd; + DWORD style, styleEx; + BOOL menu; + + style = GetWindowLong(hwnd, GWL_STYLE); + styleEx = GetWindowLong(hwnd, GWL_EXSTYLE); +#if defined(SDL_PLATFORM_XBOXONE) || defined(SDL_PLATFORM_XBOXSERIES) + menu = FALSE; +#else + menu = (style & WS_CHILDWINDOW) ? FALSE : (GetMenu(hwnd) != NULL); +#endif + return WIN_AdjustWindowRectWithStyle(window, style, styleEx, menu, x, y, width, height, rect_type); +} + +bool WIN_AdjustWindowRectForHWND(HWND hwnd, LPRECT lpRect, UINT frame_dpi) +{ + SDL_VideoDevice *videodevice = SDL_GetVideoDevice(); + SDL_VideoData *videodata = videodevice ? videodevice->internal : NULL; + DWORD style, styleEx; + BOOL menu; + + style = GetWindowLong(hwnd, GWL_STYLE); + styleEx = GetWindowLong(hwnd, GWL_EXSTYLE); +#if defined(SDL_PLATFORM_XBOXONE) || defined(SDL_PLATFORM_XBOXSERIES) + menu = FALSE; +#else + menu = (style & WS_CHILDWINDOW) ? FALSE : (GetMenu(hwnd) != NULL); +#endif + +#if defined(SDL_PLATFORM_XBOXONE) || defined(SDL_PLATFORM_XBOXSERIES) + AdjustWindowRectEx(lpRect, style, menu, styleEx); +#else + if (WIN_IsPerMonitorV2DPIAware(videodevice)) { + // With per-monitor v2, the window border/titlebar size depend on the DPI, so we need to call AdjustWindowRectExForDpi instead of AdjustWindowRectEx. + if (!frame_dpi) { + frame_dpi = videodata->GetDpiForWindow ? videodata->GetDpiForWindow(hwnd) : USER_DEFAULT_SCREEN_DPI; + } + if (!videodata->AdjustWindowRectExForDpi(lpRect, style, menu, styleEx, frame_dpi)) { + return WIN_SetError("AdjustWindowRectExForDpi()"); + } + } else { + if (!AdjustWindowRectEx(lpRect, style, menu, styleEx)) { + return WIN_SetError("AdjustWindowRectEx()"); + } + } +#endif + return true; +} + +bool WIN_SetWindowPositionInternal(SDL_Window *window, UINT flags, SDL_WindowRect rect_type) +{ + SDL_Window *child_window; + SDL_WindowData *data = window->internal; + HWND hwnd = data->hwnd; + HWND top; + int x, y; + int w, h; + bool result = true; + + // Figure out what the window area will be + if (SDL_ShouldAllowTopmost() && (window->flags & SDL_WINDOW_ALWAYS_ON_TOP)) { + top = HWND_TOPMOST; + } else { + top = HWND_NOTOPMOST; + } + + WIN_AdjustWindowRect(window, &x, &y, &w, &h, rect_type); + + data->expected_resize = true; + if (SetWindowPos(hwnd, top, x, y, w, h, flags) == 0) { + result = WIN_SetError("SetWindowPos()"); + } + data->expected_resize = false; + + // Update any child windows + for (child_window = window->first_child; child_window; child_window = child_window->next_sibling) { + if (!WIN_SetWindowPositionInternal(child_window, flags, SDL_WINDOWRECT_CURRENT)) { + result = false; + } + } + return result; +} + +static SDL_WindowEraseBackgroundMode GetEraseBackgroundModeHint(void) +{ + const char *hint = SDL_GetHint(SDL_HINT_WINDOWS_ERASE_BACKGROUND_MODE); + if (!hint) + return SDL_ERASEBACKGROUNDMODE_INITIAL; + + if (SDL_strstr(hint, "never")) + return SDL_ERASEBACKGROUNDMODE_NEVER; + + if (SDL_strstr(hint, "initial")) + return SDL_ERASEBACKGROUNDMODE_INITIAL; + + if (SDL_strstr(hint, "always")) + return SDL_ERASEBACKGROUNDMODE_ALWAYS; + + int mode = SDL_GetStringInteger(hint, 1); + if (mode < 0 || mode > 2) { + SDL_Log("GetEraseBackgroundModeHint: invalid value for SDL_HINT_WINDOWS_ERASE_BACKGROUND_MODE. Fallback to default"); + return SDL_ERASEBACKGROUNDMODE_INITIAL; + } + + return (SDL_WindowEraseBackgroundMode)mode; +} + +static bool SetupWindowData(SDL_VideoDevice *_this, SDL_Window *window, HWND hwnd, HWND parent) +{ + SDL_VideoData *videodata = _this->internal; + SDL_WindowData *data; + + // Allocate the window data + data = (SDL_WindowData *)SDL_calloc(1, sizeof(*data)); + if (!data) { + return false; + } + data->window = window; + data->hwnd = hwnd; + data->parent = parent; +#if defined(SDL_PLATFORM_XBOXONE) || defined(SDL_PLATFORM_XBOXSERIES) + data->hdc = (HDC)data->hwnd; +#else + data->hdc = GetDC(hwnd); +#endif + data->hinstance = (HINSTANCE)GetWindowLongPtr(hwnd, GWLP_HINSTANCE); + data->mouse_button_flags = (WPARAM)-1; + data->last_pointer_update = (LPARAM)-1; + data->videodata = videodata; + data->initializing = true; + data->last_displayID = window->last_displayID; + data->dwma_border_color = DWMWA_COLOR_DEFAULT; + data->hint_erase_background_mode = GetEraseBackgroundModeHint(); + + + // WIN_WarpCursor() jitters by +1, and remote desktop warp wobble is +/- 1 +#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) + LONG remote_desktop_adjustment = GetSystemMetrics(SM_REMOTESESSION) ? 2 : 0; + data->cursor_ctrlock_rect.left = 0 - remote_desktop_adjustment; + data->cursor_ctrlock_rect.top = 0; + data->cursor_ctrlock_rect.right = 1 + remote_desktop_adjustment; + data->cursor_ctrlock_rect.bottom = 1; +#endif + + if (SDL_GetHintBoolean("SDL_WINDOW_RETAIN_CONTENT", false)) { + data->copybits_flag = 0; + } else { + data->copybits_flag = SWP_NOCOPYBITS; + } + +#ifdef HIGHDPI_DEBUG + SDL_Log("SetupWindowData: initialized data->scaling_dpi to %d", data->scaling_dpi); +#endif + +#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) + // Associate the data with the window + if (!SetProp(hwnd, TEXT("SDL_WindowData"), data)) { + ReleaseDC(hwnd, data->hdc); + SDL_free(data); + return WIN_SetError("SetProp() failed"); + } +#endif + + window->internal = data; + + // Set up the window proc function +#ifdef GWLP_WNDPROC + data->wndproc = (WNDPROC)GetWindowLongPtr(hwnd, GWLP_WNDPROC); + if (data->wndproc == WIN_WindowProc) { + data->wndproc = NULL; + } else { + SetWindowLongPtr(hwnd, GWLP_WNDPROC, (LONG_PTR)WIN_WindowProc); + } +#else + data->wndproc = (WNDPROC)GetWindowLong(hwnd, GWL_WNDPROC); + if (data->wndproc == WIN_WindowProc) { + data->wndproc = NULL; + } else { + SetWindowLong(hwnd, GWL_WNDPROC, (LONG_PTR)WIN_WindowProc); + } +#endif + + // Fill in the SDL window with the window state + { + DWORD style = GetWindowLong(hwnd, GWL_STYLE); + if (style & WS_VISIBLE) { + window->flags &= ~SDL_WINDOW_HIDDEN; + } else { + window->flags |= SDL_WINDOW_HIDDEN; + } + if (style & WS_POPUP) { + window->flags |= SDL_WINDOW_BORDERLESS; + } else { + window->flags &= ~SDL_WINDOW_BORDERLESS; + } + if (style & WS_THICKFRAME) { + window->flags |= SDL_WINDOW_RESIZABLE; + } else if (!(style & WS_POPUP)) { + window->flags &= ~SDL_WINDOW_RESIZABLE; + } +#ifdef WS_MAXIMIZE + if (style & WS_MAXIMIZE) { + window->flags |= SDL_WINDOW_MAXIMIZED; + } else +#endif + { + window->flags &= ~SDL_WINDOW_MAXIMIZED; + } +#ifdef WS_MINIMIZE + if (style & WS_MINIMIZE) { + window->flags |= SDL_WINDOW_MINIMIZED; + } else +#endif + { + window->flags &= ~SDL_WINDOW_MINIMIZED; + } + } + if (!(window->flags & SDL_WINDOW_MINIMIZED)) { + RECT rect; + if (GetClientRect(hwnd, &rect) && !WIN_IsRectEmpty(&rect)) { + int w = rect.right; + int h = rect.bottom; + + if (window->flags & SDL_WINDOW_EXTERNAL) { + window->floating.w = window->windowed.w = window->w = w; + window->floating.h = window->windowed.h = window->h = h; + } else if ((window->windowed.w && window->windowed.w != w) || (window->windowed.h && window->windowed.h != h)) { + // We tried to create a window larger than the desktop and Windows didn't allow it. Override! + int x, y; + // Figure out what the window area will be + WIN_AdjustWindowRect(window, &x, &y, &w, &h, SDL_WINDOWRECT_FLOATING); + data->expected_resize = true; + SetWindowPos(hwnd, NULL, x, y, w, h, data->copybits_flag | SWP_NOZORDER | SWP_NOOWNERZORDER | SWP_NOACTIVATE); + data->expected_resize = false; + } else { + window->w = w; + window->h = h; + } + } + } +#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) + if (!(window->flags & SDL_WINDOW_MINIMIZED)) { + POINT point; + point.x = 0; + point.y = 0; + if (ClientToScreen(hwnd, &point)) { + if (window->flags & SDL_WINDOW_EXTERNAL) { + window->floating.x = window->windowed.x = point.x; + window->floating.y = window->windowed.y = point.y; + } + window->x = point.x; + window->y = point.y; + } + } + + WIN_UpdateWindowICCProfile(window, false); +#endif + +#if defined(SDL_PLATFORM_XBOXONE) || defined(SDL_PLATFORM_XBOXSERIES) + window->flags |= SDL_WINDOW_INPUT_FOCUS; + SDL_SetKeyboardFocus(window); +#else + if (GetFocus() == hwnd) { + window->flags |= SDL_WINDOW_INPUT_FOCUS; + SDL_SetKeyboardFocus(window); + WIN_UpdateClipCursor(window); + } +#endif + + if (window->flags & SDL_WINDOW_ALWAYS_ON_TOP) { + WIN_SetWindowAlwaysOnTop(_this, window, true); + } else { + WIN_SetWindowAlwaysOnTop(_this, window, false); + } + +#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) + // Enable multi-touch + if (videodata->RegisterTouchWindow) { + videodata->RegisterTouchWindow(hwnd, (TWF_FINETOUCH | TWF_WANTPALM)); + } +#endif + + if (data->parent && !window->parent) { + data->destroy_parent_with_window = true; + } + + data->initializing = false; + +#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) + if (window->flags & SDL_WINDOW_EXTERNAL) { + // Query the title from the existing window + LPTSTR title; + int titleLen; + bool isstack; + + titleLen = GetWindowTextLength(hwnd); + title = SDL_small_alloc(TCHAR, titleLen + 1, &isstack); + if (title) { + titleLen = GetWindowText(hwnd, title, titleLen + 1); + } else { + titleLen = 0; + } + if (titleLen > 0) { + window->title = WIN_StringToUTF8(title); + } + if (title) { + SDL_small_free(title, isstack); + } + } +#endif // !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) + + SDL_PropertiesID props = SDL_GetWindowProperties(window); + SDL_SetPointerProperty(props, SDL_PROP_WINDOW_WIN32_HWND_POINTER, data->hwnd); + SDL_SetPointerProperty(props, SDL_PROP_WINDOW_WIN32_HDC_POINTER, data->hdc); + SDL_SetPointerProperty(props, SDL_PROP_WINDOW_WIN32_INSTANCE_POINTER, data->hinstance); + + // All done! + return true; +} + +static void CleanupWindowData(SDL_VideoDevice *_this, SDL_Window *window) +{ + SDL_WindowData *data = window->internal; + + if (data) { + +#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) + if (data->drop_target) { + WIN_AcceptDragAndDrop(window, false); + } + if (data->ICMFileName) { + SDL_free(data->ICMFileName); + } + if (data->keyboard_hook) { + UnhookWindowsHookEx(data->keyboard_hook); + } + ReleaseDC(data->hwnd, data->hdc); + RemoveProp(data->hwnd, TEXT("SDL_WindowData")); +#endif + if (!(window->flags & SDL_WINDOW_EXTERNAL)) { + DestroyWindow(data->hwnd); + if (data->destroy_parent_with_window && data->parent) { + DestroyWindow(data->parent); + } + } else { + // Restore any original event handler... + if (data->wndproc) { +#ifdef GWLP_WNDPROC + SetWindowLongPtr(data->hwnd, GWLP_WNDPROC, + (LONG_PTR)data->wndproc); +#else + SetWindowLong(data->hwnd, GWL_WNDPROC, + (LONG_PTR)data->wndproc); +#endif + } + } + SDL_free(data); + } + window->internal = NULL; +} + +static void WIN_ConstrainPopup(SDL_Window *window, bool output_to_pending) +{ + // Clamp popup windows to the output borders + if (SDL_WINDOW_IS_POPUP(window)) { + SDL_Window *w; + SDL_DisplayID displayID; + SDL_Rect rect; + int abs_x = window->last_position_pending ? window->pending.x : window->floating.x; + int abs_y = window->last_position_pending ? window->pending.y : window->floating.y; + const int width = window->last_size_pending ? window->pending.w : window->floating.w; + const int height = window->last_size_pending ? window->pending.h : window->floating.h; + int offset_x = 0, offset_y = 0; + + // Calculate the total offset from the parents + for (w = window->parent; SDL_WINDOW_IS_POPUP(w); w = w->parent) { + offset_x += w->x; + offset_y += w->y; + } + + offset_x += w->x; + offset_y += w->y; + abs_x += offset_x; + abs_y += offset_y; + + // Constrain the popup window to the display of the toplevel parent + displayID = SDL_GetDisplayForWindow(w); + SDL_GetDisplayBounds(displayID, &rect); + if (abs_x + width > rect.x + rect.w) { + abs_x -= (abs_x + width) - (rect.x + rect.w); + } + if (abs_y + height > rect.y + rect.h) { + abs_y -= (abs_y + height) - (rect.y + rect.h); + } + abs_x = SDL_max(abs_x, rect.x); + abs_y = SDL_max(abs_y, rect.y); + + if (output_to_pending) { + window->pending.x = abs_x - offset_x; + window->pending.y = abs_y - offset_y; + window->pending.w = width; + window->pending.h = height; + } else { + window->floating.x = abs_x - offset_x; + window->floating.y = abs_y - offset_y; + window->floating.w = width; + window->floating.h = height; + } + } +} + +static void WIN_SetKeyboardFocus(SDL_Window *window, bool set_active_focus) +{ + SDL_Window *toplevel = window; + + // Find the topmost parent + while (SDL_WINDOW_IS_POPUP(toplevel)) { + toplevel = toplevel->parent; + } + + toplevel->internal->keyboard_focus = window; + + if (set_active_focus && !window->is_hiding && !window->is_destroying) { + SDL_SetKeyboardFocus(window); + } +} + +bool WIN_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID create_props) +{ + HWND hwnd = (HWND)SDL_GetPointerProperty(create_props, SDL_PROP_WINDOW_CREATE_WIN32_HWND_POINTER, SDL_GetPointerProperty(create_props, "sdl2-compat.external_window", NULL)); + HWND parent = NULL; + if (hwnd) { + window->flags |= SDL_WINDOW_EXTERNAL; + + if (!SetupWindowData(_this, window, hwnd, parent)) { + return false; + } + } else { + DWORD style = STYLE_BASIC; + DWORD styleEx = 0; + int x, y; + int w, h; + + if (window->flags & SDL_WINDOW_UTILITY) { + parent = CreateWindow(SDL_Appname, TEXT(""), STYLE_BASIC, 0, 0, 32, 32, NULL, NULL, SDL_Instance, NULL); + } else if (window->parent) { + parent = window->parent->internal->hwnd; + } + + style |= GetWindowStyle(window); + styleEx |= GetWindowStyleEx(window); + + // Figure out what the window area will be + WIN_ConstrainPopup(window, false); + WIN_AdjustWindowRectWithStyle(window, style, styleEx, FALSE, &x, &y, &w, &h, SDL_WINDOWRECT_FLOATING); + + hwnd = CreateWindowEx(styleEx, SDL_Appname, TEXT(""), style, + x, y, w, h, parent, NULL, SDL_Instance, NULL); + if (!hwnd) { + return WIN_SetError("Couldn't create window"); + } + + WIN_UpdateDarkModeForHWND(hwnd); + + WIN_PumpEvents(_this); + + if (!SetupWindowData(_this, window, hwnd, parent)) { + DestroyWindow(hwnd); + if (parent) { + DestroyWindow(parent); + } + return false; + } + + // Inform Windows of the frame change so we can respond to WM_NCCALCSIZE + SetWindowPos(hwnd, NULL, 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_NOSIZE | SWP_NOMOVE | SWP_NOZORDER | SWP_NOOWNERZORDER | SWP_NOACTIVATE); + + if (window->flags & SDL_WINDOW_MINIMIZED) { + /* TODO: We have to clear SDL_WINDOW_HIDDEN here to ensure the window flags match the window state. The + window is already shown after this and windows with WS_MINIMIZE do not generate a WM_SHOWWINDOW. This + means you can't currently create a window that is initially hidden and is minimized when shown. + */ + window->flags &= ~SDL_WINDOW_HIDDEN; + ShowWindow(hwnd, SW_SHOWMINNOACTIVE); + } + } + +#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) + // FIXME: does not work on all hardware configurations with different renders (i.e. hybrid GPUs) + if (window->flags & SDL_WINDOW_TRANSPARENT) { + SDL_SharedObject *handle = SDL_LoadObject("dwmapi.dll"); + if (handle) { + DwmEnableBlurBehindWindow_t DwmEnableBlurBehindWindowFunc = (DwmEnableBlurBehindWindow_t)SDL_LoadFunction(handle, "DwmEnableBlurBehindWindow"); + if (DwmEnableBlurBehindWindowFunc) { + /* The region indicates which part of the window will be blurred and rest will be transparent. This + is because the alpha value of the window will be used for non-blurred areas + We can use (-1, -1, 0, 0) boundary to make sure no pixels are being blurred + */ + HRGN rgn = CreateRectRgn(-1, -1, 0, 0); + DWM_BLURBEHIND bb; + bb.flags = (DWM_BB_ENABLE | DWM_BB_BLURREGION); + bb.enable = TRUE; + bb.blur_region = rgn; + bb.transition_on_maxed = FALSE; + DwmEnableBlurBehindWindowFunc(hwnd, &bb); + DeleteObject(rgn); + } + SDL_UnloadObject(handle); + } + } + + HWND share_hwnd = (HWND)SDL_GetPointerProperty(create_props, SDL_PROP_WINDOW_CREATE_WIN32_PIXEL_FORMAT_HWND_POINTER, NULL); + if (share_hwnd) { + HDC hdc = GetDC(share_hwnd); + int pixel_format = GetPixelFormat(hdc); + PIXELFORMATDESCRIPTOR pfd; + + SDL_zero(pfd); + DescribePixelFormat(hdc, pixel_format, sizeof(pfd), &pfd); + ReleaseDC(share_hwnd, hdc); + + if (!SetPixelFormat(window->internal->hdc, pixel_format, &pfd)) { + WIN_DestroyWindow(_this, window); + return WIN_SetError("SetPixelFormat()"); + } + } else { +#endif // !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) + if (!(window->flags & SDL_WINDOW_OPENGL)) { + return true; + } + + // The rest of this macro mess is for OpenGL or OpenGL ES windows +#ifdef SDL_VIDEO_OPENGL_ES2 + if ((_this->gl_config.profile_mask == SDL_GL_CONTEXT_PROFILE_ES || + SDL_GetHintBoolean(SDL_HINT_VIDEO_FORCE_EGL, false)) +#ifdef SDL_VIDEO_OPENGL_WGL + && (!_this->gl_data || WIN_GL_UseEGL(_this)) +#endif // SDL_VIDEO_OPENGL_WGL + ) { +#ifdef SDL_VIDEO_OPENGL_EGL + if (!WIN_GLES_SetupWindow(_this, window)) { + WIN_DestroyWindow(_this, window); + return false; + } + return true; +#else + return SDL_SetError("Could not create GLES window surface (EGL support not configured)"); +#endif // SDL_VIDEO_OPENGL_EGL + } +#endif // SDL_VIDEO_OPENGL_ES2 + +#ifdef SDL_VIDEO_OPENGL_WGL + if (!WIN_GL_SetupWindow(_this, window)) { + WIN_DestroyWindow(_this, window); + return false; + } +#else + return SDL_SetError("Could not create GL window (WGL support not configured)"); +#endif +#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) + } +#endif // !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) + + return true; +} + +void WIN_SetWindowTitle(SDL_VideoDevice *_this, SDL_Window *window) +{ +#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) + HWND hwnd = window->internal->hwnd; + LPTSTR title = WIN_UTF8ToString(window->title); + SetWindowText(hwnd, title); + SDL_free(title); +#endif +} + +bool WIN_SetWindowIcon(SDL_VideoDevice *_this, SDL_Window *window, SDL_Surface *icon) +{ +#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) + HWND hwnd = window->internal->hwnd; + HICON hicon = NULL; + BYTE *icon_bmp; + int icon_len, mask_len, row_len, y; + BITMAPINFOHEADER *bmi; + Uint8 *dst; + bool isstack; + bool result = true; + + // Create temporary buffer for ICONIMAGE structure + SDL_COMPILE_TIME_ASSERT(WIN_SetWindowIcon_uses_BITMAPINFOHEADER_to_prepare_an_ICONIMAGE, sizeof(BITMAPINFOHEADER) == 40); + mask_len = (icon->h * (icon->w + 7) / 8); + icon_len = sizeof(BITMAPINFOHEADER) + icon->h * icon->w * sizeof(Uint32) + mask_len; + icon_bmp = SDL_small_alloc(BYTE, icon_len, &isstack); + if (!icon_bmp) { + return false; + } + + // Write the BITMAPINFO header + bmi = (BITMAPINFOHEADER *)icon_bmp; + bmi->biSize = SDL_Swap32LE(sizeof(BITMAPINFOHEADER)); + bmi->biWidth = SDL_Swap32LE(icon->w); + bmi->biHeight = SDL_Swap32LE(icon->h * 2); + bmi->biPlanes = SDL_Swap16LE(1); + bmi->biBitCount = SDL_Swap16LE(32); + bmi->biCompression = SDL_Swap32LE(BI_RGB); + bmi->biSizeImage = SDL_Swap32LE(icon->h * icon->w * sizeof(Uint32)); + bmi->biXPelsPerMeter = SDL_Swap32LE(0); + bmi->biYPelsPerMeter = SDL_Swap32LE(0); + bmi->biClrUsed = SDL_Swap32LE(0); + bmi->biClrImportant = SDL_Swap32LE(0); + + // Write the pixels upside down into the bitmap buffer + SDL_assert(icon->format == SDL_PIXELFORMAT_ARGB8888); + dst = &icon_bmp[sizeof(BITMAPINFOHEADER)]; + row_len = icon->w * sizeof(Uint32); + y = icon->h; + while (y--) { + Uint8 *src = (Uint8 *)icon->pixels + y * icon->pitch; + SDL_memcpy(dst, src, row_len); + dst += row_len; + } + + // Write the mask + SDL_memset(icon_bmp + icon_len - mask_len, 0xFF, mask_len); + + hicon = CreateIconFromResource(icon_bmp, icon_len, TRUE, 0x00030000); + + SDL_small_free(icon_bmp, isstack); + + if (!hicon) { + result = SDL_SetError("SetWindowIcon() failed, error %08X", (unsigned int)GetLastError()); + } + + // Set the icon for the window + SendMessage(hwnd, WM_SETICON, ICON_SMALL, (LPARAM)hicon); + + // Set the icon in the task manager (should we do this?) + SendMessage(hwnd, WM_SETICON, ICON_BIG, (LPARAM)hicon); + return result; +#else + return SDL_Unsupported(); +#endif +} + +bool WIN_SetWindowPosition(SDL_VideoDevice *_this, SDL_Window *window) +{ + /* HighDPI support: removed SWP_NOSIZE. If the move results in a DPI change, we need to allow + * the window to resize (e.g. AdjustWindowRectExForDpi frame sizes are different). + */ + if (!(window->flags & SDL_WINDOW_FULLSCREEN)) { + if (!(window->flags & (SDL_WINDOW_MAXIMIZED | SDL_WINDOW_MINIMIZED))) { + WIN_ConstrainPopup(window, true); + return WIN_SetWindowPositionInternal(window, + window->internal->copybits_flag | SWP_NOZORDER | SWP_NOOWNERZORDER | SWP_NOSIZE | + SWP_NOACTIVATE, SDL_WINDOWRECT_PENDING); + } + } else { + return SDL_UpdateFullscreenMode(window, SDL_FULLSCREEN_OP_ENTER, true); + } + + return true; +} + +void WIN_SetWindowSize(SDL_VideoDevice *_this, SDL_Window *window) +{ + if (!(window->flags & (SDL_WINDOW_FULLSCREEN | SDL_WINDOW_MAXIMIZED))) { + WIN_SetWindowPositionInternal(window, window->internal->copybits_flag | SWP_NOMOVE | SWP_NOZORDER | SWP_NOOWNERZORDER | SWP_NOACTIVATE, SDL_WINDOWRECT_PENDING); + } else { + // Can't resize the window + window->last_size_pending = false; + } +} + +bool WIN_GetWindowBordersSize(SDL_VideoDevice *_this, SDL_Window *window, int *top, int *left, int *bottom, int *right) +{ +#if defined(SDL_PLATFORM_XBOXONE) || defined(SDL_PLATFORM_XBOXSERIES) + HWND hwnd = window->internal->hwnd; + RECT rcClient; + + /* rcClient stores the size of the inner window, while rcWindow stores the outer size relative to the top-left + * screen position; so the top/left values of rcClient are always {0,0} and bottom/right are {height,width} */ + GetClientRect(hwnd, &rcClient); + + *top = rcClient.top; + *left = rcClient.left; + *bottom = rcClient.bottom; + *right = rcClient.right; + + return true; +#else // !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) + HWND hwnd = window->internal->hwnd; + RECT rcClient, rcWindow; + POINT ptDiff; + + /* rcClient stores the size of the inner window, while rcWindow stores the outer size relative to the top-left + * screen position; so the top/left values of rcClient are always {0,0} and bottom/right are {height,width} */ + if (!GetClientRect(hwnd, &rcClient)) { + return SDL_SetError("GetClientRect() failed, error %08X", (unsigned int)GetLastError()); + } + + if (!GetWindowRect(hwnd, &rcWindow)) { + return SDL_SetError("GetWindowRect() failed, error %08X", (unsigned int)GetLastError()); + } + + /* convert the top/left values to make them relative to + * the window; they will end up being slightly negative */ + ptDiff.y = rcWindow.top; + ptDiff.x = rcWindow.left; + + if (!ScreenToClient(hwnd, &ptDiff)) { + return SDL_SetError("ScreenToClient() failed, error %08X", (unsigned int)GetLastError()); + } + + rcWindow.top = ptDiff.y; + rcWindow.left = ptDiff.x; + + /* convert the bottom/right values to make them relative to the window, + * these will be slightly bigger than the inner width/height */ + ptDiff.y = rcWindow.bottom; + ptDiff.x = rcWindow.right; + + if (!ScreenToClient(hwnd, &ptDiff)) { + return SDL_SetError("ScreenToClient() failed, error %08X", (unsigned int)GetLastError()); + } + + rcWindow.bottom = ptDiff.y; + rcWindow.right = ptDiff.x; + + /* Now that both the inner and outer rects use the same coordinate system we can subtract them to get the border size. + * Keep in mind that the top/left coordinates of rcWindow are negative because the border lies slightly before {0,0}, + * so switch them around because SDL3 wants them in positive. */ + *top = rcClient.top - rcWindow.top; + *left = rcClient.left - rcWindow.left; + *bottom = rcWindow.bottom - rcClient.bottom; + *right = rcWindow.right - rcClient.right; + + return true; +#endif // !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) +} + +void WIN_GetWindowSizeInPixels(SDL_VideoDevice *_this, SDL_Window *window, int *w, int *h) +{ + const SDL_WindowData *data = window->internal; + HWND hwnd = data->hwnd; + RECT rect; + + if (GetClientRect(hwnd, &rect) && !WIN_IsRectEmpty(&rect)) { + *w = rect.right; + *h = rect.bottom; + } else if (window->last_pixel_w && window->last_pixel_h) { + *w = window->last_pixel_w; + *h = window->last_pixel_h; + } else { + // Probably created minimized, use the restored size + *w = window->floating.w; + *h = window->floating.h; + } +} + +void WIN_ShowWindow(SDL_VideoDevice *_this, SDL_Window *window) +{ + DWORD style; + HWND hwnd; + + bool bActivate = SDL_GetHintBoolean(SDL_HINT_WINDOW_ACTIVATE_WHEN_SHOWN, true); + + if (SDL_WINDOW_IS_POPUP(window)) { + // Update our position in case our parent moved while we were hidden + WIN_SetWindowPosition(_this, window); + } + + hwnd = window->internal->hwnd; + style = GetWindowLong(hwnd, GWL_EXSTYLE); + if (style & WS_EX_NOACTIVATE) { + bActivate = false; + } + if (bActivate) { + ShowWindow(hwnd, SW_SHOW); + } else { + // Use SetWindowPos instead of ShowWindow to avoid activating the parent window if this is a child window + SetWindowPos(hwnd, NULL, 0, 0, 0, 0, window->internal->copybits_flag | SWP_SHOWWINDOW | SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOOWNERZORDER); + } + + if (window->flags & SDL_WINDOW_POPUP_MENU && bActivate) { + WIN_SetKeyboardFocus(window, window->parent == SDL_GetKeyboardFocus()); + } + if (window->flags & SDL_WINDOW_MODAL) { + WIN_SetWindowModal(_this, window, true); + } +} + +void WIN_HideWindow(SDL_VideoDevice *_this, SDL_Window *window) +{ + HWND hwnd = window->internal->hwnd; + + if (window->flags & SDL_WINDOW_MODAL) { + WIN_SetWindowModal(_this, window, false); + } + + ShowWindow(hwnd, SW_HIDE); + + // Transfer keyboard focus back to the parent + if (window->flags & SDL_WINDOW_POPUP_MENU) { + SDL_Window *new_focus = window->parent; + bool set_focus = window == SDL_GetKeyboardFocus(); + + // Find the highest level window, up to the toplevel parent, that isn't being hidden or destroyed. + while (SDL_WINDOW_IS_POPUP(new_focus) && (new_focus->is_hiding || new_focus->is_destroying)) { + new_focus = new_focus->parent; + + // If some window in the chain currently had keyboard focus, set it to the new lowest-level window. + if (!set_focus) { + set_focus = new_focus == SDL_GetKeyboardFocus(); + } + } + + WIN_SetKeyboardFocus(new_focus, set_focus); + } +} + +void WIN_RaiseWindow(SDL_VideoDevice *_this, SDL_Window *window) +{ +#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) + /* If desired, raise the window more forcefully. + * Technique taken from http://stackoverflow.com/questions/916259/ . + * Specifically, http://stackoverflow.com/a/34414846 . + * + * The issue is that Microsoft has gone through a lot of trouble to make it + * nearly impossible to programmatically move a window to the foreground, + * for "security" reasons. Apparently, the following song-and-dance gets + * around their objections. */ + bool bForce = SDL_GetHintBoolean(SDL_HINT_FORCE_RAISEWINDOW, false); + bool bActivate = SDL_GetHintBoolean(SDL_HINT_WINDOW_ACTIVATE_WHEN_RAISED, true); + + HWND hCurWnd = NULL; + DWORD dwMyID = 0u; + DWORD dwCurID = 0u; + + SDL_WindowData *data = window->internal; + HWND hwnd = data->hwnd; + if (bForce) { + hCurWnd = GetForegroundWindow(); + dwMyID = GetCurrentThreadId(); + dwCurID = GetWindowThreadProcessId(hCurWnd, NULL); + ShowWindow(hwnd, SW_RESTORE); + AttachThreadInput(dwCurID, dwMyID, TRUE); + SetWindowPos(hwnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE); + if (!SDL_ShouldAllowTopmost() || !(window->flags & SDL_WINDOW_ALWAYS_ON_TOP)) { + SetWindowPos(hwnd, HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE); + } + } + if (bActivate) { + SetForegroundWindow(hwnd); + if (window->flags & SDL_WINDOW_POPUP_MENU) { + WIN_SetKeyboardFocus(window, window->parent == SDL_GetKeyboardFocus()); + } + } else { + SetWindowPos(hwnd, HWND_TOP, 0, 0, 0, 0, data->copybits_flag | SWP_NOMOVE | SWP_NOSIZE | SWP_NOOWNERZORDER | SWP_NOACTIVATE); + } + if (bForce) { + AttachThreadInput(dwCurID, dwMyID, FALSE); + SetFocus(hwnd); + SetActiveWindow(hwnd); + } +#endif // !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) +} + +void WIN_MaximizeWindow(SDL_VideoDevice *_this, SDL_Window *window) +{ + SDL_WindowData *data = window->internal; + + if (!(window->flags & SDL_WINDOW_FULLSCREEN)) { + HWND hwnd = data->hwnd; + data->expected_resize = true; + ShowWindow(hwnd, SW_MAXIMIZE); + data->expected_resize = false; + + /* Clamp the maximized window size to the max window size. + * This is automatic if maximizing from the window controls. + */ + if (window->max_w || window->max_h) { + int fx, fy, fw, fh; + + window->windowed.w = window->max_w ? SDL_min(window->w, window->max_w) : window->windowed.w; + window->windowed.h = window->max_h ? SDL_min(window->h, window->max_h) : window->windowed.h; + WIN_AdjustWindowRect(window, &fx, &fy, &fw, &fh, SDL_WINDOWRECT_WINDOWED); + + data->expected_resize = true; + SetWindowPos(hwnd, HWND_TOP, fx, fy, fw, fh, data->copybits_flag | SWP_NOMOVE | SWP_NOOWNERZORDER | SWP_NOACTIVATE); + data->expected_resize = false; + } + } else { + data->windowed_mode_was_maximized = true; + } +} + +void WIN_MinimizeWindow(SDL_VideoDevice *_this, SDL_Window *window) +{ + HWND hwnd = window->internal->hwnd; + ShowWindow(hwnd, SW_MINIMIZE); +} + +void WIN_SetWindowBordered(SDL_VideoDevice *_this, SDL_Window *window, bool bordered) +{ + SDL_WindowData *data = window->internal; + HWND hwnd = data->hwnd; + DWORD style; + + style = GetWindowLong(hwnd, GWL_STYLE); + style &= ~STYLE_MASK; + style |= GetWindowStyle(window); + + data->in_border_change = true; + SetWindowLong(hwnd, GWL_STYLE, style); + WIN_SetWindowPositionInternal(window, data->copybits_flag | SWP_FRAMECHANGED | SWP_NOZORDER | SWP_NOOWNERZORDER | SWP_NOACTIVATE, SDL_WINDOWRECT_CURRENT); + data->in_border_change = false; +} + +void WIN_SetWindowResizable(SDL_VideoDevice *_this, SDL_Window *window, bool resizable) +{ + SDL_WindowData *data = window->internal; + HWND hwnd = data->hwnd; + DWORD style; + + style = GetWindowLong(hwnd, GWL_STYLE); + style &= ~STYLE_MASK; + style |= GetWindowStyle(window); + + SetWindowLong(hwnd, GWL_STYLE, style); +} + +void WIN_SetWindowAlwaysOnTop(SDL_VideoDevice *_this, SDL_Window *window, bool on_top) +{ + WIN_SetWindowPositionInternal(window, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE, SDL_WINDOWRECT_CURRENT); +} + +void WIN_RestoreWindow(SDL_VideoDevice *_this, SDL_Window *window) +{ + SDL_WindowData *data = window->internal; + if (!(window->flags & SDL_WINDOW_FULLSCREEN)) { + HWND hwnd = data->hwnd; + data->expected_resize = true; + ShowWindow(hwnd, SW_RESTORE); + data->expected_resize = false; + } else { + data->windowed_mode_was_maximized = false; + } +} + +static DWM_WINDOW_CORNER_PREFERENCE WIN_UpdateCornerRoundingForHWND(HWND hwnd, DWM_WINDOW_CORNER_PREFERENCE cornerPref) +{ + DWM_WINDOW_CORNER_PREFERENCE oldPref = DWMWCP_DEFAULT; + + SDL_SharedObject *handle = SDL_LoadObject("dwmapi.dll"); + if (handle) { + DwmGetWindowAttribute_t DwmGetWindowAttributeFunc = (DwmGetWindowAttribute_t)SDL_LoadFunction(handle, "DwmGetWindowAttribute"); + DwmSetWindowAttribute_t DwmSetWindowAttributeFunc = (DwmSetWindowAttribute_t)SDL_LoadFunction(handle, "DwmSetWindowAttribute"); + if (DwmGetWindowAttributeFunc && DwmSetWindowAttributeFunc) { + DwmGetWindowAttributeFunc(hwnd, DWMWA_WINDOW_CORNER_PREFERENCE, &oldPref, sizeof(oldPref)); + DwmSetWindowAttributeFunc(hwnd, DWMWA_WINDOW_CORNER_PREFERENCE, &cornerPref, sizeof(cornerPref)); + } + + SDL_UnloadObject(handle); + } + + return oldPref; +} + +static COLORREF WIN_UpdateBorderColorForHWND(HWND hwnd, COLORREF colorRef) +{ + COLORREF oldPref = DWMWA_COLOR_DEFAULT; + + SDL_SharedObject *handle = SDL_LoadObject("dwmapi.dll"); + if (handle) { + DwmGetWindowAttribute_t DwmGetWindowAttributeFunc = (DwmGetWindowAttribute_t)SDL_LoadFunction(handle, "DwmGetWindowAttribute"); + DwmSetWindowAttribute_t DwmSetWindowAttributeFunc = (DwmSetWindowAttribute_t)SDL_LoadFunction(handle, "DwmSetWindowAttribute"); + if (DwmGetWindowAttributeFunc && DwmSetWindowAttributeFunc) { + DwmGetWindowAttributeFunc(hwnd, DWMWA_BORDER_COLOR, &oldPref, sizeof(oldPref)); + DwmSetWindowAttributeFunc(hwnd, DWMWA_BORDER_COLOR, &colorRef, sizeof(colorRef)); + } + + SDL_UnloadObject(handle); + } + + return oldPref; +} + +/** + * Reconfigures the window to fill the given display, if fullscreen is true, otherwise restores the window. + */ +SDL_FullscreenResult WIN_SetWindowFullscreen(SDL_VideoDevice *_this, SDL_Window *window, SDL_VideoDisplay *display, SDL_FullscreenOp fullscreen) +{ +#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) + SDL_DisplayData *displaydata = display->internal; + SDL_WindowData *data = window->internal; + HWND hwnd = data->hwnd; + MONITORINFO minfo; + DWORD style, styleEx; + HWND top; + int x, y; + int w, h; + bool enterMaximized = false; + +#ifdef HIGHDPI_DEBUG + SDL_Log("WIN_SetWindowFullscreen: %d", (int)fullscreen); +#endif + + /* Early out if already not in fullscreen, or the styling on + * external windows may end up being overridden. + */ + if (!(window->flags & SDL_WINDOW_FULLSCREEN) && !fullscreen) { + return SDL_FULLSCREEN_SUCCEEDED; + } + + if (SDL_ShouldAllowTopmost() && (window->flags & SDL_WINDOW_ALWAYS_ON_TOP)) { + top = HWND_TOPMOST; + } else { + top = HWND_NOTOPMOST; + } + + /* Use GetMonitorInfo instead of WIN_GetDisplayBounds because we want the + monitor bounds in Windows coordinates (pixels) rather than SDL coordinates (points). */ + SDL_zero(minfo); + minfo.cbSize = sizeof(MONITORINFO); + if (!GetMonitorInfo(displaydata->MonitorHandle, &minfo)) { + SDL_SetError("GetMonitorInfo failed"); + return SDL_FULLSCREEN_FAILED; + } + + SDL_SendWindowEvent(window, fullscreen ? SDL_EVENT_WINDOW_ENTER_FULLSCREEN : SDL_EVENT_WINDOW_LEAVE_FULLSCREEN, 0, 0); + style = GetWindowLong(hwnd, GWL_STYLE); + style &= ~STYLE_MASK; + style |= GetWindowStyle(window); + styleEx = GetWindowLong(hwnd, GWL_EXSTYLE); + + if (fullscreen) { + x = minfo.rcMonitor.left; + y = minfo.rcMonitor.top; + w = minfo.rcMonitor.right - minfo.rcMonitor.left; + h = minfo.rcMonitor.bottom - minfo.rcMonitor.top; + + /* Unset the maximized flag. This fixes + https://bugzilla.libsdl.org/show_bug.cgi?id=3215 + */ + if (style & WS_MAXIMIZE) { + data->windowed_mode_was_maximized = true; + style &= ~WS_MAXIMIZE; + } + + // Disable corner rounding & border color (Windows 11+) so the window fills the full screen + data->windowed_mode_corner_rounding = WIN_UpdateCornerRoundingForHWND(hwnd, DWMWCP_DONOTROUND); + data->dwma_border_color = WIN_UpdateBorderColorForHWND(hwnd, DWMWA_COLOR_NONE); + } else { + BOOL menu; + + WIN_UpdateCornerRoundingForHWND(hwnd, (DWM_WINDOW_CORNER_PREFERENCE)data->windowed_mode_corner_rounding); + WIN_UpdateBorderColorForHWND(hwnd, data->dwma_border_color); + + /* Restore window-maximization state, as applicable. + Special care is taken to *not* do this if and when we're + alt-tab'ing away (to some other window; as indicated by + in_window_deactivation), otherwise + https://bugzilla.libsdl.org/show_bug.cgi?id=3215 can reproduce! + */ + if (data->windowed_mode_was_maximized && !data->in_window_deactivation) { + enterMaximized = true; + data->disable_move_size_events = true; + } + + menu = (style & WS_CHILDWINDOW) ? FALSE : (GetMenu(hwnd) != NULL); + WIN_AdjustWindowRectWithStyle(window, style, styleEx, menu, + &x, &y, + &w, &h, + SDL_WINDOWRECT_FLOATING); + data->windowed_mode_was_maximized = false; + + /* A window may have been maximized by dragging it to the top of another display, in which case the floating + * position may be out-of-date. If the window is being restored to maximized, and the maximized and floating + * position are on different displays, try to center the window on the maximized display for restoration, which + * mimics native Windows behavior. + */ + if (enterMaximized) { + const SDL_Point windowed_point = { window->windowed.x, window->windowed.y }; + const SDL_Point floating_point = { window->floating.x, window->floating.y }; + const SDL_DisplayID floating_display = SDL_GetDisplayForPoint(&floating_point); + const SDL_DisplayID windowed_display = SDL_GetDisplayForPoint(&windowed_point); + + if (floating_display != windowed_display) { + SDL_Rect bounds; + + SDL_zero(bounds); + SDL_GetDisplayUsableBounds(windowed_display, &bounds); + if (w < bounds.w) { + x = bounds.x + (bounds.w - w) / 2; + } else { + x = bounds.x; + } + if (h < bounds.h) { + y = bounds.y + (bounds.h - h) / 2; + } else { + y = bounds.y; + } + } + } + } + + /* Always reset the window to the base floating size before possibly re-applying the maximized state, + * otherwise, the base floating size can seemingly be lost in some cases. + */ + SetWindowLong(hwnd, GWL_STYLE, style); + data->expected_resize = true; + SetWindowPos(hwnd, top, x, y, w, h, data->copybits_flag | SWP_NOACTIVATE); + data->expected_resize = false; + data->disable_move_size_events = false; + + if (enterMaximized) { + WIN_MaximizeWindow(_this, window); + } + +#ifdef HIGHDPI_DEBUG + SDL_Log("WIN_SetWindowFullscreen: %d finished. Set window to %d,%d, %dx%d", (int)fullscreen, x, y, w, h); +#endif + +#endif // !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) + return SDL_FULLSCREEN_SUCCEEDED; +} + +#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) +void WIN_UpdateWindowICCProfile(SDL_Window *window, bool send_event) +{ + SDL_WindowData *data = window->internal; + SDL_DisplayData *displaydata = SDL_GetDisplayDriverDataForWindow(window); + + if (displaydata) { + HDC hdc = CreateDCW(displaydata->DeviceName, NULL, NULL, NULL); + if (hdc) { + WCHAR fileName[MAX_PATH]; + DWORD fileNameSize = SDL_arraysize(fileName); + if (GetICMProfileW(hdc, &fileNameSize, fileName)) { + // fileNameSize includes '\0' on return + if (!data->ICMFileName || + SDL_wcscmp(data->ICMFileName, fileName) != 0) { + if (data->ICMFileName) { + SDL_free(data->ICMFileName); + } + data->ICMFileName = SDL_wcsdup(fileName); + if (send_event) { + SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_ICCPROF_CHANGED, 0, 0); + } + } + } + DeleteDC(hdc); + } + } +} + +void *WIN_GetWindowICCProfile(SDL_VideoDevice *_this, SDL_Window *window, size_t *size) +{ + SDL_WindowData *data = window->internal; + char *filename_utf8; + void *iccProfileData = NULL; + + filename_utf8 = WIN_StringToUTF8(data->ICMFileName); + if (filename_utf8) { + iccProfileData = SDL_LoadFile(filename_utf8, size); + if (!iccProfileData) { + SDL_SetError("Could not open ICC profile"); + } + SDL_free(filename_utf8); + } + return iccProfileData; +} + +static void WIN_GrabKeyboard(SDL_Window *window) +{ + SDL_WindowData *data = window->internal; + HMODULE module; + + if (data->keyboard_hook) { + return; + } + + /* SetWindowsHookEx() needs to know which module contains the hook we + want to install. This is complicated by the fact that SDL can be + linked statically or dynamically. Fortunately XP and later provide + this nice API that will go through the loaded modules and find the + one containing our code. + */ + if (!GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT | GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, + (LPTSTR)WIN_KeyboardHookProc, + &module)) { + return; + } + + // Capture a snapshot of the current keyboard state before the hook + if (!GetKeyboardState(data->videodata->pre_hook_key_state)) { + return; + } + + /* To grab the keyboard, we have to install a low-level keyboard hook to + intercept keys that would normally be captured by the OS. Intercepting + all key events on the system is rather invasive, but it's what Microsoft + actually documents that you do to capture these. + */ + data->keyboard_hook = SetWindowsHookEx(WH_KEYBOARD_LL, WIN_KeyboardHookProc, module, 0); +} + +void WIN_UngrabKeyboard(SDL_Window *window) +{ + SDL_WindowData *data = window->internal; + + if (data->keyboard_hook) { + UnhookWindowsHookEx(data->keyboard_hook); + data->keyboard_hook = NULL; + } +} + +bool WIN_SetWindowMouseRect(SDL_VideoDevice *_this, SDL_Window *window) +{ + WIN_UpdateClipCursor(window); + return true; +} + +bool WIN_SetWindowMouseGrab(SDL_VideoDevice *_this, SDL_Window *window, bool grabbed) +{ + WIN_UpdateClipCursor(window); + return true; +} + +bool WIN_SetWindowKeyboardGrab(SDL_VideoDevice *_this, SDL_Window *window, bool grabbed) +{ + if (grabbed) { + WIN_GrabKeyboard(window); + } else { + WIN_UngrabKeyboard(window); + } + + return true; +} +#endif // !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) + +void WIN_DestroyWindow(SDL_VideoDevice *_this, SDL_Window *window) +{ + CleanupWindowData(_this, window); +} + +/* + * Creates a HelperWindow used for DirectInput. + */ +bool SDL_HelperWindowCreate(void) +{ + HINSTANCE hInstance = GetModuleHandle(NULL); + WNDCLASS wce; + + // Make sure window isn't created twice. + if (SDL_HelperWindow != NULL) { + return true; + } + + // Create the class. + SDL_zero(wce); + wce.lpfnWndProc = DefWindowProc; + wce.lpszClassName = SDL_HelperWindowClassName; + wce.hInstance = hInstance; + + // Register the class. + SDL_HelperWindowClass = RegisterClass(&wce); + if (SDL_HelperWindowClass == 0 && GetLastError() != ERROR_CLASS_ALREADY_EXISTS) { + return WIN_SetError("Unable to create Helper Window Class"); + } + + // Create the window. + SDL_HelperWindow = CreateWindowEx(0, SDL_HelperWindowClassName, + SDL_HelperWindowName, + WS_OVERLAPPED, CW_USEDEFAULT, + CW_USEDEFAULT, CW_USEDEFAULT, + CW_USEDEFAULT, HWND_MESSAGE, NULL, + hInstance, NULL); + if (!SDL_HelperWindow) { + UnregisterClass(SDL_HelperWindowClassName, hInstance); + return WIN_SetError("Unable to create Helper Window"); + } + + return true; +} + +/* + * Destroys the HelperWindow previously created with SDL_HelperWindowCreate. + */ +void SDL_HelperWindowDestroy(void) +{ + HINSTANCE hInstance = GetModuleHandle(NULL); + + // Destroy the window. + if (SDL_HelperWindow != NULL) { + if (DestroyWindow(SDL_HelperWindow) == 0) { + WIN_SetError("Unable to destroy Helper Window"); + return; + } + SDL_HelperWindow = NULL; + } + + // Unregister the class. + if (SDL_HelperWindowClass != 0) { + if ((UnregisterClass(SDL_HelperWindowClassName, hInstance)) == 0) { + WIN_SetError("Unable to destroy Helper Window Class"); + return; + } + SDL_HelperWindowClass = 0; + } +} + +#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) +void WIN_OnWindowEnter(SDL_VideoDevice *_this, SDL_Window *window) +{ + SDL_WindowData *data = window->internal; + + if (!data || !data->hwnd) { + // The window wasn't fully initialized + return; + } + + if (window->flags & SDL_WINDOW_ALWAYS_ON_TOP) { + WIN_SetWindowPositionInternal(window, data->copybits_flag | SWP_NOSIZE | SWP_NOACTIVATE, SDL_WINDOWRECT_CURRENT); + } +} + +static BOOL GetClientScreenRect(HWND hwnd, RECT *rect) +{ + return GetClientRect(hwnd, rect) && // RECT( left , top , right , bottom ) + ClientToScreen(hwnd, (LPPOINT)rect) && // POINT( left , top ) + ClientToScreen(hwnd, (LPPOINT)rect + 1); // POINT( right , bottom ) +} + +void WIN_UnclipCursorForWindow(SDL_Window *window) { + SDL_WindowData *data = window->internal; + RECT rect; + if (GetClipCursor(&rect) && SDL_memcmp(&rect, &data->cursor_clipped_rect, sizeof(rect)) == 0) { + ClipCursor(NULL); + SDL_zero(data->cursor_clipped_rect); + } +} + +void WIN_UpdateClipCursor(SDL_Window *window) +{ + SDL_WindowData *data = window->internal; + if (data->in_title_click || data->focus_click_pending || data->skip_update_clipcursor) { + return; + } + + SDL_Rect mouse_rect = window->mouse_rect; + bool win_mouse_rect = (mouse_rect.w > 0 && mouse_rect.h > 0); + bool win_have_focus = (window->flags & SDL_WINDOW_INPUT_FOCUS); + bool win_is_grabbed = (window->flags & SDL_WINDOW_MOUSE_GRABBED); + bool win_in_relmode = (window->flags & SDL_WINDOW_MOUSE_RELATIVE_MODE); + bool cursor_confine = win_in_relmode || win_is_grabbed || win_mouse_rect; + + // This is verbatim translation of the old logic, + // but I don't quite get what it's trying to do. + // A clean-room implementation according to MSDN + // documentation of GetClipCursor is provided in + // a commented-out block below. + if (!win_have_focus || !cursor_confine) { + SDL_VideoDevice *videodevice = SDL_GetVideoDevice(); + RECT current; + if (!GetClipCursor(¤t)) { + return; + } + if (videodevice && ( + current.left != videodevice->desktop_bounds.x || + current.top != videodevice->desktop_bounds.y + )) { + POINT first, second; + first.x = current.left; + first.y = current.top; + second.x = current.right - 1; + second.y = current.bottom - 1; + if (!PtInRect(&data->cursor_clipped_rect, first) || + !PtInRect(&data->cursor_clipped_rect, second)) { + return; + } + } + ClipCursor(NULL); + SDL_zero(data->cursor_clipped_rect); + return; + } + + // if (!win_have_focus || !cursor_confine) { + // RECT current; + // SDL_VideoDevice *videodevice = SDL_GetVideoDevice(); + // if (GetClipCursor(¤t) && (!videodevice || + // current.left != videodevice->desktop_bounds.x || + // current.top != videodevice->desktop_bounds.y || + // current.right != videodevice->desktop_bounds.x + videodevice->desktop_bounds.w || + // current.bottom != videodevice->desktop_bounds.y + videodevice->desktop_bounds.h )) { + // ClipCursor(NULL); + // SDL_zero(data->cursor_clipped_rect); + // } + // return; + // } + + SDL_Mouse *mouse = SDL_GetMouse(); + bool lock_to_ctr = (mouse->relative_mode && mouse->relative_mode_center); + + RECT client; + if (!GetClientScreenRect(data->hwnd, &client)) { + return; + } + + RECT target = client; + if (lock_to_ctr) { + LONG cx = (client.left + client.right ) / 2; + LONG cy = (client.top + client.bottom) / 2; + target = data->cursor_ctrlock_rect; + target.left += cx; + target.right += cx; + target.top += cy; + target.bottom += cy; + } else if (win_mouse_rect) { + RECT custom, overlap; + custom.left = client.left + mouse_rect.x; + custom.top = client.top + mouse_rect.y; + custom.right = client.left + mouse_rect.x + mouse_rect.w; + custom.bottom = client.top + mouse_rect.y + mouse_rect.h; + if (IntersectRect(&overlap, &client, &custom)) { + target = overlap; + } else if (!win_is_grabbed) { + WIN_UnclipCursorForWindow(window); + return; + } + } + + if (GetClipCursor(&client) && + 0 != SDL_memcmp(&target, &client, sizeof(client)) && + ClipCursor(&target)) { + data->cursor_clipped_rect = target; // ClipCursor may fail if rect beyond screen + } +} + +bool WIN_SetWindowHitTest(SDL_Window *window, bool enabled) +{ + return true; // just succeed, the real work is done elsewhere. +} +#endif // !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) + +bool WIN_SetWindowOpacity(SDL_VideoDevice *_this, SDL_Window *window, float opacity) +{ +#if defined(SDL_PLATFORM_XBOXONE) || defined(SDL_PLATFORM_XBOXSERIES) + return false; +#else + const SDL_WindowData *data = window->internal; + HWND hwnd = data->hwnd; + const LONG style = GetWindowLong(hwnd, GWL_EXSTYLE); + + SDL_assert(style != 0); + + if (opacity == 1.0f) { + // want it fully opaque, just mark it unlayered if necessary. + if (style & WS_EX_LAYERED) { + if (SetWindowLong(hwnd, GWL_EXSTYLE, style & ~WS_EX_LAYERED) == 0) { + return WIN_SetError("SetWindowLong()"); + } + } + } else { + const BYTE alpha = (BYTE)((int)(opacity * 255.0f)); + // want it transparent, mark it layered if necessary. + if (!(style & WS_EX_LAYERED)) { + if (SetWindowLong(hwnd, GWL_EXSTYLE, style | WS_EX_LAYERED) == 0) { + return WIN_SetError("SetWindowLong()"); + } + } + + if (SetLayeredWindowAttributes(hwnd, 0, alpha, LWA_ALPHA) == 0) { + return WIN_SetError("SetLayeredWindowAttributes()"); + } + } + + return true; +#endif // !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) +} + +#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) + +static const char *SDLGetClipboardFormatName(UINT cf, char *text, int len) +{ + switch (cf) { + case CF_TEXT: + return "CF_TEXT"; + case CF_BITMAP: + return "CF_BITMAP"; + case CF_METAFILEPICT: + return "CF_METAFILEPICT"; + case CF_SYLK: + return "CF_SYLK"; + case CF_DIF: + return "CF_DIF"; + case CF_TIFF: + return "CF_TIFF"; + case CF_OEMTEXT: + return "CF_OEMTEXT"; + case CF_DIB: + return "CF_DIB"; + case CF_PALETTE: + return "CF_PALETTE"; + case CF_PENDATA: + return "CF_PENDATA"; + case CF_RIFF: + return "CF_RIFF"; + case CF_WAVE: + return "CF_WAVE"; + case CF_UNICODETEXT: + return "CF_UNICODETEXT"; + case CF_ENHMETAFILE: + return "CF_ENHMETAFILE"; + case CF_HDROP: + return "CF_HDROP"; + case CF_LOCALE: + return "CF_LOCALE"; + case CF_DIBV5: + return "CF_DIBV5"; + case CF_OWNERDISPLAY: + return "CF_OWNERDISPLAY"; + case CF_DSPTEXT: + return "CF_DSPTEXT"; + case CF_DSPBITMAP: + return "CF_DSPBITMAP"; + case CF_DSPMETAFILEPICT: + return "CF_DSPMETAFILEPICT"; + case CF_DSPENHMETAFILE: + return "CF_DSPENHMETAFILE"; + default: + if (GetClipboardFormatNameA(cf, text, len)) { + return text; + } else { + return NULL; + } + } +} + +static STDMETHODIMP_(ULONG) SDLDropTarget_AddRef(SDLDropTarget *target) +{ + return ++target->refcount; +} + +static STDMETHODIMP_(ULONG) SDLDropTarget_Release(SDLDropTarget *target) +{ + --target->refcount; + if (target->refcount == 0) { + SDL_free(target); + return 0; + } + return target->refcount; +} + +static STDMETHODIMP SDLDropTarget_QueryInterface(SDLDropTarget *target, REFIID riid, PVOID *ppv) +{ + if (ppv == NULL) { + return E_INVALIDARG; + } + + *ppv = NULL; + if (WIN_IsEqualIID(riid, &IID_IUnknown) || + WIN_IsEqualIID(riid, &IID_IDropTarget)) { + *ppv = (void *)target; + } + if (*ppv) { + SDLDropTarget_AddRef(target); + return S_OK; + } + return E_NOINTERFACE; +} + +static STDMETHODIMP SDLDropTarget_DragEnter(SDLDropTarget *target, + IDataObject *pDataObject, DWORD grfKeyState, + POINTL pt, DWORD *pdwEffect) +{ + SDL_LogTrace(SDL_LOG_CATEGORY_INPUT, + ". In DragEnter at %ld, %ld", pt.x, pt.y); + *pdwEffect = DROPEFFECT_COPY; + POINT pnt = { pt.x, pt.y }; + if (ScreenToClient(target->hwnd, &pnt)) { + SDL_LogTrace(SDL_LOG_CATEGORY_INPUT, + ". In DragEnter at %ld, %ld => window %u at %ld, %ld", pt.x, pt.y, target->window->id, pnt.x, pnt.y); + SDL_SendDropPosition(target->window, pnt.x, pnt.y); + } else { + SDL_LogTrace(SDL_LOG_CATEGORY_INPUT, + ". In DragEnter at %ld, %ld => nil, nil", pt.x, pt.y); + } + return S_OK; +} + +static STDMETHODIMP SDLDropTarget_DragOver(SDLDropTarget *target, + DWORD grfKeyState, + POINTL pt, DWORD *pdwEffect) +{ + SDL_LogTrace(SDL_LOG_CATEGORY_INPUT, + ". In DragOver at %ld, %ld", pt.x, pt.y); + *pdwEffect = DROPEFFECT_COPY; + POINT pnt = { pt.x, pt.y }; + if (ScreenToClient(target->hwnd, &pnt)) { + SDL_LogTrace(SDL_LOG_CATEGORY_INPUT, + ". In DragOver at %ld, %ld => window %u at %ld, %ld", pt.x, pt.y, target->window->id, pnt.x, pnt.y); + SDL_SendDropPosition(target->window, pnt.x, pnt.y); + } else { + SDL_LogTrace(SDL_LOG_CATEGORY_INPUT, + ". In DragOver at %ld, %ld => nil, nil", pt.x, pt.y); + } + return S_OK; +} + +static STDMETHODIMP SDLDropTarget_DragLeave(SDLDropTarget *target) +{ + SDL_LogTrace(SDL_LOG_CATEGORY_INPUT, + ". In DragLeave"); + SDL_SendDropComplete(target->window); + return S_OK; +} + +static STDMETHODIMP SDLDropTarget_Drop(SDLDropTarget *target, + IDataObject *pDataObject, DWORD grfKeyState, + POINTL pt, DWORD *pdwEffect) +{ + *pdwEffect = DROPEFFECT_COPY; + POINT pnt = { pt.x, pt.y }; + if (ScreenToClient(target->hwnd, &pnt)) { + SDL_LogTrace(SDL_LOG_CATEGORY_INPUT, + ". In Drop at %ld, %ld => window %u at %ld, %ld", pt.x, pt.y, target->window->id, pnt.x, pnt.y); + SDL_SendDropPosition(target->window, pnt.x, pnt.y); + } else { + SDL_LogTrace(SDL_LOG_CATEGORY_INPUT, + ". In Drop at %ld, %ld => nil, nil", pt.x, pt.y); + } + + { + IEnumFORMATETC *pEnumFormatEtc; + HRESULT hres; + hres = pDataObject->lpVtbl->EnumFormatEtc(pDataObject, DATADIR_GET, &pEnumFormatEtc); + SDL_LogTrace(SDL_LOG_CATEGORY_INPUT, + ". In Drop for EnumFormatEtc, HRESULT is %08lx", hres); + if (hres == S_OK) { + FORMATETC fetc; + while (pEnumFormatEtc->lpVtbl->Next(pEnumFormatEtc, 1, &fetc, NULL) == S_OK) { + char name[257] = { 0 }; + const char *cfnm = SDLGetClipboardFormatName(fetc.cfFormat, name, 256); + if (cfnm) { + SDL_LogTrace(SDL_LOG_CATEGORY_INPUT, + ". In Drop, Supported format is %08x, '%s'", fetc.cfFormat, cfnm); + } else { + SDL_LogTrace(SDL_LOG_CATEGORY_INPUT, + ". In Drop, Supported format is %08x, Predefined", fetc.cfFormat); + } + } + } + } + + { + FORMATETC fetc; + fetc.cfFormat = target->format_file; + fetc.ptd = NULL; + fetc.dwAspect = DVASPECT_CONTENT; + fetc.lindex = -1; + fetc.tymed = TYMED_HGLOBAL; + const char *format_mime = "text/uri-list"; + if (SUCCEEDED(pDataObject->lpVtbl->QueryGetData(pDataObject, &fetc))) { + SDL_LogTrace(SDL_LOG_CATEGORY_INPUT, + ". In Drop File for QueryGetData, format %08x '%s', success", + fetc.cfFormat, format_mime); + STGMEDIUM med; + HRESULT hres = pDataObject->lpVtbl->GetData(pDataObject, &fetc, &med); + SDL_LogTrace(SDL_LOG_CATEGORY_INPUT, + ". In Drop File for GetData, format %08x '%s', HRESULT is %08lx", + fetc.cfFormat, format_mime, hres); + if (SUCCEEDED(hres)) { + const size_t bsize = GlobalSize(med.hGlobal); + const void *buffer = (void *)GlobalLock(med.hGlobal); + SDL_LogTrace(SDL_LOG_CATEGORY_INPUT, + ". In Drop File for GlobalLock, format %08x '%s', memory (%lu) %p", + fetc.cfFormat, format_mime, (unsigned long)bsize, buffer); + if (buffer) { + char *text = (char *)SDL_malloc(bsize + sizeof(Uint32)); + SDL_memcpy((Uint8 *)text, buffer, bsize); + SDL_memset((Uint8 *)text + bsize, 0, sizeof(Uint32)); + char *saveptr = NULL; + char *token = SDL_strtok_r(text, "\r\n", &saveptr); + while (token != NULL) { + if (SDL_URIToLocal(token, token) >= 0) { + SDL_LogTrace(SDL_LOG_CATEGORY_INPUT, + ". In Drop File, file (%lu of %lu) '%s'", + (unsigned long)SDL_strlen(token), (unsigned long)bsize, token); + SDL_SendDropFile(target->window, NULL, token); + } + token = SDL_strtok_r(NULL, "\r\n", &saveptr); + } + SDL_free(text); + } + GlobalUnlock(med.hGlobal); + ReleaseStgMedium(&med); + SDL_SendDropComplete(target->window); + return S_OK; + } + } + } + + { + FORMATETC fetc; + fetc.cfFormat = target->format_text; + fetc.ptd = NULL; + fetc.dwAspect = DVASPECT_CONTENT; + fetc.lindex = -1; + fetc.tymed = TYMED_HGLOBAL; + const char *format_mime = "text/plain;charset=utf-8"; + if (SUCCEEDED(pDataObject->lpVtbl->QueryGetData(pDataObject, &fetc))) { + SDL_LogTrace(SDL_LOG_CATEGORY_INPUT, + ". In Drop Text for QueryGetData, format %08x '%s', success", + fetc.cfFormat, format_mime); + STGMEDIUM med; + HRESULT hres = pDataObject->lpVtbl->GetData(pDataObject, &fetc, &med); + SDL_LogTrace(SDL_LOG_CATEGORY_INPUT, + ". In Drop Text for GetData, format %08x '%s', HRESULT is %08lx", + fetc.cfFormat, format_mime, hres); + if (SUCCEEDED(hres)) { + const size_t bsize = GlobalSize(med.hGlobal); + const void *buffer = (void *)GlobalLock(med.hGlobal); + SDL_LogTrace(SDL_LOG_CATEGORY_INPUT, + ". In Drop Text for GlobalLock, format %08x '%s', memory (%lu) %p", + fetc.cfFormat, format_mime, (unsigned long)bsize, buffer); + if (buffer) { + char *text = (char *)SDL_malloc(bsize + sizeof(Uint32)); + SDL_memcpy((Uint8 *)text, buffer, bsize); + SDL_memset((Uint8 *)text + bsize, 0, sizeof(Uint32)); + char *saveptr = NULL; + char *token = SDL_strtok_r(text, "\r\n", &saveptr); + while (token != NULL) { + SDL_LogTrace(SDL_LOG_CATEGORY_INPUT, + ". In Drop Text, text (%lu of %lu) '%s'", + (unsigned long)SDL_strlen(token), (unsigned long)bsize, token); + SDL_SendDropText(target->window, (char *)token); + token = SDL_strtok_r(NULL, "\r\n", &saveptr); + } + SDL_free(text); + } + GlobalUnlock(med.hGlobal); + ReleaseStgMedium(&med); + SDL_SendDropComplete(target->window); + return S_OK; + } + } + } + + { + FORMATETC fetc; + fetc.cfFormat = CF_UNICODETEXT; + fetc.ptd = NULL; + fetc.dwAspect = DVASPECT_CONTENT; + fetc.lindex = -1; + fetc.tymed = TYMED_HGLOBAL; + const char *format_mime = "CF_UNICODETEXT"; + if (SUCCEEDED(pDataObject->lpVtbl->QueryGetData(pDataObject, &fetc))) { + SDL_LogTrace(SDL_LOG_CATEGORY_INPUT, + ". In Drop Text for QueryGetData, format %08x '%s', success", + fetc.cfFormat, format_mime); + STGMEDIUM med; + HRESULT hres = pDataObject->lpVtbl->GetData(pDataObject, &fetc, &med); + SDL_LogTrace(SDL_LOG_CATEGORY_INPUT, + ". In Drop Text for GetData, format %08x '%s', HRESULT is %08lx", + fetc.cfFormat, format_mime, hres); + if (SUCCEEDED(hres)) { + const size_t bsize = GlobalSize(med.hGlobal); + const void *buffer = (void *)GlobalLock(med.hGlobal); + SDL_LogTrace(SDL_LOG_CATEGORY_INPUT, + ". In Drop Text for GlobalLock, format %08x '%s', memory (%lu) %p", + fetc.cfFormat, format_mime, (unsigned long)bsize, buffer); + if (buffer) { + buffer = WIN_StringToUTF8((const wchar_t *)buffer); + if (buffer) { + const size_t lbuffer = SDL_strlen((const char *)buffer); + SDL_LogTrace(SDL_LOG_CATEGORY_INPUT, + ". In Drop Text for StringToUTF8, format %08x '%s', memory (%lu) %p", + fetc.cfFormat, format_mime, (unsigned long)lbuffer, buffer); + char *text = (char *)SDL_malloc(lbuffer + sizeof(Uint32)); + SDL_memcpy((Uint8 *)text, buffer, lbuffer); + SDL_memset((Uint8 *)text + lbuffer, 0, sizeof(Uint32)); + char *saveptr = NULL; + char *token = SDL_strtok_r(text, "\r\n", &saveptr); + while (token != NULL) { + SDL_LogTrace(SDL_LOG_CATEGORY_INPUT, + ". In Drop Text, text (%lu of %lu) '%s'", + (unsigned long)SDL_strlen(token), (unsigned long)lbuffer, token); + SDL_SendDropText(target->window, (char *)token); + token = SDL_strtok_r(NULL, "\r\n", &saveptr); + } + SDL_free(text); + SDL_free((void *)buffer); + } + } + GlobalUnlock(med.hGlobal); + ReleaseStgMedium(&med); + SDL_SendDropComplete(target->window); + return S_OK; + } + } + } + + { + FORMATETC fetc; + fetc.cfFormat = CF_TEXT; + fetc.ptd = NULL; + fetc.dwAspect = DVASPECT_CONTENT; + fetc.lindex = -1; + fetc.tymed = TYMED_HGLOBAL; + const char *format_mime = "CF_TEXT"; + if (SUCCEEDED(pDataObject->lpVtbl->QueryGetData(pDataObject, &fetc))) { + SDL_LogTrace(SDL_LOG_CATEGORY_INPUT, + ". In Drop Text for QueryGetData, format %08x '%s', success", + fetc.cfFormat, format_mime); + STGMEDIUM med; + HRESULT hres = pDataObject->lpVtbl->GetData(pDataObject, &fetc, &med); + SDL_LogTrace(SDL_LOG_CATEGORY_INPUT, + ". In Drop Text for GetData, format %08x '%s', HRESULT is %08lx", + fetc.cfFormat, format_mime, hres); + if (SUCCEEDED(hres)) { + const size_t bsize = GlobalSize(med.hGlobal); + const void *buffer = (void *)GlobalLock(med.hGlobal); + SDL_LogTrace(SDL_LOG_CATEGORY_INPUT, + ". In Drop Text for GlobalLock, format %08x '%s', memory (%lu) %p", + fetc.cfFormat, format_mime, (unsigned long)bsize, buffer); + if (buffer) { + char *text = (char *)SDL_malloc(bsize + sizeof(Uint32)); + SDL_memcpy((Uint8 *)text, buffer, bsize); + SDL_memset((Uint8 *)text + bsize, 0, sizeof(Uint32)); + char *saveptr = NULL; + char *token = SDL_strtok_r(text, "\r\n", &saveptr); + while (token != NULL) { + SDL_LogTrace(SDL_LOG_CATEGORY_INPUT, + ". In Drop Text, text (%lu of %lu) '%s'", + (unsigned long)SDL_strlen(token), (unsigned long)bsize, token); + SDL_SendDropText(target->window, (char *)token); + token = SDL_strtok_r(NULL, "\r\n", &saveptr); + } + SDL_free(text); + } + GlobalUnlock(med.hGlobal); + ReleaseStgMedium(&med); + SDL_SendDropComplete(target->window); + return S_OK; + } + } + } + + { + FORMATETC fetc; + fetc.cfFormat = CF_HDROP; + fetc.ptd = NULL; + fetc.dwAspect = DVASPECT_CONTENT; + fetc.lindex = -1; + fetc.tymed = TYMED_HGLOBAL; + const char *format_mime = "CF_HDROP"; + if (SUCCEEDED(pDataObject->lpVtbl->QueryGetData(pDataObject, &fetc))) { + SDL_LogTrace(SDL_LOG_CATEGORY_INPUT, + ". In Drop File for QueryGetData, format %08x '%s', success", + fetc.cfFormat, format_mime); + STGMEDIUM med; + HRESULT hres = pDataObject->lpVtbl->GetData(pDataObject, &fetc, &med); + SDL_LogTrace(SDL_LOG_CATEGORY_INPUT, + ". In Drop File for GetData, format %08x '%s', HRESULT is %08lx", + fetc.cfFormat, format_mime, hres); + if (SUCCEEDED(hres)) { + const size_t bsize = GlobalSize(med.hGlobal); + HDROP drop = (HDROP)GlobalLock(med.hGlobal); + SDL_LogTrace(SDL_LOG_CATEGORY_INPUT, + ". In Drop File for GlobalLock, format %08x '%s', memory (%lu) %p", + fetc.cfFormat, format_mime, (unsigned long)bsize, drop); + UINT count = DragQueryFile(drop, 0xFFFFFFFF, NULL, 0); + for (UINT i = 0; i < count; ++i) { + UINT size = DragQueryFile(drop, i, NULL, 0) + 1; + LPTSTR buffer = (LPTSTR)SDL_malloc(size * sizeof(TCHAR)); + if (buffer) { + if (DragQueryFile(drop, i, buffer, size)) { + char *file = WIN_StringToUTF8(buffer); + SDL_LogTrace(SDL_LOG_CATEGORY_INPUT, + ". In Drop File, file (%lu of %lu) '%s'", + (unsigned long)SDL_strlen(file), (unsigned long)bsize, file); + SDL_SendDropFile(target->window, NULL, file); + SDL_free(file); + } + SDL_free(buffer); + } + } + GlobalUnlock(med.hGlobal); + ReleaseStgMedium(&med); + SDL_SendDropComplete(target->window); + return S_OK; + } + } + } + + SDL_SendDropComplete(target->window); + return S_OK; +} + +static void *vtDropTarget[] = { + (void *)(SDLDropTarget_QueryInterface), + (void *)(SDLDropTarget_AddRef), + (void *)(SDLDropTarget_Release), + (void *)(SDLDropTarget_DragEnter), + (void *)(SDLDropTarget_DragOver), + (void *)(SDLDropTarget_DragLeave), + (void *)(SDLDropTarget_Drop) +}; + +void WIN_AcceptDragAndDrop(SDL_Window *window, bool accept) +{ + SDL_WindowData *data = window->internal; + if (data->videodata->oleinitialized) { + if (accept && !data->drop_target) { + SDLDropTarget *drop_target = (SDLDropTarget *)SDL_calloc(1, sizeof(SDLDropTarget)); + if (drop_target != NULL) { + drop_target->lpVtbl = vtDropTarget; + drop_target->window = window; + drop_target->hwnd = data->hwnd; + drop_target->format_file = RegisterClipboardFormat(L"text/uri-list"); + drop_target->format_text = RegisterClipboardFormat(L"text/plain;charset=utf-8"); + data->drop_target = drop_target; + SDLDropTarget_AddRef(drop_target); + RegisterDragDrop(data->hwnd, (LPDROPTARGET)drop_target); + SDL_LogTrace(SDL_LOG_CATEGORY_INPUT, + ". In Accept Drag and Drop, window %u, enabled Full OLE IDropTarget", + window->id); + } + } else if (!accept && data->drop_target) { + RevokeDragDrop(data->hwnd); + SDLDropTarget_Release(data->drop_target); + data->drop_target = NULL; + SDL_LogTrace(SDL_LOG_CATEGORY_INPUT, + ". In Accept Drag and Drop, window %u, disabled Full OLE IDropTarget", + window->id); + } + } else { + DragAcceptFiles(data->hwnd, accept ? TRUE : FALSE); + SDL_LogTrace(SDL_LOG_CATEGORY_INPUT, + ". In Accept Drag and Drop, window %u, %s Fallback WM_DROPFILES", + window->id, (accept ? "enabled" : "disabled")); + } +} + +bool WIN_FlashWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_FlashOperation operation) +{ + FLASHWINFO desc; + + SDL_zero(desc); + desc.cbSize = sizeof(desc); + desc.hwnd = window->internal->hwnd; + switch (operation) { + case SDL_FLASH_CANCEL: + desc.dwFlags = FLASHW_STOP; + break; + case SDL_FLASH_BRIEFLY: + desc.dwFlags = FLASHW_TRAY; + desc.uCount = 1; + break; + case SDL_FLASH_UNTIL_FOCUSED: + desc.dwFlags = (FLASHW_TRAY | FLASHW_TIMERNOFG); + break; + default: + return SDL_Unsupported(); + } + + FlashWindowEx(&desc); + + return true; +} + +void WIN_ShowWindowSystemMenu(SDL_Window *window, int x, int y) +{ + const SDL_WindowData *data = window->internal; + POINT pt; + + pt.x = x; + pt.y = y; + ClientToScreen(data->hwnd, &pt); + SendMessage(data->hwnd, WM_POPUPSYSTEMMENU, 0, MAKELPARAM(pt.x, pt.y)); +} + +bool WIN_SetWindowFocusable(SDL_VideoDevice *_this, SDL_Window *window, bool focusable) +{ + SDL_WindowData *data = window->internal; + HWND hwnd = data->hwnd; + const LONG style = GetWindowLong(hwnd, GWL_EXSTYLE); + + SDL_assert(style != 0); + + if (focusable) { + if (style & WS_EX_NOACTIVATE) { + if (SetWindowLong(hwnd, GWL_EXSTYLE, style & ~WS_EX_NOACTIVATE) == 0) { + return WIN_SetError("SetWindowLong()"); + } + } + } else { + if (!(style & WS_EX_NOACTIVATE)) { + if (SetWindowLong(hwnd, GWL_EXSTYLE, style | WS_EX_NOACTIVATE) == 0) { + return WIN_SetError("SetWindowLong()"); + } + } + } + + return true; +} +#endif // !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) + +void WIN_UpdateDarkModeForHWND(HWND hwnd) +{ +#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) + SDL_SharedObject *ntdll = SDL_LoadObject("ntdll.dll"); + if (!ntdll) { + return; + } + // There is no function to get Windows build number, so let's get it here via RtlGetVersion + RtlGetVersion_t RtlGetVersionFunc = (RtlGetVersion_t)SDL_LoadFunction(ntdll, "RtlGetVersion"); + NT_OSVERSIONINFOW os_info; + os_info.dwOSVersionInfoSize = sizeof(NT_OSVERSIONINFOW); + os_info.dwBuildNumber = 0; + if (RtlGetVersionFunc) { + RtlGetVersionFunc(&os_info); + } + SDL_UnloadObject(ntdll); + os_info.dwBuildNumber &= ~0xF0000000; + if (os_info.dwBuildNumber < 17763) { + // Too old to support dark mode + return; + } + SDL_SharedObject *uxtheme = SDL_LoadObject("uxtheme.dll"); + if (!uxtheme) { + return; + } + RefreshImmersiveColorPolicyState_t RefreshImmersiveColorPolicyStateFunc = (RefreshImmersiveColorPolicyState_t)SDL_LoadFunction(uxtheme, MAKEINTRESOURCEA(104)); + ShouldAppsUseDarkMode_t ShouldAppsUseDarkModeFunc = (ShouldAppsUseDarkMode_t)SDL_LoadFunction(uxtheme, MAKEINTRESOURCEA(132)); + AllowDarkModeForWindow_t AllowDarkModeForWindowFunc = (AllowDarkModeForWindow_t)SDL_LoadFunction(uxtheme, MAKEINTRESOURCEA(133)); + if (os_info.dwBuildNumber < 18362) { + AllowDarkModeForApp_t AllowDarkModeForAppFunc = (AllowDarkModeForApp_t)SDL_LoadFunction(uxtheme, MAKEINTRESOURCEA(135)); + if (AllowDarkModeForAppFunc) { + AllowDarkModeForAppFunc(true); + } + } else { + SetPreferredAppMode_t SetPreferredAppModeFunc = (SetPreferredAppMode_t)SDL_LoadFunction(uxtheme, MAKEINTRESOURCEA(135)); + if (SetPreferredAppModeFunc) { + SetPreferredAppModeFunc(UXTHEME_APPMODE_ALLOW_DARK); + } + } + if (RefreshImmersiveColorPolicyStateFunc) { + RefreshImmersiveColorPolicyStateFunc(); + } + if (AllowDarkModeForWindowFunc) { + AllowDarkModeForWindowFunc(hwnd, true); + } + BOOL value; + // Check dark mode using ShouldAppsUseDarkMode, but use SDL_GetSystemTheme as a fallback + if (ShouldAppsUseDarkModeFunc) { + value = ShouldAppsUseDarkModeFunc() ? TRUE : FALSE; + } else { + value = (SDL_GetSystemTheme() == SDL_SYSTEM_THEME_DARK) ? TRUE : FALSE; + } + SDL_UnloadObject(uxtheme); + if (os_info.dwBuildNumber < 18362) { + SetProp(hwnd, TEXT("UseImmersiveDarkModeColors"), SDL_reinterpret_cast(HANDLE, SDL_static_cast(INT_PTR, value))); + } else { + HMODULE user32 = GetModuleHandle(TEXT("user32.dll")); + if (user32) { + SetWindowCompositionAttribute_t SetWindowCompositionAttributeFunc = (SetWindowCompositionAttribute_t)GetProcAddress(user32, "SetWindowCompositionAttribute"); + if (SetWindowCompositionAttributeFunc) { + WINDOWCOMPOSITIONATTRIBDATA data = { WCA_USEDARKMODECOLORS, &value, sizeof(value) }; + SetWindowCompositionAttributeFunc(hwnd, &data); + } + } + } +#endif +} + +bool WIN_SetWindowParent(SDL_VideoDevice *_this, SDL_Window *window, SDL_Window *parent) +{ +#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) + SDL_WindowData *child_data = window->internal; + const LONG_PTR parent_hwnd = (LONG_PTR)(parent ? parent->internal->hwnd : NULL); + const DWORD style = GetWindowLong(child_data->hwnd, GWL_STYLE); + + if (!(style & WS_CHILD)) { + /* Despite the name, this changes the *owner* of a toplevel window, not + * the parent of a child window. + * + * https://devblogs.microsoft.com/oldnewthing/20100315-00/?p=14613 + */ + SetWindowLongPtr(child_data->hwnd, GWLP_HWNDPARENT, parent_hwnd); + } else { + SetParent(child_data->hwnd, (HWND)parent_hwnd); + } +#endif /*!defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)*/ + + return true; +} + +bool WIN_SetWindowModal(SDL_VideoDevice *_this, SDL_Window *window, bool modal) +{ +#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) + const HWND parent_hwnd = window->parent->internal->hwnd; + + if (modal) { + // Disable the parent window. + EnableWindow(parent_hwnd, FALSE); + } else if (!(window->flags & SDL_WINDOW_HIDDEN)) { + // Re-enable the parent window + EnableWindow(parent_hwnd, TRUE); + } +#endif // !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) + + return true; +} + +#endif // SDL_VIDEO_DRIVER_WINDOWS diff --git a/contrib/SDL-3.2.8/src/video/windows/SDL_windowswindow.h b/contrib/SDL-3.2.8/src/video/windows/SDL_windowswindow.h new file mode 100644 index 0000000..a2c9a21 --- /dev/null +++ b/contrib/SDL-3.2.8/src/video/windows/SDL_windowswindow.h @@ -0,0 +1,152 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include "SDL_internal.h" + +#ifndef SDL_windowswindow_h_ +#define SDL_windowswindow_h_ + +#ifdef SDL_VIDEO_OPENGL_EGL +#include "../SDL_egl_c.h" +#else +#include "../SDL_sysvideo.h" +#endif + +// Set up for C function definitions, even when using C++ +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum SDL_WindowRect +{ + SDL_WINDOWRECT_CURRENT, + SDL_WINDOWRECT_WINDOWED, + SDL_WINDOWRECT_FLOATING, + SDL_WINDOWRECT_PENDING +} SDL_WindowRect; + +typedef enum SDL_WindowEraseBackgroundMode +{ + SDL_ERASEBACKGROUNDMODE_NEVER, + SDL_ERASEBACKGROUNDMODE_INITIAL, + SDL_ERASEBACKGROUNDMODE_ALWAYS, +} SDL_WindowEraseBackgroundMode; + +typedef struct +{ + void **lpVtbl; + int refcount; + SDL_Window *window; + HWND hwnd; + UINT format_text; + UINT format_file; +} SDLDropTarget; + +struct SDL_WindowData +{ + SDL_Window *window; + HWND hwnd; + HWND parent; + HDC hdc; + HDC mdc; + HINSTANCE hinstance; + HBITMAP hbm; + WNDPROC wndproc; + HHOOK keyboard_hook; + WPARAM mouse_button_flags; + LPARAM last_pointer_update; + WCHAR high_surrogate; + bool initializing; + bool expected_resize; + bool in_border_change; + bool in_title_click; + Uint8 focus_click_pending; + bool skip_update_clipcursor; + bool windowed_mode_was_maximized; + bool in_window_deactivation; + bool force_ws_maximizebox; + bool disable_move_size_events; + int in_modal_loop; + RECT initial_size_rect; + RECT cursor_clipped_rect; // last successfully committed clipping rect for this window + RECT cursor_ctrlock_rect; // this is Windows-specific, but probably does not need to be per-window + UINT windowed_mode_corner_rounding; + COLORREF dwma_border_color; + bool mouse_tracked; + bool destroy_parent_with_window; + SDL_DisplayID last_displayID; + WCHAR *ICMFileName; + SDL_Window *keyboard_focus; + SDL_WindowEraseBackgroundMode hint_erase_background_mode; + struct SDL_VideoData *videodata; +#ifdef SDL_VIDEO_OPENGL_EGL + EGLSurface egl_surface; +#endif + + // Whether we retain the content of the window when changing state + UINT copybits_flag; + SDLDropTarget *drop_target; +}; + +extern bool WIN_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID create_props); +extern void WIN_SetWindowTitle(SDL_VideoDevice *_this, SDL_Window *window); +extern bool WIN_SetWindowIcon(SDL_VideoDevice *_this, SDL_Window *window, SDL_Surface *icon); +extern bool WIN_SetWindowPosition(SDL_VideoDevice *_this, SDL_Window *window); +extern void WIN_SetWindowSize(SDL_VideoDevice *_this, SDL_Window *window); +extern bool WIN_GetWindowBordersSize(SDL_VideoDevice *_this, SDL_Window *window, int *top, int *left, int *bottom, int *right); +extern void WIN_GetWindowSizeInPixels(SDL_VideoDevice *_this, SDL_Window *window, int *width, int *height); +extern bool WIN_SetWindowOpacity(SDL_VideoDevice *_this, SDL_Window *window, float opacity); +extern void WIN_ShowWindow(SDL_VideoDevice *_this, SDL_Window *window); +extern void WIN_HideWindow(SDL_VideoDevice *_this, SDL_Window *window); +extern void WIN_RaiseWindow(SDL_VideoDevice *_this, SDL_Window *window); +extern void WIN_MaximizeWindow(SDL_VideoDevice *_this, SDL_Window *window); +extern void WIN_MinimizeWindow(SDL_VideoDevice *_this, SDL_Window *window); +extern void WIN_RestoreWindow(SDL_VideoDevice *_this, SDL_Window *window); +extern void WIN_SetWindowBordered(SDL_VideoDevice *_this, SDL_Window *window, bool bordered); +extern void WIN_SetWindowResizable(SDL_VideoDevice *_this, SDL_Window *window, bool resizable); +extern void WIN_SetWindowAlwaysOnTop(SDL_VideoDevice *_this, SDL_Window *window, bool on_top); +extern SDL_FullscreenResult WIN_SetWindowFullscreen(SDL_VideoDevice *_this, SDL_Window *window, SDL_VideoDisplay *display, SDL_FullscreenOp fullscreen); +extern void WIN_UpdateWindowICCProfile(SDL_Window *window, bool send_event); +extern void *WIN_GetWindowICCProfile(SDL_VideoDevice *_this, SDL_Window *window, size_t *size); +extern bool WIN_SetWindowMouseRect(SDL_VideoDevice *_this, SDL_Window *window); +extern bool WIN_SetWindowMouseGrab(SDL_VideoDevice *_this, SDL_Window *window, bool grabbed); +extern bool WIN_SetWindowKeyboardGrab(SDL_VideoDevice *_this, SDL_Window *window, bool grabbed); +extern void WIN_DestroyWindow(SDL_VideoDevice *_this, SDL_Window *window); +extern void WIN_OnWindowEnter(SDL_VideoDevice *_this, SDL_Window *window); +extern void WIN_UpdateClipCursor(SDL_Window *window); +extern void WIN_UnclipCursorForWindow(SDL_Window *window); +extern bool WIN_SetWindowHitTest(SDL_Window *window, bool enabled); +extern void WIN_AcceptDragAndDrop(SDL_Window *window, bool accept); +extern bool WIN_FlashWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_FlashOperation operation); +extern void WIN_UpdateDarkModeForHWND(HWND hwnd); +extern bool WIN_SetWindowPositionInternal(SDL_Window *window, UINT flags, SDL_WindowRect rect_type); +extern void WIN_ShowWindowSystemMenu(SDL_Window *window, int x, int y); +extern bool WIN_SetWindowFocusable(SDL_VideoDevice *_this, SDL_Window *window, bool focusable); +extern bool WIN_AdjustWindowRect(SDL_Window *window, int *x, int *y, int *width, int *height, SDL_WindowRect rect_type); +extern bool WIN_AdjustWindowRectForHWND(HWND hwnd, LPRECT lpRect, UINT frame_dpi); +extern bool WIN_SetWindowParent(SDL_VideoDevice *_this, SDL_Window *window, SDL_Window *parent); +extern bool WIN_SetWindowModal(SDL_VideoDevice *_this, SDL_Window *window, bool modal); + +// Ends C function definitions when using C++ +#ifdef __cplusplus +} +#endif + +#endif // SDL_windowswindow_h_ diff --git a/contrib/SDL-3.2.8/src/video/windows/wmmsg.h b/contrib/SDL-3.2.8/src/video/windows/wmmsg.h new file mode 100644 index 0000000..2f18aaa --- /dev/null +++ b/contrib/SDL-3.2.8/src/video/windows/wmmsg.h @@ -0,0 +1,1050 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +#define MAX_WMMSG (sizeof(wmtab) / sizeof(wmtab[0])) + +const char *wmtab[] = { + "WM_NULL", + "WM_CREATE", + "WM_DESTROY", + "WM_MOVE", + "UNKNOWN (4)", + "WM_SIZE", + "WM_ACTIVATE", + "WM_SETFOCUS", + "WM_KILLFOCUS", + "UNKNOWN (9)", + "WM_ENABLE", + "WM_SETREDRAW", + "WM_SETTEXT", + "WM_GETTEXT", + "WM_GETTEXTLENGTH", + "WM_PAINT", + "WM_CLOSE", + "WM_QUERYENDSESSION", + "WM_QUIT", + "WM_QUERYOPEN", + "WM_ERASEBKGND", + "WM_SYSCOLORCHANGE", + "WM_ENDSESSION", + "UNKNOWN (23)", + "WM_SHOWWINDOW", + "UNKNOWN (25)", + "WM_SETTINGCHANGE", + "WM_DEVMODECHANGE", + "WM_ACTIVATEAPP", + "WM_FONTCHANGE", + "WM_TIMECHANGE", + "WM_CANCELMODE", + "WM_SETCURSOR", + "WM_MOUSEACTIVATE", + "WM_CHILDACTIVATE", + "WM_QUEUESYNC", + "WM_GETMINMAXINFO", + "UNKNOWN (37)", + "WM_PAINTICON", + "WM_ICONERASEBKGND", + "WM_NEXTDLGCTL", + "UNKNOWN (41)", + "WM_SPOOLERSTATUS", + "WM_DRAWITEM", + "WM_MEASUREITEM", + "WM_DELETEITEM", + "WM_VKEYTOITEM", + "WM_CHARTOITEM", + "WM_SETFONT", + "WM_GETFONT", + "WM_SETHOTKEY", + "WM_GETHOTKEY", + "UNKNOWN (52)", + "UNKNOWN (53)", + "UNKNOWN (54)", + "WM_QUERYDRAGICON", + "UNKNOWN (56)", + "WM_COMPAREITEM", + "UNKNOWN (58)", + "UNKNOWN (59)", + "UNKNOWN (60)", + "WM_GETOBJECT", + "UNKNOWN (62)", + "UNKNOWN (63)", + "UNKNOWN (64)", + "WM_COMPACTING", + "UNKNOWN (66)", + "UNKNOWN (67)", + "WM_COMMNOTIFY", + "UNKNOWN (69)", + "WM_WINDOWPOSCHANGING", + "WM_WINDOWPOSCHANGED", + "WM_POWER", + "UNKNOWN (73)", + "WM_COPYDATA", + "WM_CANCELJOURNAL", + "UNKNOWN (76)", + "UNKNOWN (77)", + "WM_NOTIFY", + "UNKNOWN (79)", + "WM_INPUTLANGCHANGEREQUEST", + "WM_INPUTLANGCHANGE", + "WM_TCARD", + "WM_HELP", + "WM_USERCHANGED", + "WM_NOTIFYFORMAT", + "UNKNOWN (86)", + "UNKNOWN (87)", + "UNKNOWN (88)", + "UNKNOWN (89)", + "UNKNOWN (90)", + "UNKNOWN (91)", + "UNKNOWN (92)", + "UNKNOWN (93)", + "UNKNOWN (94)", + "UNKNOWN (95)", + "UNKNOWN (96)", + "UNKNOWN (97)", + "UNKNOWN (98)", + "UNKNOWN (99)", + "UNKNOWN (100)", + "UNKNOWN (101)", + "UNKNOWN (102)", + "UNKNOWN (103)", + "UNKNOWN (104)", + "UNKNOWN (105)", + "UNKNOWN (106)", + "UNKNOWN (107)", + "UNKNOWN (108)", + "UNKNOWN (109)", + "UNKNOWN (110)", + "UNKNOWN (111)", + "UNKNOWN (112)", + "UNKNOWN (113)", + "UNKNOWN (114)", + "UNKNOWN (115)", + "UNKNOWN (116)", + "UNKNOWN (117)", + "UNKNOWN (118)", + "UNKNOWN (119)", + "UNKNOWN (120)", + "UNKNOWN (121)", + "UNKNOWN (122)", + "WM_CONTEXTMENU", + "WM_STYLECHANGING", + "WM_STYLECHANGED", + "WM_DISPLAYCHANGE", + "WM_GETICON", + "WM_SETICON", + "WM_NCCREATE", + "WM_NCDESTROY", + "WM_NCCALCSIZE", + "WM_NCHITTEST", + "WM_NCPAINT", + "WM_NCACTIVATE", + "WM_GETDLGCODE", + "WM_SYNCPAINT", + "UNKNOWN (137)", + "UNKNOWN (138)", + "UNKNOWN (139)", + "UNKNOWN (140)", + "UNKNOWN (141)", + "UNKNOWN (142)", + "UNKNOWN (143)", + "UNKNOWN (144)", + "UNKNOWN (145)", + "UNKNOWN (146)", + "UNKNOWN (147)", + "UNKNOWN (148)", + "UNKNOWN (149)", + "UNKNOWN (150)", + "UNKNOWN (151)", + "UNKNOWN (152)", + "UNKNOWN (153)", + "UNKNOWN (154)", + "UNKNOWN (155)", + "UNKNOWN (156)", + "UNKNOWN (157)", + "UNKNOWN (158)", + "UNKNOWN (159)", + "WM_NCMOUSEMOVE", + "WM_NCLBUTTONDOWN", + "WM_NCLBUTTONUP", + "WM_NCLBUTTONDBLCLK", + "WM_NCRBUTTONDOWN", + "WM_NCRBUTTONUP", + "WM_NCRBUTTONDBLCLK", + "WM_NCMBUTTONDOWN", + "WM_NCMBUTTONUP", + "WM_NCMBUTTONDBLCLK", + "UNKNOWN (170)", + "WM_NCXBUTTONDOWN", + "WM_NCXBUTTONUP", + "WM_NCXBUTTONDBLCLK", + "WM_NCUAHDRAWCAPTION", + "WM_NCUAHDRAWFRAME", + "UNKNOWN (176)", + "UNKNOWN (177)", + "UNKNOWN (178)", + "UNKNOWN (179)", + "UNKNOWN (180)", + "UNKNOWN (181)", + "UNKNOWN (182)", + "UNKNOWN (183)", + "UNKNOWN (184)", + "UNKNOWN (185)", + "UNKNOWN (186)", + "UNKNOWN (187)", + "UNKNOWN (188)", + "UNKNOWN (189)", + "UNKNOWN (190)", + "UNKNOWN (191)", + "UNKNOWN (192)", + "UNKNOWN (193)", + "UNKNOWN (194)", + "UNKNOWN (195)", + "UNKNOWN (196)", + "UNKNOWN (197)", + "UNKNOWN (198)", + "UNKNOWN (199)", + "UNKNOWN (200)", + "UNKNOWN (201)", + "UNKNOWN (202)", + "UNKNOWN (203)", + "UNKNOWN (204)", + "UNKNOWN (205)", + "UNKNOWN (206)", + "UNKNOWN (207)", + "UNKNOWN (208)", + "UNKNOWN (209)", + "UNKNOWN (210)", + "UNKNOWN (211)", + "UNKNOWN (212)", + "UNKNOWN (213)", + "UNKNOWN (214)", + "UNKNOWN (215)", + "UNKNOWN (216)", + "UNKNOWN (217)", + "UNKNOWN (218)", + "UNKNOWN (219)", + "UNKNOWN (220)", + "UNKNOWN (221)", + "UNKNOWN (222)", + "UNKNOWN (223)", + "UNKNOWN (224)", + "UNKNOWN (225)", + "UNKNOWN (226)", + "UNKNOWN (227)", + "UNKNOWN (228)", + "UNKNOWN (229)", + "UNKNOWN (230)", + "UNKNOWN (231)", + "UNKNOWN (232)", + "UNKNOWN (233)", + "UNKNOWN (234)", + "UNKNOWN (235)", + "UNKNOWN (236)", + "UNKNOWN (237)", + "UNKNOWN (238)", + "UNKNOWN (239)", + "UNKNOWN (240)", + "UNKNOWN (241)", + "UNKNOWN (242)", + "UNKNOWN (243)", + "UNKNOWN (244)", + "UNKNOWN (245)", + "UNKNOWN (246)", + "UNKNOWN (247)", + "UNKNOWN (248)", + "UNKNOWN (249)", + "UNKNOWN (250)", + "UNKNOWN (251)", + "UNKNOWN (252)", + "UNKNOWN (253)", + "UNKNOWN (254)", + "WM_INPUT", + "WM_KEYDOWN", + "WM_KEYUP", + "WM_CHAR", + "WM_DEADCHAR", + "WM_SYSKEYDOWN", + "WM_SYSKEYUP", + "WM_SYSCHAR", + "WM_SYSDEADCHAR", + "WM_KEYLAST", + "UNKNOWN (265)", + "UNKNOWN (266)", + "UNKNOWN (267)", + "UNKNOWN (268)", + "UNKNOWN (269)", + "UNKNOWN (270)", + "UNKNOWN (271)", + "WM_INITDIALOG", + "WM_COMMAND", + "WM_SYSCOMMAND", + "WM_TIMER", + "WM_HSCROLL", + "WM_VSCROLL", + "WM_INITMENU", + "WM_INITMENUPOPUP", + "UNKNOWN (280)", + "WM_GESTURE", + "UNKNOWN (282)", + "UNKNOWN (283)", + "UNKNOWN (284)", + "UNKNOWN (285)", + "UNKNOWN (286)", + "WM_MENUSELECT", + "WM_MENUCHAR", + "WM_ENTERIDLE", + "WM_MENURBUTTONUP", + "WM_MENUDRAG", + "WM_MENUGETOBJECT", + "WM_UNINITMENUPOPUP", + "WM_MENUCOMMAND", + "UNKNOWN (295)", + "UNKNOWN (296)", + "UNKNOWN (297)", + "UNKNOWN (298)", + "UNKNOWN (299)", + "UNKNOWN (300)", + "UNKNOWN (301)", + "UNKNOWN (302)", + "UNKNOWN (303)", + "UNKNOWN (304)", + "UNKNOWN (305)", + "WM_CTLCOLORMSGBOX", + "WM_CTLCOLOREDIT", + "WM_CTLCOLORLISTBOX", + "WM_CTLCOLORBTN", + "WM_CTLCOLORDLG", + "WM_CTLCOLORSCROLLBAR", + "WM_CTLCOLORSTATIC", + "UNKNOWN (313)", + "UNKNOWN (314)", + "UNKNOWN (315)", + "UNKNOWN (316)", + "UNKNOWN (317)", + "UNKNOWN (318)", + "UNKNOWN (319)", + "UNKNOWN (320)", + "UNKNOWN (321)", + "UNKNOWN (322)", + "UNKNOWN (323)", + "UNKNOWN (324)", + "UNKNOWN (325)", + "UNKNOWN (326)", + "UNKNOWN (327)", + "UNKNOWN (328)", + "UNKNOWN (329)", + "UNKNOWN (330)", + "UNKNOWN (331)", + "UNKNOWN (332)", + "UNKNOWN (333)", + "UNKNOWN (334)", + "UNKNOWN (335)", + "UNKNOWN (336)", + "UNKNOWN (337)", + "UNKNOWN (338)", + "UNKNOWN (339)", + "UNKNOWN (340)", + "UNKNOWN (341)", + "UNKNOWN (342)", + "UNKNOWN (343)", + "UNKNOWN (344)", + "UNKNOWN (345)", + "UNKNOWN (346)", + "UNKNOWN (347)", + "UNKNOWN (348)", + "UNKNOWN (349)", + "UNKNOWN (350)", + "UNKNOWN (351)", + "UNKNOWN (352)", + "UNKNOWN (353)", + "UNKNOWN (354)", + "UNKNOWN (355)", + "UNKNOWN (356)", + "UNKNOWN (357)", + "UNKNOWN (358)", + "UNKNOWN (359)", + "UNKNOWN (360)", + "UNKNOWN (361)", + "UNKNOWN (362)", + "UNKNOWN (363)", + "UNKNOWN (364)", + "UNKNOWN (365)", + "UNKNOWN (366)", + "UNKNOWN (367)", + "UNKNOWN (368)", + "UNKNOWN (369)", + "UNKNOWN (370)", + "UNKNOWN (371)", + "UNKNOWN (372)", + "UNKNOWN (373)", + "UNKNOWN (374)", + "UNKNOWN (375)", + "UNKNOWN (376)", + "UNKNOWN (377)", + "UNKNOWN (378)", + "UNKNOWN (379)", + "UNKNOWN (380)", + "UNKNOWN (381)", + "UNKNOWN (382)", + "UNKNOWN (383)", + "UNKNOWN (384)", + "UNKNOWN (385)", + "UNKNOWN (386)", + "UNKNOWN (387)", + "UNKNOWN (388)", + "UNKNOWN (389)", + "UNKNOWN (390)", + "UNKNOWN (391)", + "UNKNOWN (392)", + "UNKNOWN (393)", + "UNKNOWN (394)", + "UNKNOWN (395)", + "UNKNOWN (396)", + "UNKNOWN (397)", + "UNKNOWN (398)", + "UNKNOWN (399)", + "UNKNOWN (400)", + "UNKNOWN (401)", + "UNKNOWN (402)", + "UNKNOWN (403)", + "UNKNOWN (404)", + "UNKNOWN (405)", + "UNKNOWN (406)", + "UNKNOWN (407)", + "UNKNOWN (408)", + "UNKNOWN (409)", + "UNKNOWN (410)", + "UNKNOWN (411)", + "UNKNOWN (412)", + "UNKNOWN (413)", + "UNKNOWN (414)", + "UNKNOWN (415)", + "UNKNOWN (416)", + "UNKNOWN (417)", + "UNKNOWN (418)", + "UNKNOWN (419)", + "UNKNOWN (420)", + "UNKNOWN (421)", + "UNKNOWN (422)", + "UNKNOWN (423)", + "UNKNOWN (424)", + "UNKNOWN (425)", + "UNKNOWN (426)", + "UNKNOWN (427)", + "UNKNOWN (428)", + "UNKNOWN (429)", + "UNKNOWN (430)", + "UNKNOWN (431)", + "UNKNOWN (432)", + "UNKNOWN (433)", + "UNKNOWN (434)", + "UNKNOWN (435)", + "UNKNOWN (436)", + "UNKNOWN (437)", + "UNKNOWN (438)", + "UNKNOWN (439)", + "UNKNOWN (440)", + "UNKNOWN (441)", + "UNKNOWN (442)", + "UNKNOWN (443)", + "UNKNOWN (444)", + "UNKNOWN (445)", + "UNKNOWN (446)", + "UNKNOWN (447)", + "UNKNOWN (448)", + "UNKNOWN (449)", + "UNKNOWN (450)", + "UNKNOWN (451)", + "UNKNOWN (452)", + "UNKNOWN (453)", + "UNKNOWN (454)", + "UNKNOWN (455)", + "UNKNOWN (456)", + "UNKNOWN (457)", + "UNKNOWN (458)", + "UNKNOWN (459)", + "UNKNOWN (460)", + "UNKNOWN (461)", + "UNKNOWN (462)", + "UNKNOWN (463)", + "UNKNOWN (464)", + "UNKNOWN (465)", + "UNKNOWN (466)", + "UNKNOWN (467)", + "UNKNOWN (468)", + "UNKNOWN (469)", + "UNKNOWN (470)", + "UNKNOWN (471)", + "UNKNOWN (472)", + "UNKNOWN (473)", + "UNKNOWN (474)", + "UNKNOWN (475)", + "UNKNOWN (476)", + "UNKNOWN (477)", + "UNKNOWN (478)", + "UNKNOWN (479)", + "UNKNOWN (480)", + "UNKNOWN (481)", + "UNKNOWN (482)", + "UNKNOWN (483)", + "UNKNOWN (484)", + "UNKNOWN (485)", + "UNKNOWN (486)", + "UNKNOWN (487)", + "UNKNOWN (488)", + "UNKNOWN (489)", + "UNKNOWN (490)", + "UNKNOWN (491)", + "UNKNOWN (492)", + "UNKNOWN (493)", + "UNKNOWN (494)", + "UNKNOWN (495)", + "UNKNOWN (496)", + "UNKNOWN (497)", + "UNKNOWN (498)", + "UNKNOWN (499)", + "UNKNOWN (500)", + "UNKNOWN (501)", + "UNKNOWN (502)", + "UNKNOWN (503)", + "UNKNOWN (504)", + "UNKNOWN (505)", + "UNKNOWN (506)", + "UNKNOWN (507)", + "UNKNOWN (508)", + "UNKNOWN (509)", + "UNKNOWN (510)", + "UNKNOWN (511)", + "WM_MOUSEMOVE", + "WM_LBUTTONDOWN", + "WM_LBUTTONUP", + "WM_LBUTTONDBLCLK", + "WM_RBUTTONDOWN", + "WM_RBUTTONUP", + "WM_RBUTTONDBLCLK", + "WM_MBUTTONDOWN", + "WM_MBUTTONUP", + "WM_MOUSELAST", + "WM_MOUSEWHEEL", + "WM_XBUTTONDOWN", + "WM_XBUTTONUP", + "UNKNOWN (525)", + "UNKNOWN (526)", + "UNKNOWN (527)", + "WM_PARENTNOTIFY", + "WM_ENTERMENULOOP", + "WM_EXITMENULOOP", + "WM_NEXTMENU", + "WM_SIZING", + "WM_CAPTURECHANGED", + "WM_MOVING", + "UNKNOWN (535)", + "WM_POWERBROADCAST", + "WM_DEVICECHANGE", + "UNKNOWN (538)", + "UNKNOWN (539)", + "UNKNOWN (540)", + "UNKNOWN (541)", + "UNKNOWN (542)", + "UNKNOWN (543)", + "WM_MDICREATE", + "WM_MDIDESTROY", + "WM_MDIACTIVATE", + "WM_MDIRESTORE", + "WM_MDINEXT", + "WM_MDIMAXIMIZE", + "WM_MDITILE", + "WM_MDICASCADE", + "WM_MDIICONARRANGE", + "WM_MDIGETACTIVE", + "UNKNOWN (554)", + "UNKNOWN (555)", + "UNKNOWN (556)", + "UNKNOWN (557)", + "UNKNOWN (558)", + "UNKNOWN (559)", + "WM_MDISETMENU", + "WM_ENTERSIZEMOVE", + "WM_EXITSIZEMOVE", + "WM_DROPFILES", + "WM_MDIREFRESHMENU", + "UNKNOWN (565)", + "UNKNOWN (566)", + "UNKNOWN (567)", + "WM_POINTERDEVICECHANGE", + "WM_POINTERDEVICEINRANGE", + "WM_POINTERDEVICEOUTOFRANGE", + "UNKNOWN (571)", + "UNKNOWN (572)", + "UNKNOWN (573)", + "UNKNOWN (574)", + "UNKNOWN (575)", + "WM_TOUCH", + "WM_NCPOINTERUPDATE", + "WM_NCPOINTERDOWN", + "WM_NCPOINTERUP", + "UNKNOWN (580)", + "WM_POINTERUPDATE", + "WM_POINTERDOWN", + "WM_POINTERUP", + "WM_POINTERENTER", + "WM_POINTERLEAVE", + "WM_POINTERACTIVATE", + "WM_POINTERCAPTURECHANGED", + "WM_TOUCHHITTESTING", + "WM_POINTERWHEEL", + "WM_POINTERHWHEEL", + "DM_POINTERHITTEST", + "UNKNOWN (592)", + "UNKNOWN (593)", + "UNKNOWN (594)", + "UNKNOWN (595)", + "UNKNOWN (596)", + "UNKNOWN (597)", + "UNKNOWN (598)", + "UNKNOWN (599)", + "UNKNOWN (600)", + "UNKNOWN (601)", + "UNKNOWN (602)", + "UNKNOWN (603)", + "UNKNOWN (604)", + "UNKNOWN (605)", + "UNKNOWN (606)", + "UNKNOWN (607)", + "UNKNOWN (608)", + "UNKNOWN (609)", + "UNKNOWN (610)", + "UNKNOWN (611)", + "UNKNOWN (612)", + "UNKNOWN (613)", + "UNKNOWN (614)", + "UNKNOWN (615)", + "UNKNOWN (616)", + "UNKNOWN (617)", + "UNKNOWN (618)", + "UNKNOWN (619)", + "UNKNOWN (620)", + "UNKNOWN (621)", + "UNKNOWN (622)", + "UNKNOWN (623)", + "UNKNOWN (624)", + "UNKNOWN (625)", + "UNKNOWN (626)", + "UNKNOWN (627)", + "UNKNOWN (628)", + "UNKNOWN (629)", + "UNKNOWN (630)", + "UNKNOWN (631)", + "UNKNOWN (632)", + "UNKNOWN (633)", + "UNKNOWN (634)", + "UNKNOWN (635)", + "UNKNOWN (636)", + "UNKNOWN (637)", + "UNKNOWN (638)", + "UNKNOWN (639)", + "UNKNOWN (640)", + "WM_IME_SETCONTEXT", + "WM_IME_NOTIFY", + "WM_IME_CONTROL", + "WM_IME_COMPOSITIONFULL", + "WM_IME_SELECT", + "WM_IME_CHAR", + "UNKNOWN (647)", + "WM_IME_REQUEST", + "UNKNOWN (649)", + "UNKNOWN (650)", + "UNKNOWN (651)", + "UNKNOWN (652)", + "UNKNOWN (653)", + "UNKNOWN (654)", + "UNKNOWN (655)", + "WM_IME_KEYDOWN", + "WM_IME_KEYUP", + "UNKNOWN (658)", + "UNKNOWN (659)", + "UNKNOWN (660)", + "UNKNOWN (661)", + "UNKNOWN (662)", + "UNKNOWN (663)", + "UNKNOWN (664)", + "UNKNOWN (665)", + "UNKNOWN (666)", + "UNKNOWN (667)", + "UNKNOWN (668)", + "UNKNOWN (669)", + "UNKNOWN (670)", + "UNKNOWN (671)", + "WM_NCMOUSEHOVER", + "WM_MOUSEHOVER", + "WM_NCMOUSELEAVE", + "WM_MOUSELEAVE", + "UNKNOWN (676)", + "UNKNOWN (677)", + "UNKNOWN (678)", + "UNKNOWN (679)", + "UNKNOWN (680)", + "UNKNOWN (681)", + "UNKNOWN (682)", + "UNKNOWN (683)", + "UNKNOWN (684)", + "UNKNOWN (685)", + "UNKNOWN (686)", + "UNKNOWN (687)", + "UNKNOWN (688)", + "WM_WTSSESSION_CHANGE", + "UNKNOWN (690)", + "UNKNOWN (691)", + "UNKNOWN (692)", + "UNKNOWN (693)", + "UNKNOWN (694)", + "UNKNOWN (695)", + "UNKNOWN (696)", + "UNKNOWN (697)", + "UNKNOWN (698)", + "UNKNOWN (699)", + "UNKNOWN (700)", + "UNKNOWN (701)", + "UNKNOWN (702)", + "UNKNOWN (703)", + "UNKNOWN (704)", + "UNKNOWN (705)", + "UNKNOWN (706)", + "UNKNOWN (707)", + "UNKNOWN (708)", + "UNKNOWN (709)", + "UNKNOWN (710)", + "UNKNOWN (711)", + "UNKNOWN (712)", + "UNKNOWN (713)", + "UNKNOWN (714)", + "UNKNOWN (715)", + "UNKNOWN (716)", + "UNKNOWN (717)", + "UNKNOWN (718)", + "UNKNOWN (719)", + "UNKNOWN (720)", + "UNKNOWN (721)", + "UNKNOWN (722)", + "UNKNOWN (723)", + "UNKNOWN (724)", + "UNKNOWN (725)", + "UNKNOWN (726)", + "UNKNOWN (727)", + "UNKNOWN (728)", + "UNKNOWN (729)", + "UNKNOWN (730)", + "UNKNOWN (731)", + "UNKNOWN (732)", + "UNKNOWN (733)", + "UNKNOWN (734)", + "UNKNOWN (735)", + "WM_DPICHANGED", + "UNKNOWN (737)", + "UNKNOWN (738)", + "UNKNOWN (739)", + "WM_GETDPISCALEDSIZE", + "UNKNOWN (741)", + "UNKNOWN (742)", + "UNKNOWN (743)", + "UNKNOWN (744)", + "UNKNOWN (745)", + "UNKNOWN (746)", + "UNKNOWN (747)", + "UNKNOWN (748)", + "UNKNOWN (749)", + "UNKNOWN (750)", + "UNKNOWN (751)", + "UNKNOWN (752)", + "UNKNOWN (753)", + "UNKNOWN (754)", + "UNKNOWN (755)", + "UNKNOWN (756)", + "UNKNOWN (757)", + "UNKNOWN (758)", + "UNKNOWN (759)", + "UNKNOWN (760)", + "UNKNOWN (761)", + "UNKNOWN (762)", + "UNKNOWN (763)", + "UNKNOWN (764)", + "UNKNOWN (765)", + "UNKNOWN (766)", + "UNKNOWN (767)", + "WM_CUT", + "WM_COPY", + "WM_PASTE", + "WM_CLEAR", + "WM_UNDO", + "WM_RENDERFORMAT", + "WM_RENDERALLFORMATS", + "WM_DESTROYCLIPBOARD", + "WM_DRAWCLIPBOARD", + "WM_PAINTCLIPBOARD", + "WM_VSCROLLCLIPBOARD", + "WM_SIZECLIPBOARD", + "WM_ASKCBFORMATNAME", + "WM_CHANGECBCHAIN", + "WM_HSCROLLCLIPBOARD", + "WM_QUERYNEWPALETTE", + "WM_PALETTEISCHANGING", + "WM_PALETTECHANGED", + "WM_HOTKEY", + "UNKNOWN (787)", + "UNKNOWN (788)", + "UNKNOWN (789)", + "UNKNOWN (790)", + "WM_PRINT", + "WM_PRINTCLIENT", + "WM_APPCOMMAND", + "WM_THEMECHANGED", + "UNKNOWN (795)", + "UNKNOWN (796)", + "WM_CLIPBOARDUPDATE", + "WM_DWMCOMPOSITIONCHANGED", + "WM_DWMNCRENDERINGCHANGED", + "WM_DWMCOLORIZATIONCOLORCHANGED", + "WM_DWMWINDOWMAXIMIZEDCHANGE", + "UNKNOWN (802)", + "WM_DWMSENDICONICTHUMBNAIL", + "UNKNOWN (804)", + "UNKNOWN (805)", + "WM_DWMSENDICONICLIVEPREVIEWBITMAP", + "UNKNOWN (807)", + "UNKNOWN (808)", + "UNKNOWN (809)", + "UNKNOWN (810)", + "UNKNOWN (811)", + "UNKNOWN (812)", + "UNKNOWN (813)", + "UNKNOWN (814)", + "UNKNOWN (815)", + "UNKNOWN (816)", + "UNKNOWN (817)", + "UNKNOWN (818)", + "UNKNOWN (819)", + "UNKNOWN (820)", + "UNKNOWN (821)", + "UNKNOWN (822)", + "UNKNOWN (823)", + "UNKNOWN (824)", + "UNKNOWN (825)", + "UNKNOWN (826)", + "UNKNOWN (827)", + "UNKNOWN (828)", + "UNKNOWN (829)", + "UNKNOWN (830)", + "WM_GETTITLEBARINFOEX", + "UNKNOWN (832)", + "UNKNOWN (833)", + "UNKNOWN (834)", + "UNKNOWN (835)", + "UNKNOWN (836)", + "UNKNOWN (837)", + "UNKNOWN (838)", + "UNKNOWN (839)", + "UNKNOWN (840)", + "UNKNOWN (841)", + "UNKNOWN (842)", + "UNKNOWN (843)", + "UNKNOWN (844)", + "UNKNOWN (845)", + "UNKNOWN (846)", + "UNKNOWN (847)", + "UNKNOWN (848)", + "UNKNOWN (849)", + "UNKNOWN (850)", + "UNKNOWN (851)", + "UNKNOWN (852)", + "UNKNOWN (853)", + "UNKNOWN (854)", + "UNKNOWN (855)", + "WM_HANDHELDFIRST", + "UNKNOWN (857)", + "UNKNOWN (858)", + "UNKNOWN (859)", + "UNKNOWN (860)", + "UNKNOWN (861)", + "UNKNOWN (862)", + "WM_HANDHELDLAST", + "WM_AFXFIRST", + "UNKNOWN (865)", + "UNKNOWN (866)", + "UNKNOWN (867)", + "UNKNOWN (868)", + "UNKNOWN (869)", + "UNKNOWN (870)", + "UNKNOWN (871)", + "UNKNOWN (872)", + "UNKNOWN (873)", + "UNKNOWN (874)", + "UNKNOWN (875)", + "UNKNOWN (876)", + "UNKNOWN (877)", + "UNKNOWN (878)", + "UNKNOWN (879)", + "UNKNOWN (880)", + "UNKNOWN (881)", + "UNKNOWN (882)", + "UNKNOWN (883)", + "UNKNOWN (884)", + "UNKNOWN (885)", + "UNKNOWN (886)", + "UNKNOWN (887)", + "UNKNOWN (888)", + "UNKNOWN (889)", + "UNKNOWN (890)", + "UNKNOWN (891)", + "UNKNOWN (892)", + "UNKNOWN (893)", + "UNKNOWN (894)", + "WM_AFXLAST", + "WM_PENWINFIRST", + "UNKNOWN (897)", + "UNKNOWN (898)", + "UNKNOWN (899)", + "UNKNOWN (900)", + "UNKNOWN (901)", + "UNKNOWN (902)", + "UNKNOWN (903)", + "UNKNOWN (904)", + "UNKNOWN (905)", + "UNKNOWN (906)", + "UNKNOWN (907)", + "UNKNOWN (908)", + "UNKNOWN (909)", + "UNKNOWN (910)", + "WM_PENWINLAST", + "UNKNOWN (912)", + "UNKNOWN (913)", + "UNKNOWN (914)", + "UNKNOWN (915)", + "UNKNOWN (916)", + "UNKNOWN (917)", + "UNKNOWN (918)", + "UNKNOWN (919)", + "UNKNOWN (920)", + "UNKNOWN (921)", + "UNKNOWN (922)", + "UNKNOWN (923)", + "UNKNOWN (924)", + "UNKNOWN (925)", + "UNKNOWN (926)", + "UNKNOWN (927)", + "UNKNOWN (928)", + "UNKNOWN (929)", + "UNKNOWN (930)", + "UNKNOWN (931)", + "UNKNOWN (932)", + "UNKNOWN (933)", + "UNKNOWN (934)", + "UNKNOWN (935)", + "UNKNOWN (936)", + "UNKNOWN (937)", + "UNKNOWN (938)", + "UNKNOWN (939)", + "UNKNOWN (940)", + "UNKNOWN (941)", + "UNKNOWN (942)", + "UNKNOWN (943)", + "UNKNOWN (944)", + "UNKNOWN (945)", + "UNKNOWN (946)", + "UNKNOWN (947)", + "UNKNOWN (948)", + "UNKNOWN (949)", + "UNKNOWN (950)", + "UNKNOWN (951)", + "UNKNOWN (952)", + "UNKNOWN (953)", + "UNKNOWN (954)", + "UNKNOWN (955)", + "UNKNOWN (956)", + "UNKNOWN (957)", + "UNKNOWN (958)", + "UNKNOWN (959)", + "UNKNOWN (960)", + "UNKNOWN (961)", + "UNKNOWN (962)", + "UNKNOWN (963)", + "UNKNOWN (964)", + "UNKNOWN (965)", + "UNKNOWN (966)", + "UNKNOWN (967)", + "UNKNOWN (968)", + "UNKNOWN (969)", + "UNKNOWN (970)", + "UNKNOWN (971)", + "UNKNOWN (972)", + "UNKNOWN (973)", + "UNKNOWN (974)", + "UNKNOWN (975)", + "UNKNOWN (976)", + "UNKNOWN (977)", + "UNKNOWN (978)", + "UNKNOWN (979)", + "UNKNOWN (980)", + "UNKNOWN (981)", + "UNKNOWN (982)", + "UNKNOWN (983)", + "UNKNOWN (984)", + "UNKNOWN (985)", + "UNKNOWN (986)", + "UNKNOWN (987)", + "UNKNOWN (988)", + "UNKNOWN (989)", + "UNKNOWN (990)", + "UNKNOWN (991)", + "UNKNOWN (992)", + "UNKNOWN (993)", + "UNKNOWN (994)", + "UNKNOWN (995)", + "UNKNOWN (996)", + "UNKNOWN (997)", + "UNKNOWN (998)", + "UNKNOWN (999)", + "UNKNOWN (1000)", + "UNKNOWN (1001)", + "UNKNOWN (1002)", + "UNKNOWN (1003)", + "UNKNOWN (1004)", + "UNKNOWN (1005)", + "UNKNOWN (1006)", + "UNKNOWN (1007)", + "UNKNOWN (1008)", + "UNKNOWN (1009)", + "UNKNOWN (1010)", + "UNKNOWN (1011)", + "UNKNOWN (1012)", + "UNKNOWN (1013)", + "UNKNOWN (1014)", + "UNKNOWN (1015)", + "UNKNOWN (1016)", + "UNKNOWN (1017)", + "UNKNOWN (1018)", + "UNKNOWN (1019)", + "UNKNOWN (1020)", + "UNKNOWN (1021)", + "UNKNOWN (1022)", + "UNKNOWN (1023)", + "WM_USER" +}; -- cgit v1.2.3