summaryrefslogtreecommitdiff
path: root/contrib/SDL-3.2.8/src/video/windows
diff options
context:
space:
mode:
author3gg <3gg@shellblade.net>2025-12-27 12:03:39 -0800
committer3gg <3gg@shellblade.net>2025-12-27 12:03:39 -0800
commit5a079a2d114f96d4847d1ee305d5b7c16eeec50e (patch)
tree8926ab44f168acf787d8e19608857b3af0f82758 /contrib/SDL-3.2.8/src/video/windows
Initial commit
Diffstat (limited to 'contrib/SDL-3.2.8/src/video/windows')
-rw-r--r--contrib/SDL-3.2.8/src/video/windows/SDL_msctf.h246
-rw-r--r--contrib/SDL-3.2.8/src/video/windows/SDL_surface_utils.c97
-rw-r--r--contrib/SDL-3.2.8/src/video/windows/SDL_surface_utils.h38
-rw-r--r--contrib/SDL-3.2.8/src/video/windows/SDL_windowsclipboard.c442
-rw-r--r--contrib/SDL-3.2.8/src/video/windows/SDL_windowsclipboard.h34
-rw-r--r--contrib/SDL-3.2.8/src/video/windows/SDL_windowsevents.c2734
-rw-r--r--contrib/SDL-3.2.8/src/video/windows/SDL_windowsevents.h39
-rw-r--r--contrib/SDL-3.2.8/src/video/windows/SDL_windowsframebuffer.c132
-rw-r--r--contrib/SDL-3.2.8/src/video/windows/SDL_windowsframebuffer.h25
-rw-r--r--contrib/SDL-3.2.8/src/video/windows/SDL_windowsgameinput.c612
-rw-r--r--contrib/SDL-3.2.8/src/video/windows/SDL_windowsgameinput.h29
-rw-r--r--contrib/SDL-3.2.8/src/video/windows/SDL_windowskeyboard.c1145
-rw-r--r--contrib/SDL-3.2.8/src/video/windows/SDL_windowskeyboard.h40
-rw-r--r--contrib/SDL-3.2.8/src/video/windows/SDL_windowsmessagebox.c1086
-rw-r--r--contrib/SDL-3.2.8/src/video/windows/SDL_windowsmessagebox.h27
-rw-r--r--contrib/SDL-3.2.8/src/video/windows/SDL_windowsmodes.c927
-rw-r--r--contrib/SDL-3.2.8/src/video/windows/SDL_windowsmodes.h55
-rw-r--r--contrib/SDL-3.2.8/src/video/windows/SDL_windowsmouse.c745
-rw-r--r--contrib/SDL-3.2.8/src/video/windows/SDL_windowsmouse.h34
-rw-r--r--contrib/SDL-3.2.8/src/video/windows/SDL_windowsopengl.c906
-rw-r--r--contrib/SDL-3.2.8/src/video/windows/SDL_windowsopengl.h178
-rw-r--r--contrib/SDL-3.2.8/src/video/windows/SDL_windowsopengles.c142
-rw-r--r--contrib/SDL-3.2.8/src/video/windows/SDL_windowsopengles.h48
-rw-r--r--contrib/SDL-3.2.8/src/video/windows/SDL_windowsrawinput.c262
-rw-r--r--contrib/SDL-3.2.8/src/video/windows/SDL_windowsrawinput.h30
-rw-r--r--contrib/SDL-3.2.8/src/video/windows/SDL_windowsshape.c125
-rw-r--r--contrib/SDL-3.2.8/src/video/windows/SDL_windowsshape.h28
-rw-r--r--contrib/SDL-3.2.8/src/video/windows/SDL_windowsvideo.c773
-rw-r--r--contrib/SDL-3.2.8/src/video/windows/SDL_windowsvideo.h507
-rw-r--r--contrib/SDL-3.2.8/src/video/windows/SDL_windowsvulkan.c195
-rw-r--r--contrib/SDL-3.2.8/src/video/windows/SDL_windowsvulkan.h55
-rw-r--r--contrib/SDL-3.2.8/src/video/windows/SDL_windowswindow.c2435
-rw-r--r--contrib/SDL-3.2.8/src/video/windows/SDL_windowswindow.h152
-rw-r--r--contrib/SDL-3.2.8/src/video/windows/wmmsg.h1050
34 files changed, 15373 insertions, 0 deletions
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 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21
22#ifndef SDL_msctf_h_
23#define SDL_msctf_h_
24
25#include <unknwn.h>
26
27#define TF_INVALID_COOKIE (0xffffffff)
28#define TF_IPSINK_FLAG_ACTIVE 0x0001
29#define TF_TMAE_UIELEMENTENABLEDONLY 0x00000004
30
31/* *INDENT-OFF* */ // clang-format off
32
33typedef struct ITfThreadMgr ITfThreadMgr;
34typedef struct ITfDocumentMgr ITfDocumentMgr;
35typedef struct ITfClientId ITfClientId;
36
37typedef struct IEnumTfDocumentMgrs IEnumTfDocumentMgrs;
38typedef struct IEnumTfFunctionProviders IEnumTfFunctionProviders;
39typedef struct ITfFunctionProvider ITfFunctionProvider;
40typedef struct ITfCompartmentMgr ITfCompartmentMgr;
41typedef struct ITfContext ITfContext;
42typedef struct IEnumTfContexts IEnumTfContexts;
43typedef struct ITfUIElementSink ITfUIElementSink;
44typedef struct ITfUIElement ITfUIElement;
45typedef struct ITfUIElementMgr ITfUIElementMgr;
46typedef struct IEnumTfUIElements IEnumTfUIElements;
47typedef struct ITfThreadMgrEx ITfThreadMgrEx;
48typedef struct ITfCandidateListUIElement ITfCandidateListUIElement;
49typedef struct ITfReadingInformationUIElement ITfReadingInformationUIElement;
50typedef struct ITfInputProcessorProfileActivationSink ITfInputProcessorProfileActivationSink;
51typedef struct ITfSource ITfSource;
52
53typedef DWORD TfClientId;
54typedef DWORD TfEditCookie;
55
56typedef struct ITfThreadMgrVtbl
57{
58 HRESULT (STDMETHODCALLTYPE *QueryInterface)(ITfThreadMgr *, REFIID, void **);
59 ULONG (STDMETHODCALLTYPE *AddRef)(ITfThreadMgr *);
60 ULONG (STDMETHODCALLTYPE *Release)(ITfThreadMgr *);
61 HRESULT (STDMETHODCALLTYPE *Activate)(ITfThreadMgr *, TfClientId *);
62 HRESULT (STDMETHODCALLTYPE *Deactivate)(ITfThreadMgr *);
63 HRESULT (STDMETHODCALLTYPE *CreateDocumentMgr)(ITfThreadMgr *);
64 HRESULT (STDMETHODCALLTYPE *EnumDocumentMgrs)(ITfThreadMgr *, IEnumTfDocumentMgrs **);
65 HRESULT (STDMETHODCALLTYPE *GetFocus)(ITfThreadMgr *, ITfDocumentMgr **);
66 HRESULT (STDMETHODCALLTYPE *SetFocus)(ITfThreadMgr *, ITfDocumentMgr *);
67 HRESULT (STDMETHODCALLTYPE *AssociateFocus)(ITfThreadMgr *, HWND, ITfDocumentMgr *, ITfDocumentMgr **);
68 HRESULT (STDMETHODCALLTYPE *IsThreadFocus)(ITfThreadMgr *, BOOL *);
69 HRESULT (STDMETHODCALLTYPE *GetFunctionProvider)(ITfThreadMgr *, REFCLSID, ITfFunctionProvider **);
70 HRESULT (STDMETHODCALLTYPE *EnumFunctionProviders)(ITfThreadMgr *, IEnumTfFunctionProviders **);
71 HRESULT (STDMETHODCALLTYPE *GetGlobalCompartment)(ITfThreadMgr *, ITfCompartmentMgr **);
72} ITfThreadMgrVtbl;
73
74struct ITfThreadMgr
75{
76 const struct ITfThreadMgrVtbl *lpVtbl;
77};
78
79typedef struct ITfThreadMgrExVtbl
80{
81 HRESULT (STDMETHODCALLTYPE *QueryInterface)(ITfThreadMgrEx *, REFIID, void **);
82 ULONG (STDMETHODCALLTYPE *AddRef)(ITfThreadMgrEx *);
83 ULONG (STDMETHODCALLTYPE *Release)(ITfThreadMgrEx *);
84 HRESULT (STDMETHODCALLTYPE *Activate)(ITfThreadMgrEx *, TfClientId *);
85 HRESULT (STDMETHODCALLTYPE *Deactivate)(ITfThreadMgrEx *);
86 HRESULT (STDMETHODCALLTYPE *CreateDocumentMgr)(ITfThreadMgrEx *, ITfDocumentMgr **);
87 HRESULT (STDMETHODCALLTYPE *EnumDocumentMgrs)(ITfThreadMgrEx *, IEnumTfDocumentMgrs **);
88 HRESULT (STDMETHODCALLTYPE *GetFocus)(ITfThreadMgrEx *, ITfDocumentMgr **);
89 HRESULT (STDMETHODCALLTYPE *SetFocus)(ITfThreadMgrEx *, ITfDocumentMgr *);
90 HRESULT (STDMETHODCALLTYPE *AssociateFocus)(ITfThreadMgrEx *, ITfDocumentMgr *, ITfDocumentMgr **);
91 HRESULT (STDMETHODCALLTYPE *IsThreadFocus)(ITfThreadMgrEx *, BOOL *);
92 HRESULT (STDMETHODCALLTYPE *GetFunctionProvider)(ITfThreadMgrEx *, REFCLSID, ITfFunctionProvider **);
93 HRESULT (STDMETHODCALLTYPE *EnumFunctionProviders)(ITfThreadMgrEx *, IEnumTfFunctionProviders **);
94 HRESULT (STDMETHODCALLTYPE *GetGlobalCompartment)(ITfThreadMgrEx *, ITfCompartmentMgr **);
95 HRESULT (STDMETHODCALLTYPE *ActivateEx)(ITfThreadMgrEx *, TfClientId *, DWORD);
96 HRESULT (STDMETHODCALLTYPE *GetActiveFlags)(ITfThreadMgrEx *, DWORD *);
97} ITfThreadMgrExVtbl;
98
99struct ITfThreadMgrEx
100{
101 const struct ITfThreadMgrExVtbl *lpVtbl;
102};
103
104typedef struct ITfDocumentMgrVtbl
105{
106 HRESULT (STDMETHODCALLTYPE *QueryInterface)(ITfDocumentMgr *, REFIID, void **);
107 ULONG (STDMETHODCALLTYPE *AddRef)(ITfDocumentMgr *);
108 ULONG (STDMETHODCALLTYPE *Release)(ITfDocumentMgr *);
109 HRESULT (STDMETHODCALLTYPE *CreateContext)(ITfDocumentMgr *, TfClientId, DWORD, IUnknown *, ITfContext **, TfEditCookie *);
110 HRESULT (STDMETHODCALLTYPE *Push)(ITfDocumentMgr *, ITfContext *);
111 HRESULT (STDMETHODCALLTYPE *Pop)(ITfDocumentMgr *);
112 HRESULT (STDMETHODCALLTYPE *GetTop)(ITfDocumentMgr *, ITfContext **);
113 HRESULT (STDMETHODCALLTYPE *GetBase)(ITfDocumentMgr *, ITfContext **);
114 HRESULT (STDMETHODCALLTYPE *EnumContexts)(ITfDocumentMgr *, IEnumTfContexts **);
115} ITfDocumentMgrVtbl;
116
117struct ITfDocumentMgr
118{
119 const struct ITfDocumentMgrVtbl *lpVtbl;
120};
121
122typedef struct ITfUIElementSinkVtbl
123{
124 HRESULT (STDMETHODCALLTYPE *QueryInterface)(ITfUIElementSink *, REFIID, void **);
125 ULONG (STDMETHODCALLTYPE *AddRef)(ITfUIElementSink *);
126 ULONG (STDMETHODCALLTYPE *Release)(ITfUIElementSink *);
127 HRESULT (STDMETHODCALLTYPE *BeginUIElement)(ITfUIElementSink *, DWORD, BOOL *);
128 HRESULT (STDMETHODCALLTYPE *UpdateUIElement)(ITfUIElementSink *, DWORD);
129 HRESULT (STDMETHODCALLTYPE *EndUIElement)(ITfUIElementSink *, DWORD);
130} ITfUIElementSinkVtbl;
131
132struct ITfUIElementSink
133{
134 const struct ITfUIElementSinkVtbl *lpVtbl;
135};
136
137typedef struct ITfUIElementMgrVtbl
138{
139 HRESULT (STDMETHODCALLTYPE *QueryInterface)(ITfUIElementMgr *, REFIID, void **);
140 ULONG (STDMETHODCALLTYPE *AddRef)(ITfUIElementMgr *);
141 ULONG (STDMETHODCALLTYPE *Release)(ITfUIElementMgr *);
142 HRESULT (STDMETHODCALLTYPE *BeginUIElement)(ITfUIElementMgr *, ITfUIElement *, BOOL *, DWORD *);
143 HRESULT (STDMETHODCALLTYPE *UpdateUIElement)(ITfUIElementMgr *, DWORD);
144 HRESULT (STDMETHODCALLTYPE *EndUIElement)(ITfUIElementMgr *, DWORD);
145 HRESULT (STDMETHODCALLTYPE *GetUIElement)(ITfUIElementMgr *, DWORD, ITfUIElement **);
146 HRESULT (STDMETHODCALLTYPE *EnumUIElements)(ITfUIElementMgr *, IEnumTfUIElements **);
147} ITfUIElementMgrVtbl;
148
149struct ITfUIElementMgr
150{
151 const struct ITfUIElementMgrVtbl *lpVtbl;
152};
153
154typedef struct ITfCandidateListUIElementVtbl
155{
156 HRESULT (STDMETHODCALLTYPE *QueryInterface)(ITfCandidateListUIElement *, REFIID, void **);
157 ULONG (STDMETHODCALLTYPE *AddRef)(ITfCandidateListUIElement *);
158 ULONG (STDMETHODCALLTYPE *Release)(ITfCandidateListUIElement *);
159 HRESULT (STDMETHODCALLTYPE *GetDescription)(ITfCandidateListUIElement *, BSTR *);
160 HRESULT (STDMETHODCALLTYPE *GetGUID)(ITfCandidateListUIElement *, GUID *);
161 HRESULT (STDMETHODCALLTYPE *Show)(ITfCandidateListUIElement *, BOOL);
162 HRESULT (STDMETHODCALLTYPE *IsShown)(ITfCandidateListUIElement *, BOOL *);
163 HRESULT (STDMETHODCALLTYPE *GetUpdatedFlags)(ITfCandidateListUIElement *, DWORD *);
164 HRESULT (STDMETHODCALLTYPE *GetDocumentMgr)(ITfCandidateListUIElement *, ITfDocumentMgr **);
165 HRESULT (STDMETHODCALLTYPE *GetCount)(ITfCandidateListUIElement *, UINT *);
166 HRESULT (STDMETHODCALLTYPE *GetSelection)(ITfCandidateListUIElement *, UINT *);
167 HRESULT (STDMETHODCALLTYPE *GetString)(ITfCandidateListUIElement *, UINT, BSTR *);
168 HRESULT (STDMETHODCALLTYPE *GetPageIndex)(ITfCandidateListUIElement *, UINT *, UINT, UINT *);
169 HRESULT (STDMETHODCALLTYPE *SetPageIndex)(ITfCandidateListUIElement *, UINT *, UINT);
170 HRESULT (STDMETHODCALLTYPE *GetCurrentPage)(ITfCandidateListUIElement *, UINT *);
171} ITfCandidateListUIElementVtbl;
172
173struct ITfCandidateListUIElement
174{
175 const struct ITfCandidateListUIElementVtbl *lpVtbl;
176};
177
178typedef struct ITfReadingInformationUIElementVtbl
179{
180 HRESULT (STDMETHODCALLTYPE *QueryInterface)(ITfReadingInformationUIElement *, REFIID, void **);
181 ULONG (STDMETHODCALLTYPE *AddRef)(ITfReadingInformationUIElement *);
182 ULONG (STDMETHODCALLTYPE *Release)(ITfReadingInformationUIElement *);
183 HRESULT (STDMETHODCALLTYPE *GetDescription)(ITfReadingInformationUIElement *, BSTR *);
184 HRESULT (STDMETHODCALLTYPE *GetGUID)(ITfReadingInformationUIElement *, GUID *);
185 HRESULT (STDMETHODCALLTYPE *Show)(ITfReadingInformationUIElement *, BOOL);
186 HRESULT (STDMETHODCALLTYPE *IsShown)(ITfReadingInformationUIElement *, BOOL *);
187 HRESULT (STDMETHODCALLTYPE *GetUpdatedFlags)(ITfReadingInformationUIElement *, DWORD *);
188 HRESULT (STDMETHODCALLTYPE *GetContext)(ITfReadingInformationUIElement *, ITfContext **);
189 HRESULT (STDMETHODCALLTYPE *GetString)(ITfReadingInformationUIElement *, BSTR *);
190 HRESULT (STDMETHODCALLTYPE *GetMaxReadingStringLength)(ITfReadingInformationUIElement *, UINT *);
191 HRESULT (STDMETHODCALLTYPE *GetErrorIndex)(ITfReadingInformationUIElement *, UINT *);
192 HRESULT (STDMETHODCALLTYPE *IsVerticalOrderPreferred)(ITfReadingInformationUIElement *, BOOL *);
193} ITfReadingInformationUIElementVtbl;
194
195struct ITfReadingInformationUIElement
196{
197 const struct ITfReadingInformationUIElementVtbl *lpVtbl;
198};
199
200typedef struct ITfUIElementVtbl
201{
202 HRESULT (STDMETHODCALLTYPE *QueryInterface)(ITfUIElement *, REFIID, void **);
203 ULONG (STDMETHODCALLTYPE *AddRef)(ITfUIElement *);
204 ULONG (STDMETHODCALLTYPE *Release)(ITfUIElement *);
205 HRESULT (STDMETHODCALLTYPE *GetDescription)(ITfUIElement *, BSTR *);
206 HRESULT (STDMETHODCALLTYPE *GetGUID)(ITfUIElement *, GUID *);
207 HRESULT (STDMETHODCALLTYPE *Show)(ITfUIElement *, BOOL);
208 HRESULT (STDMETHODCALLTYPE *IsShown)(ITfUIElement *, BOOL *);
209} ITfUIElementVtbl;
210
211struct ITfUIElement
212{
213 const struct ITfUIElementVtbl *lpVtbl;
214};
215
216typedef struct ITfInputProcessorProfileActivationSinkVtbl
217{
218 HRESULT (STDMETHODCALLTYPE *QueryInterface)(ITfInputProcessorProfileActivationSink *, REFIID, void **);
219 ULONG (STDMETHODCALLTYPE *AddRef)(ITfInputProcessorProfileActivationSink *);
220 ULONG (STDMETHODCALLTYPE *Release)(ITfInputProcessorProfileActivationSink *);
221 HRESULT (STDMETHODCALLTYPE *OnActivated)(ITfInputProcessorProfileActivationSink *, DWORD, LANGID, REFCLSID, REFGUID, REFGUID, HKL, DWORD);
222
223} ITfInputProcessorProfileActivationSinkVtbl;
224
225struct ITfInputProcessorProfileActivationSink
226{
227 const struct ITfInputProcessorProfileActivationSinkVtbl *lpVtbl;
228};
229
230typedef struct ITfSourceVtbl
231{
232 HRESULT (STDMETHODCALLTYPE *QueryInterface)(ITfSource *, REFIID, void **);
233 ULONG (STDMETHODCALLTYPE *AddRef)(ITfSource *);
234 ULONG (STDMETHODCALLTYPE *Release)(ITfSource *);
235 HRESULT (STDMETHODCALLTYPE *AdviseSink)(ITfSource *, REFIID, IUnknown *, DWORD *);
236 HRESULT (STDMETHODCALLTYPE *UnadviseSink)(ITfSource *, DWORD);
237} ITfSourceVtbl;
238
239struct ITfSource
240{
241 const struct ITfSourceVtbl *lpVtbl;
242};
243
244/* *INDENT-ON* */ // clang-format on
245
246#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 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#include "SDL_surface_utils.h"
24
25#include "../SDL_surface_c.h"
26
27#if !(defined(SDL_PLATFORM_XBOXONE) || defined(SDL_PLATFORM_XBOXSERIES))
28HICON CreateIconFromSurface(SDL_Surface *surface)
29{
30 SDL_Surface *s = SDL_ConvertSurface(surface, SDL_PIXELFORMAT_ARGB8888);
31 if (!s) {
32 return NULL;
33 }
34
35 /* The dimensions will be needed after s is freed */
36 const int width = s->w;
37 const int height = s->h;
38
39 BITMAPINFO bmpInfo;
40 SDL_zero(bmpInfo);
41 bmpInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
42 bmpInfo.bmiHeader.biWidth = width;
43 bmpInfo.bmiHeader.biHeight = -height; /* Top-down bitmap */
44 bmpInfo.bmiHeader.biPlanes = 1;
45 bmpInfo.bmiHeader.biBitCount = 32;
46 bmpInfo.bmiHeader.biCompression = BI_RGB;
47
48 HDC hdc = GetDC(NULL);
49 void* pBits = NULL;
50 HBITMAP hBitmap = CreateDIBSection(hdc, &bmpInfo, DIB_RGB_COLORS, &pBits, NULL, 0);
51 if (!hBitmap) {
52 ReleaseDC(NULL, hdc);
53 SDL_DestroySurface(s);
54 return NULL;
55 }
56
57 SDL_memcpy(pBits, s->pixels, width * height * 4);
58
59 SDL_DestroySurface(s);
60
61 HBITMAP hMask = CreateBitmap(width, height, 1, 1, NULL);
62 if (!hMask) {
63 DeleteObject(hBitmap);
64 ReleaseDC(NULL, hdc);
65 return NULL;
66 }
67
68 HDC hdcMem = CreateCompatibleDC(hdc);
69 HGDIOBJ oldBitmap = SelectObject(hdcMem, hMask);
70
71 for (int y = 0; y < height; y++) {
72 for (int x = 0; x < width; x++) {
73 BYTE* pixel = (BYTE*)pBits + (y * width + x) * 4;
74 BYTE alpha = pixel[3];
75 COLORREF maskColor = (alpha == 0) ? RGB(0, 0, 0) : RGB(255, 255, 255);
76 SetPixel(hdcMem, x, y, maskColor);
77 }
78 }
79
80 ICONINFO iconInfo;
81 iconInfo.fIcon = TRUE;
82 iconInfo.xHotspot = 0;
83 iconInfo.yHotspot = 0;
84 iconInfo.hbmMask = hMask;
85 iconInfo.hbmColor = hBitmap;
86
87 HICON hIcon = CreateIconIndirect(&iconInfo);
88
89 SelectObject(hdcMem, oldBitmap);
90 DeleteDC(hdcMem);
91 DeleteObject(hBitmap);
92 DeleteObject(hMask);
93 ReleaseDC(NULL, hdc);
94
95 return hIcon;
96}
97#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 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#ifndef SDL_surface_utils_h_
24#define SDL_surface_utils_h_
25
26#include "../../core/windows/SDL_windows.h"
27
28#ifdef __cplusplus
29extern "C" {
30#endif
31
32extern HICON CreateIconFromSurface(SDL_Surface *surface);
33
34#ifdef __cplusplus
35}
36#endif
37
38#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 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#if defined(SDL_VIDEO_DRIVER_WINDOWS) && !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
24
25#include "SDL_windowsvideo.h"
26#include "SDL_windowswindow.h"
27#include "../SDL_clipboard_c.h"
28#include "../../events/SDL_events_c.h"
29#include "../../events/SDL_clipboardevents_c.h"
30
31#ifdef UNICODE
32#define TEXT_FORMAT CF_UNICODETEXT
33#else
34#define TEXT_FORMAT CF_TEXT
35#endif
36
37#define IMAGE_FORMAT CF_DIB
38#define IMAGE_MIME_TYPE "image/bmp"
39#define BFT_BITMAP 0x4d42 // 'BM'
40
41// Assume we can directly read and write BMP fields without byte swapping
42SDL_COMPILE_TIME_ASSERT(verify_byte_order, SDL_BYTEORDER == SDL_LIL_ENDIAN);
43
44static BOOL WIN_OpenClipboard(SDL_VideoDevice *_this)
45{
46 // Retry to open the clipboard in case another application has it open
47 const int MAX_ATTEMPTS = 3;
48 int attempt;
49 HWND hwnd = NULL;
50
51 if (_this->windows) {
52 hwnd = _this->windows->internal->hwnd;
53 }
54 for (attempt = 0; attempt < MAX_ATTEMPTS; ++attempt) {
55 if (OpenClipboard(hwnd)) {
56 return TRUE;
57 }
58 SDL_Delay(10);
59 }
60 return FALSE;
61}
62
63static void WIN_CloseClipboard(void)
64{
65 CloseClipboard();
66}
67
68static HANDLE WIN_ConvertBMPtoDIB(const void *bmp, size_t bmp_size)
69{
70 HANDLE hMem = NULL;
71
72 if (bmp && bmp_size > sizeof(BITMAPFILEHEADER) && ((BITMAPFILEHEADER *)bmp)->bfType == BFT_BITMAP) {
73 BITMAPFILEHEADER *pbfh = (BITMAPFILEHEADER *)bmp;
74 BITMAPINFOHEADER *pbih = (BITMAPINFOHEADER *)((Uint8 *)bmp + sizeof(BITMAPFILEHEADER));
75 size_t bih_size = pbih->biSize + pbih->biClrUsed * sizeof(RGBQUAD);
76 size_t pixels_size = pbih->biSizeImage;
77
78 if (pbfh->bfOffBits >= (sizeof(BITMAPFILEHEADER) + bih_size) &&
79 (pbfh->bfOffBits + pixels_size) <= bmp_size) {
80 const Uint8 *pixels = (const Uint8 *)bmp + pbfh->bfOffBits;
81 size_t dib_size = bih_size + pixels_size;
82 hMem = GlobalAlloc(GMEM_MOVEABLE, dib_size);
83 if (hMem) {
84 LPVOID dst = GlobalLock(hMem);
85 if (dst) {
86 SDL_memcpy(dst, pbih, bih_size);
87 SDL_memcpy((Uint8 *)dst + bih_size, pixels, pixels_size);
88 GlobalUnlock(hMem);
89 } else {
90 WIN_SetError("GlobalLock()");
91 GlobalFree(hMem);
92 hMem = NULL;
93 }
94 } else {
95 SDL_OutOfMemory();
96 }
97 } else {
98 SDL_SetError("Invalid BMP data");
99 }
100 } else {
101 SDL_SetError("Invalid BMP data");
102 }
103 return hMem;
104}
105
106static void *WIN_ConvertDIBtoBMP(HANDLE hMem, size_t *size)
107{
108 void *bmp = NULL;
109 size_t mem_size = GlobalSize(hMem);
110
111 if (mem_size > sizeof(BITMAPINFOHEADER)) {
112 LPVOID dib = GlobalLock(hMem);
113 if (dib) {
114 BITMAPINFOHEADER *pbih = (BITMAPINFOHEADER *)dib;
115
116 // https://learn.microsoft.com/en-us/windows/win32/api/wingdi/ns-wingdi-bitmapinfoheader#color-tables
117 size_t color_table_size;
118 switch (pbih->biCompression) {
119 case BI_RGB:
120 if (pbih->biBitCount <= 8) {
121 color_table_size = sizeof(RGBQUAD) * (pbih->biClrUsed == 0 ? 1 << pbih->biBitCount : pbih->biClrUsed);
122 } else {
123 color_table_size = 0;
124 }
125 break;
126 case BI_BITFIELDS:
127 color_table_size = 3 * sizeof(DWORD);
128 break;
129 case 6 /* BI_ALPHABITFIELDS */:
130 // https://learn.microsoft.com/en-us/previous-versions/windows/embedded/aa452885(v=msdn.10)
131 color_table_size = 4 * sizeof(DWORD);
132 break;
133 default: // FOURCC
134 color_table_size = sizeof(RGBQUAD) * pbih->biClrUsed;
135 }
136
137 size_t bih_size = pbih->biSize + color_table_size;
138 size_t dib_size = bih_size + pbih->biSizeImage;
139 if (dib_size <= mem_size) {
140 size_t bmp_size = sizeof(BITMAPFILEHEADER) + mem_size;
141 bmp = SDL_malloc(bmp_size);
142 if (bmp) {
143 BITMAPFILEHEADER *pbfh = (BITMAPFILEHEADER *)bmp;
144 pbfh->bfType = BFT_BITMAP;
145 pbfh->bfSize = (DWORD)bmp_size;
146 pbfh->bfReserved1 = 0;
147 pbfh->bfReserved2 = 0;
148 pbfh->bfOffBits = (DWORD)(sizeof(BITMAPFILEHEADER) + pbih->biSize + color_table_size);
149 SDL_memcpy((Uint8 *)bmp + sizeof(BITMAPFILEHEADER), dib, dib_size);
150 *size = bmp_size;
151 }
152 } else {
153 SDL_SetError("Invalid BMP data");
154 }
155 GlobalUnlock(hMem);
156 } else {
157 WIN_SetError("GlobalLock()");
158 }
159 } else {
160 SDL_SetError("Invalid BMP data");
161 }
162 return bmp;
163}
164
165static bool WIN_SetClipboardImage(SDL_VideoDevice *_this)
166{
167 HANDLE hMem;
168 size_t clipboard_data_size;
169 const void *clipboard_data;
170 bool result = true;
171
172 clipboard_data = _this->clipboard_callback(_this->clipboard_userdata, IMAGE_MIME_TYPE, &clipboard_data_size);
173 hMem = WIN_ConvertBMPtoDIB(clipboard_data, clipboard_data_size);
174 if (hMem) {
175 // Save the image to the clipboard
176 if (!SetClipboardData(IMAGE_FORMAT, hMem)) {
177 result = WIN_SetError("Couldn't set clipboard data");
178 }
179 } else {
180 // WIN_ConvertBMPtoDIB() set the error
181 result = false;
182 }
183 return result;
184}
185
186static bool WIN_SetClipboardText(SDL_VideoDevice *_this, const char *mime_type)
187{
188 HANDLE hMem;
189 size_t clipboard_data_size;
190 const void *clipboard_data;
191 bool result = true;
192
193 clipboard_data = _this->clipboard_callback(_this->clipboard_userdata, mime_type, &clipboard_data_size);
194 if (clipboard_data && clipboard_data_size > 0) {
195 SIZE_T i, size;
196 LPTSTR tstr = (WCHAR *)SDL_iconv_string("UTF-16LE", "UTF-8", (const char *)clipboard_data, clipboard_data_size);
197 if (!tstr) {
198 return SDL_SetError("Couldn't convert text from UTF-8");
199 }
200
201 // Find out the size of the data
202 for (size = 0, i = 0; tstr[i]; ++i, ++size) {
203 if (tstr[i] == '\n' && (i == 0 || tstr[i - 1] != '\r')) {
204 // We're going to insert a carriage return
205 ++size;
206 }
207 }
208 size = (size + 1) * sizeof(*tstr);
209
210 // Save the data to the clipboard
211 hMem = GlobalAlloc(GMEM_MOVEABLE, size);
212 if (hMem) {
213 LPTSTR dst = (LPTSTR)GlobalLock(hMem);
214 if (dst) {
215 // Copy the text over, adding carriage returns as necessary
216 for (i = 0; tstr[i]; ++i) {
217 if (tstr[i] == '\n' && (i == 0 || tstr[i - 1] != '\r')) {
218 *dst++ = '\r';
219 }
220 *dst++ = tstr[i];
221 }
222 *dst = 0;
223 GlobalUnlock(hMem);
224 }
225
226 if (!SetClipboardData(TEXT_FORMAT, hMem)) {
227 result = WIN_SetError("Couldn't set clipboard data");
228 }
229 } else {
230 result = SDL_OutOfMemory();
231 }
232 SDL_free(tstr);
233 }
234 return result;
235}
236
237bool WIN_SetClipboardData(SDL_VideoDevice *_this)
238{
239 SDL_VideoData *data = _this->internal;
240 size_t i;
241 bool result = true;
242
243 /* I investigated delayed clipboard rendering, and at least with text and image
244 * formats you have to use an output window, not SDL_HelperWindow, and the system
245 * requests them being rendered immediately, so there isn't any benefit.
246 */
247
248 if (WIN_OpenClipboard(_this)) {
249 EmptyClipboard();
250
251 // Set the clipboard text
252 for (i = 0; i < _this->num_clipboard_mime_types; ++i) {
253 const char *mime_type = _this->clipboard_mime_types[i];
254
255 if (SDL_IsTextMimeType(mime_type)) {
256 if (!WIN_SetClipboardText(_this, mime_type)) {
257 result = false;
258 }
259 // Only set the first clipboard text
260 break;
261 }
262 }
263
264 // Set the clipboard image
265 for (i = 0; i < _this->num_clipboard_mime_types; ++i) {
266 const char *mime_type = _this->clipboard_mime_types[i];
267
268 if (SDL_strcmp(mime_type, IMAGE_MIME_TYPE) == 0) {
269 if (!WIN_SetClipboardImage(_this)) {
270 result = false;
271 }
272 break;
273 }
274 }
275
276 data->clipboard_count = GetClipboardSequenceNumber();
277 WIN_CloseClipboard();
278 } else {
279 result = WIN_SetError("Couldn't open clipboard");
280 }
281 return result;
282}
283
284void *WIN_GetClipboardData(SDL_VideoDevice *_this, const char *mime_type, size_t *size)
285{
286 void *data = NULL;
287
288 if (SDL_IsTextMimeType(mime_type)) {
289 char *text = NULL;
290
291 if (IsClipboardFormatAvailable(TEXT_FORMAT)) {
292 if (WIN_OpenClipboard(_this)) {
293 HANDLE hMem;
294 LPTSTR tstr;
295
296 hMem = GetClipboardData(TEXT_FORMAT);
297 if (hMem) {
298 tstr = (LPTSTR)GlobalLock(hMem);
299 if (tstr) {
300 text = WIN_StringToUTF8(tstr);
301 GlobalUnlock(hMem);
302 } else {
303 WIN_SetError("Couldn't lock clipboard data");
304 }
305 } else {
306 WIN_SetError("Couldn't get clipboard data");
307 }
308 WIN_CloseClipboard();
309 }
310 }
311 if (!text) {
312 text = SDL_strdup("");
313 }
314 data = text;
315 *size = SDL_strlen(text);
316
317 } else if (SDL_strcmp(mime_type, IMAGE_MIME_TYPE) == 0) {
318 if (IsClipboardFormatAvailable(IMAGE_FORMAT)) {
319 if (WIN_OpenClipboard(_this)) {
320 HANDLE hMem;
321
322 hMem = GetClipboardData(IMAGE_FORMAT);
323 if (hMem) {
324 data = WIN_ConvertDIBtoBMP(hMem, size);
325 } else {
326 WIN_SetError("Couldn't get clipboard data");
327 }
328 WIN_CloseClipboard();
329 }
330 }
331 } else {
332 data = SDL_GetInternalClipboardData(_this, mime_type, size);
333 }
334 return data;
335}
336
337bool WIN_HasClipboardData(SDL_VideoDevice *_this, const char *mime_type)
338{
339 if (SDL_IsTextMimeType(mime_type)) {
340 if (IsClipboardFormatAvailable(TEXT_FORMAT)) {
341 return true;
342 }
343 } else if (SDL_strcmp(mime_type, IMAGE_MIME_TYPE) == 0) {
344 if (IsClipboardFormatAvailable(IMAGE_FORMAT)) {
345 return true;
346 }
347 } else {
348 if (SDL_HasInternalClipboardData(_this, mime_type)) {
349 return true;
350 }
351 }
352 return false;
353}
354
355static int GetClipboardFormatMimeType(UINT format, char *name)
356{
357 static struct
358 {
359 UINT format;
360 const char *mime_type;
361 } mime_types[] = {
362 { TEXT_FORMAT, "text/plain;charset=utf-8" },
363 { IMAGE_FORMAT, IMAGE_MIME_TYPE },
364 };
365
366 for (int i = 0; i < SDL_arraysize(mime_types); ++i) {
367 if (format == mime_types[i].format) {
368 size_t len = SDL_strlen(mime_types[i].mime_type) + 1;
369 if (name) {
370 SDL_memcpy(name, mime_types[i].mime_type, len);
371 }
372 return (int)len;
373 }
374 }
375 return 0;
376}
377
378static char **GetMimeTypes(int *pnformats)
379{
380 char **new_mime_types = NULL;
381
382 *pnformats = 0;
383
384 if (WIN_OpenClipboard(SDL_GetVideoDevice())) {
385 int nformats = 0;
386 UINT format = 0;
387 int formatsSz = 0;
388 for ( ; ; ) {
389 format = EnumClipboardFormats(format);
390 if (!format) {
391 break;
392 }
393
394 int len = GetClipboardFormatMimeType(format, NULL);
395 if (len > 0) {
396 ++nformats;
397 formatsSz += len;
398 }
399 }
400
401 new_mime_types = SDL_AllocateTemporaryMemory((nformats + 1) * sizeof(char *) + formatsSz);
402 if (new_mime_types) {
403 format = 0;
404 char *strPtr = (char *)(new_mime_types + nformats + 1);
405 int i = 0;
406 for ( ; ; ) {
407 format = EnumClipboardFormats(format);
408 if (!format) {
409 break;
410 }
411
412 int len = GetClipboardFormatMimeType(format, strPtr);
413 if (len > 0) {
414 new_mime_types[i++] = strPtr;
415 strPtr += len;
416 }
417 }
418
419 new_mime_types[nformats] = NULL;
420 *pnformats = nformats;
421 }
422 WIN_CloseClipboard();
423 }
424 return new_mime_types;
425}
426
427void WIN_CheckClipboardUpdate(struct SDL_VideoData *data)
428{
429 DWORD count = GetClipboardSequenceNumber();
430 if (count != data->clipboard_count) {
431 if (count) {
432 int nformats = 0;
433 char **new_mime_types = GetMimeTypes(&nformats);
434 if (new_mime_types) {
435 SDL_SendClipboardUpdate(false, new_mime_types, nformats);
436 }
437 }
438 data->clipboard_count = count;
439 }
440}
441
442#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 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#ifndef SDL_windowsclipboard_h_
24#define SDL_windowsclipboard_h_
25
26// Forward declaration
27struct SDL_VideoData;
28
29extern bool WIN_SetClipboardData(SDL_VideoDevice *_this);
30extern void *WIN_GetClipboardData(SDL_VideoDevice *_this, const char *mime_type, size_t *size);
31extern bool WIN_HasClipboardData(SDL_VideoDevice *_this, const char *mime_type);
32extern void WIN_CheckClipboardUpdate(struct SDL_VideoData *data);
33
34#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 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#ifdef SDL_VIDEO_DRIVER_WINDOWS
24
25#include "SDL_windowsvideo.h"
26#include "../../events/SDL_events_c.h"
27#include "../../events/SDL_touch_c.h"
28#include "../../events/scancodes_windows.h"
29#include "../../main/SDL_main_callbacks.h"
30#include "../../core/windows/SDL_hid.h"
31
32// Dropfile support
33#include <shellapi.h>
34
35// Device names
36#include <setupapi.h>
37
38// For GET_X_LPARAM, GET_Y_LPARAM.
39#include <windowsx.h>
40
41// For WM_TABLET_QUERYSYSTEMGESTURESTATUS et. al.
42#ifdef HAVE_TPCSHRD_H
43#include <tpcshrd.h>
44#endif // HAVE_TPCSHRD_H
45
46#if 0
47#define WMMSG_DEBUG
48#endif
49#ifdef WMMSG_DEBUG
50#include <stdio.h>
51#include "wmmsg.h"
52#endif
53
54#ifdef SDL_PLATFORM_GDK
55#include "../../core/gdk/SDL_gdk.h"
56#endif
57
58// #define HIGHDPI_DEBUG
59
60// Make sure XBUTTON stuff is defined that isn't in older Platform SDKs...
61#ifndef WM_XBUTTONDOWN
62#define WM_XBUTTONDOWN 0x020B
63#endif
64#ifndef WM_XBUTTONUP
65#define WM_XBUTTONUP 0x020C
66#endif
67#ifndef GET_XBUTTON_WPARAM
68#define GET_XBUTTON_WPARAM(w) (HIWORD(w))
69#endif
70#ifndef WM_INPUT
71#define WM_INPUT 0x00ff
72#endif
73#ifndef WM_TOUCH
74#define WM_TOUCH 0x0240
75#endif
76#ifndef WM_MOUSEHWHEEL
77#define WM_MOUSEHWHEEL 0x020E
78#endif
79#ifndef RI_MOUSE_HWHEEL
80#define RI_MOUSE_HWHEEL 0x0800
81#endif
82#ifndef WM_POINTERUPDATE
83#define WM_POINTERUPDATE 0x0245
84#endif
85#ifndef WM_POINTERDOWN
86#define WM_POINTERDOWN 0x0246
87#endif
88#ifndef WM_POINTERUP
89#define WM_POINTERUP 0x0247
90#endif
91#ifndef WM_POINTERENTER
92#define WM_POINTERENTER 0x0249
93#endif
94#ifndef WM_POINTERLEAVE
95#define WM_POINTERLEAVE 0x024A
96#endif
97#ifndef WM_POINTERCAPTURECHANGED
98#define WM_POINTERCAPTURECHANGED 0x024C
99#endif
100#ifndef WM_UNICHAR
101#define WM_UNICHAR 0x0109
102#endif
103#ifndef WM_DPICHANGED
104#define WM_DPICHANGED 0x02E0
105#endif
106#ifndef WM_GETDPISCALEDSIZE
107#define WM_GETDPISCALEDSIZE 0x02E4
108#endif
109#ifndef TOUCHEVENTF_PEN
110#define TOUCHEVENTF_PEN 0x0040
111#endif
112
113#ifndef MAPVK_VK_TO_VSC_EX
114#define MAPVK_VK_TO_VSC_EX 4
115#endif
116
117#ifndef WC_ERR_INVALID_CHARS
118#define WC_ERR_INVALID_CHARS 0x00000080
119#endif
120
121#ifndef IS_HIGH_SURROGATE
122#define IS_HIGH_SURROGATE(x) (((x) >= 0xd800) && ((x) <= 0xdbff))
123#endif
124
125#ifndef USER_TIMER_MINIMUM
126#define USER_TIMER_MINIMUM 0x0000000A
127#endif
128
129// Used to compare Windows message timestamps
130#define SDL_TICKS_PASSED(A, B) ((Sint32)((B) - (A)) <= 0)
131
132#ifdef _WIN64
133typedef Uint64 QWORD; // Needed for NEXTRAWINPUTBLOCK()
134#endif
135
136static bool SDL_processing_messages;
137static DWORD message_tick;
138static Uint64 timestamp_offset;
139
140static void WIN_SetMessageTick(DWORD tick)
141{
142 message_tick = tick;
143}
144
145static Uint64 WIN_GetEventTimestamp(void)
146{
147 const Uint64 TIMESTAMP_WRAP_OFFSET = SDL_MS_TO_NS(0x100000000LL);
148 Uint64 timestamp, now;
149
150 if (!SDL_processing_messages) {
151 // message_tick isn't valid, just use the current time
152 return 0;
153 }
154
155 now = SDL_GetTicksNS();
156 timestamp = SDL_MS_TO_NS(message_tick);
157 timestamp += timestamp_offset;
158 if (!timestamp_offset) {
159 // Initializing timestamp offset
160 //SDL_Log("Initializing timestamp offset");
161 timestamp_offset = (now - timestamp);
162 timestamp = now;
163 } else if ((Sint64)(now - timestamp - TIMESTAMP_WRAP_OFFSET) >= 0) {
164 // The windows message tick wrapped
165 //SDL_Log("Adjusting timestamp offset for wrapping tick");
166 timestamp_offset += TIMESTAMP_WRAP_OFFSET;
167 timestamp += TIMESTAMP_WRAP_OFFSET;
168 } else if (timestamp > now) {
169 // We got a newer timestamp, but it can't be newer than now, so adjust our offset
170 //SDL_Log("Adjusting timestamp offset, %.2f ms newer", (double)(timestamp - now) / SDL_NS_PER_MS);
171 timestamp_offset -= (timestamp - now);
172 timestamp = now;
173 }
174 return timestamp;
175}
176
177// A message hook called before TranslateMessage()
178static SDL_WindowsMessageHook g_WindowsMessageHook = NULL;
179static void *g_WindowsMessageHookData = NULL;
180
181void SDL_SetWindowsMessageHook(SDL_WindowsMessageHook callback, void *userdata)
182{
183 g_WindowsMessageHook = callback;
184 g_WindowsMessageHookData = userdata;
185}
186
187static SDL_Scancode WindowsScanCodeToSDLScanCode(LPARAM lParam, WPARAM wParam, Uint16 *rawcode, bool *virtual_key)
188{
189 SDL_Scancode code;
190 Uint8 index;
191 Uint16 keyFlags = HIWORD(lParam);
192 Uint16 scanCode = LOBYTE(keyFlags);
193
194 /* On-Screen Keyboard can send wrong scan codes with high-order bit set (key break code).
195 * Strip high-order bit. */
196 scanCode &= ~0x80;
197
198 *virtual_key = (scanCode == 0);
199
200 if (scanCode != 0) {
201 if ((keyFlags & KF_EXTENDED) == KF_EXTENDED) {
202 scanCode = MAKEWORD(scanCode, 0xe0);
203 } else if (scanCode == 0x45) {
204 // Pause
205 scanCode = 0xe046;
206 }
207 } else {
208 Uint16 vkCode = LOWORD(wParam);
209
210#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
211 /* Windows may not report scan codes for some buttons (multimedia buttons etc).
212 * Get scan code from the VK code.*/
213 scanCode = LOWORD(MapVirtualKey(vkCode, WIN_IsWindowsXP() ? MAPVK_VK_TO_VSC : MAPVK_VK_TO_VSC_EX));
214#endif // !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
215
216 /* Pause/Break key have a special scan code with 0xe1 prefix.
217 * Use Pause scan code that is used in Win32. */
218 if (scanCode == 0xe11d) {
219 scanCode = 0xe046;
220 }
221 }
222
223 // Pack scan code into one byte to make the index.
224 index = LOBYTE(scanCode) | (HIBYTE(scanCode) ? 0x80 : 0x00);
225 code = windows_scancode_table[index];
226 *rawcode = scanCode;
227
228 return code;
229}
230
231#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
232static bool WIN_ShouldIgnoreFocusClick(SDL_WindowData *data)
233{
234 return !SDL_WINDOW_IS_POPUP(data->window) &&
235 !SDL_GetHintBoolean(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, false);
236}
237
238static void WIN_CheckWParamMouseButton(Uint64 timestamp, bool bwParamMousePressed, Uint32 mouseFlags, bool bSwapButtons, SDL_WindowData *data, Uint8 button, SDL_MouseID mouseID)
239{
240 if (bSwapButtons) {
241 if (button == SDL_BUTTON_LEFT) {
242 button = SDL_BUTTON_RIGHT;
243 } else if (button == SDL_BUTTON_RIGHT) {
244 button = SDL_BUTTON_LEFT;
245 }
246 }
247
248 if (data->focus_click_pending & SDL_BUTTON_MASK(button)) {
249 // Ignore the button click for activation
250 if (!bwParamMousePressed) {
251 data->focus_click_pending &= ~SDL_BUTTON_MASK(button);
252 WIN_UpdateClipCursor(data->window);
253 }
254 return;
255 }
256
257 if (bwParamMousePressed && !(mouseFlags & SDL_BUTTON_MASK(button))) {
258 SDL_SendMouseButton(timestamp, data->window, mouseID, button, true);
259 } else if (!bwParamMousePressed && (mouseFlags & SDL_BUTTON_MASK(button))) {
260 SDL_SendMouseButton(timestamp, data->window, mouseID, button, false);
261 }
262}
263
264/*
265 * Some windows systems fail to send a WM_LBUTTONDOWN sometimes, but each mouse move contains the current button state also
266 * so this function reconciles our view of the world with the current buttons reported by windows
267 */
268static void WIN_CheckWParamMouseButtons(Uint64 timestamp, WPARAM wParam, SDL_WindowData *data, SDL_MouseID mouseID)
269{
270 if (wParam != data->mouse_button_flags) {
271 SDL_MouseButtonFlags mouseFlags = SDL_GetMouseState(NULL, NULL);
272
273 // WM_LBUTTONDOWN and friends handle button swapping for us. No need to check SM_SWAPBUTTON here.
274 WIN_CheckWParamMouseButton(timestamp, (wParam & MK_LBUTTON), mouseFlags, false, data, SDL_BUTTON_LEFT, mouseID);
275 WIN_CheckWParamMouseButton(timestamp, (wParam & MK_MBUTTON), mouseFlags, false, data, SDL_BUTTON_MIDDLE, mouseID);
276 WIN_CheckWParamMouseButton(timestamp, (wParam & MK_RBUTTON), mouseFlags, false, data, SDL_BUTTON_RIGHT, mouseID);
277 WIN_CheckWParamMouseButton(timestamp, (wParam & MK_XBUTTON1), mouseFlags, false, data, SDL_BUTTON_X1, mouseID);
278 WIN_CheckWParamMouseButton(timestamp, (wParam & MK_XBUTTON2), mouseFlags, false, data, SDL_BUTTON_X2, mouseID);
279
280 data->mouse_button_flags = wParam;
281 }
282}
283
284static void WIN_CheckAsyncMouseRelease(Uint64 timestamp, SDL_WindowData *data)
285{
286 SDL_MouseID mouseID = SDL_GLOBAL_MOUSE_ID;
287 Uint32 mouseFlags;
288 SHORT keyState;
289 bool swapButtons;
290
291 /* mouse buttons may have changed state here, we need to resync them,
292 but we will get a WM_MOUSEMOVE right away which will fix things up if in non raw mode also
293 */
294 mouseFlags = SDL_GetMouseState(NULL, NULL);
295 swapButtons = GetSystemMetrics(SM_SWAPBUTTON) != 0;
296
297 keyState = GetAsyncKeyState(VK_LBUTTON);
298 if (!(keyState & 0x8000)) {
299 WIN_CheckWParamMouseButton(timestamp, false, mouseFlags, swapButtons, data, SDL_BUTTON_LEFT, mouseID);
300 }
301 keyState = GetAsyncKeyState(VK_RBUTTON);
302 if (!(keyState & 0x8000)) {
303 WIN_CheckWParamMouseButton(timestamp, false, mouseFlags, swapButtons, data, SDL_BUTTON_RIGHT, mouseID);
304 }
305 keyState = GetAsyncKeyState(VK_MBUTTON);
306 if (!(keyState & 0x8000)) {
307 WIN_CheckWParamMouseButton(timestamp, false, mouseFlags, swapButtons, data, SDL_BUTTON_MIDDLE, mouseID);
308 }
309 keyState = GetAsyncKeyState(VK_XBUTTON1);
310 if (!(keyState & 0x8000)) {
311 WIN_CheckWParamMouseButton(timestamp, false, mouseFlags, swapButtons, data, SDL_BUTTON_X1, mouseID);
312 }
313 keyState = GetAsyncKeyState(VK_XBUTTON2);
314 if (!(keyState & 0x8000)) {
315 WIN_CheckWParamMouseButton(timestamp, false, mouseFlags, swapButtons, data, SDL_BUTTON_X2, mouseID);
316 }
317 data->mouse_button_flags = (WPARAM)-1;
318}
319
320static void WIN_UpdateMouseCapture(void)
321{
322 SDL_Window *focusWindow = SDL_GetKeyboardFocus();
323
324 if (focusWindow && (focusWindow->flags & SDL_WINDOW_MOUSE_CAPTURE)) {
325 SDL_WindowData *data = focusWindow->internal;
326
327 if (!data->mouse_tracked) {
328 POINT cursorPos;
329
330 if (GetCursorPos(&cursorPos) && ScreenToClient(data->hwnd, &cursorPos)) {
331 bool swapButtons = GetSystemMetrics(SM_SWAPBUTTON) != 0;
332 SDL_MouseID mouseID = SDL_GLOBAL_MOUSE_ID;
333
334 SDL_SendMouseMotion(WIN_GetEventTimestamp(), data->window, mouseID, false, (float)cursorPos.x, (float)cursorPos.y);
335 SDL_SendMouseButton(WIN_GetEventTimestamp(), data->window, mouseID,
336 !swapButtons ? SDL_BUTTON_LEFT : SDL_BUTTON_RIGHT,
337 (GetAsyncKeyState(VK_LBUTTON) & 0x8000) != 0);
338 SDL_SendMouseButton(WIN_GetEventTimestamp(), data->window, mouseID,
339 !swapButtons ? SDL_BUTTON_RIGHT : SDL_BUTTON_LEFT,
340 (GetAsyncKeyState(VK_RBUTTON) & 0x8000) != 0);
341 SDL_SendMouseButton(WIN_GetEventTimestamp(), data->window, mouseID,
342 SDL_BUTTON_MIDDLE,
343 (GetAsyncKeyState(VK_MBUTTON) & 0x8000) != 0);
344 SDL_SendMouseButton(WIN_GetEventTimestamp(), data->window, mouseID,
345 SDL_BUTTON_X1,
346 (GetAsyncKeyState(VK_XBUTTON1) & 0x8000) != 0);
347 SDL_SendMouseButton(WIN_GetEventTimestamp(), data->window, mouseID,
348 SDL_BUTTON_X2,
349 (GetAsyncKeyState(VK_XBUTTON2) & 0x8000) != 0);
350 }
351 }
352 }
353}
354
355static void WIN_UpdateFocus(SDL_Window *window, bool expect_focus)
356{
357 SDL_WindowData *data = window->internal;
358 HWND hwnd = data->hwnd;
359 bool had_focus = (SDL_GetKeyboardFocus() == window);
360 bool has_focus = (GetForegroundWindow() == hwnd);
361
362 if (had_focus == has_focus || has_focus != expect_focus) {
363 return;
364 }
365
366 if (has_focus) {
367 POINT cursorPos;
368
369 if (WIN_ShouldIgnoreFocusClick(data) && !(window->flags & SDL_WINDOW_MOUSE_CAPTURE)) {
370 bool swapButtons = GetSystemMetrics(SM_SWAPBUTTON) != 0;
371 if (GetAsyncKeyState(VK_LBUTTON)) {
372 data->focus_click_pending |= !swapButtons ? SDL_BUTTON_LMASK : SDL_BUTTON_RMASK;
373 }
374 if (GetAsyncKeyState(VK_RBUTTON)) {
375 data->focus_click_pending |= !swapButtons ? SDL_BUTTON_RMASK : SDL_BUTTON_LMASK;
376 }
377 if (GetAsyncKeyState(VK_MBUTTON)) {
378 data->focus_click_pending |= SDL_BUTTON_MMASK;
379 }
380 if (GetAsyncKeyState(VK_XBUTTON1)) {
381 data->focus_click_pending |= SDL_BUTTON_X1MASK;
382 }
383 if (GetAsyncKeyState(VK_XBUTTON2)) {
384 data->focus_click_pending |= SDL_BUTTON_X2MASK;
385 }
386 }
387
388 SDL_SetKeyboardFocus(data->keyboard_focus ? data->keyboard_focus : window);
389
390 // In relative mode we are guaranteed to have mouse focus if we have keyboard focus
391 if (!SDL_GetMouse()->relative_mode) {
392 GetCursorPos(&cursorPos);
393 ScreenToClient(hwnd, &cursorPos);
394 SDL_SendMouseMotion(WIN_GetEventTimestamp(), window, SDL_GLOBAL_MOUSE_ID, false, (float)cursorPos.x, (float)cursorPos.y);
395 }
396
397 WIN_CheckAsyncMouseRelease(WIN_GetEventTimestamp(), data);
398 WIN_UpdateClipCursor(window);
399
400 /*
401 * FIXME: Update keyboard state
402 */
403 WIN_CheckClipboardUpdate(data->videodata);
404
405 SDL_ToggleModState(SDL_KMOD_CAPS, (GetKeyState(VK_CAPITAL) & 0x0001) ? true : false);
406 SDL_ToggleModState(SDL_KMOD_NUM, (GetKeyState(VK_NUMLOCK) & 0x0001) ? true : false);
407 SDL_ToggleModState(SDL_KMOD_SCROLL, (GetKeyState(VK_SCROLL) & 0x0001) ? true : false);
408
409 WIN_UpdateWindowICCProfile(data->window, true);
410 } else {
411 data->in_window_deactivation = true;
412
413 SDL_SetKeyboardFocus(NULL);
414 // In relative mode we are guaranteed to not have mouse focus if we don't have keyboard focus
415 if (SDL_GetMouse()->relative_mode) {
416 SDL_SetMouseFocus(NULL);
417 }
418 WIN_ResetDeadKeys();
419
420 WIN_UnclipCursorForWindow(window);
421
422 data->in_window_deactivation = false;
423 }
424}
425#endif // !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
426
427static bool ShouldGenerateWindowCloseOnAltF4(void)
428{
429 return SDL_GetHintBoolean(SDL_HINT_WINDOWS_CLOSE_ON_ALT_F4, true);
430}
431
432static bool ShouldClearWindowOnEraseBackground(SDL_WindowData *data)
433{
434 switch (data->hint_erase_background_mode) {
435 case SDL_ERASEBACKGROUNDMODE_NEVER:
436 return false;
437 case SDL_ERASEBACKGROUNDMODE_INITIAL:
438 return !data->videodata->cleared;
439 case SDL_ERASEBACKGROUNDMODE_ALWAYS:
440 return true;
441 default:
442 // Unexpected value, fallback to default behaviour
443 return !data->videodata->cleared;
444 }
445}
446
447#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
448// We want to generate mouse events from mouse and pen, and touch events from touchscreens
449#define MI_WP_SIGNATURE 0xFF515700
450#define MI_WP_SIGNATURE_MASK 0xFFFFFF00
451#define IsTouchEvent(dw) ((dw)&MI_WP_SIGNATURE_MASK) == MI_WP_SIGNATURE
452
453typedef enum
454{
455 SDL_MOUSE_EVENT_SOURCE_UNKNOWN,
456 SDL_MOUSE_EVENT_SOURCE_MOUSE,
457 SDL_MOUSE_EVENT_SOURCE_TOUCH,
458 SDL_MOUSE_EVENT_SOURCE_PEN,
459} SDL_MOUSE_EVENT_SOURCE;
460
461static SDL_MOUSE_EVENT_SOURCE GetMouseMessageSource(ULONG extrainfo)
462{
463 // Mouse data (ignoring synthetic mouse events generated for touchscreens)
464 /* Versions below Vista will set the low 7 bits to the Mouse ID and don't use bit 7:
465 Check bits 8-31 for the signature (which will indicate a Tablet PC Pen or Touch Device).
466 Only check bit 7 when Vista and up(Cleared=Pen, Set=Touch(which we need to filter out)),
467 when the signature is set. The Mouse ID will be zero for an actual mouse. */
468 if (IsTouchEvent(extrainfo)) {
469 if (extrainfo & 0x80) {
470 return SDL_MOUSE_EVENT_SOURCE_TOUCH;
471 } else {
472 return SDL_MOUSE_EVENT_SOURCE_PEN;
473 }
474 }
475 /* Sometimes WM_INPUT events won't have the correct touch signature,
476 so we have to rely purely on the touch bit being set. */
477 if (SDL_TouchDevicesAvailable() && extrainfo & 0x80) {
478 return SDL_MOUSE_EVENT_SOURCE_TOUCH;
479 }
480 return SDL_MOUSE_EVENT_SOURCE_MOUSE;
481}
482#endif // !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
483
484static SDL_WindowData *WIN_GetWindowDataFromHWND(HWND hwnd)
485{
486 SDL_VideoDevice *_this = SDL_GetVideoDevice();
487 SDL_Window *window;
488
489 if (_this) {
490 for (window = _this->windows; window; window = window->next) {
491 SDL_WindowData *data = window->internal;
492 if (data && data->hwnd == hwnd) {
493 return data;
494 }
495 }
496 }
497 return NULL;
498}
499
500#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
501LRESULT CALLBACK
502WIN_KeyboardHookProc(int nCode, WPARAM wParam, LPARAM lParam)
503{
504 KBDLLHOOKSTRUCT *hookData = (KBDLLHOOKSTRUCT *)lParam;
505 SDL_VideoData *data = SDL_GetVideoDevice()->internal;
506 SDL_Scancode scanCode;
507
508 if (nCode < 0 || nCode != HC_ACTION) {
509 return CallNextHookEx(NULL, nCode, wParam, lParam);
510 }
511 if (hookData->scanCode == 0x21d) {
512 // Skip fake LCtrl when RAlt is pressed
513 return 1;
514 }
515
516 switch (hookData->vkCode) {
517 case VK_LWIN:
518 scanCode = SDL_SCANCODE_LGUI;
519 break;
520 case VK_RWIN:
521 scanCode = SDL_SCANCODE_RGUI;
522 break;
523 case VK_LMENU:
524 scanCode = SDL_SCANCODE_LALT;
525 break;
526 case VK_RMENU:
527 scanCode = SDL_SCANCODE_RALT;
528 break;
529 case VK_LCONTROL:
530 scanCode = SDL_SCANCODE_LCTRL;
531 break;
532 case VK_RCONTROL:
533 scanCode = SDL_SCANCODE_RCTRL;
534 break;
535
536 // These are required to intercept Alt+Tab and Alt+Esc on Windows 7
537 case VK_TAB:
538 scanCode = SDL_SCANCODE_TAB;
539 break;
540 case VK_ESCAPE:
541 scanCode = SDL_SCANCODE_ESCAPE;
542 break;
543
544 default:
545 return CallNextHookEx(NULL, nCode, wParam, lParam);
546 }
547
548 if (wParam == WM_KEYDOWN || wParam == WM_SYSKEYDOWN) {
549 if (!data->raw_keyboard_enabled) {
550 SDL_SendKeyboardKey(0, SDL_GLOBAL_KEYBOARD_ID, hookData->scanCode, scanCode, true);
551 }
552 } else {
553 if (!data->raw_keyboard_enabled) {
554 SDL_SendKeyboardKey(0, SDL_GLOBAL_KEYBOARD_ID, hookData->scanCode, scanCode, false);
555 }
556
557 /* If the key was down prior to our hook being installed, allow the
558 key up message to pass normally the first time. This ensures other
559 windows have a consistent view of the key state, and avoids keys
560 being stuck down in those windows if they are down when the grab
561 happens and raised while grabbed. */
562 if (hookData->vkCode <= 0xFF && data->pre_hook_key_state[hookData->vkCode]) {
563 data->pre_hook_key_state[hookData->vkCode] = 0;
564 return CallNextHookEx(NULL, nCode, wParam, lParam);
565 }
566 }
567
568 return 1;
569}
570
571static bool WIN_SwapButtons(HANDLE hDevice)
572{
573 if (hDevice == NULL) {
574 // Touchpad, already has buttons swapped
575 return false;
576 }
577 return GetSystemMetrics(SM_SWAPBUTTON) != 0;
578}
579
580static void WIN_HandleRawMouseInput(Uint64 timestamp, SDL_VideoData *data, HANDLE hDevice, RAWMOUSE *rawmouse)
581{
582 static struct {
583 USHORT usButtonFlags;
584 Uint8 button;
585 bool down;
586 } raw_buttons[] = {
587 { RI_MOUSE_LEFT_BUTTON_DOWN, SDL_BUTTON_LEFT, true },
588 { RI_MOUSE_LEFT_BUTTON_UP, SDL_BUTTON_LEFT, false },
589 { RI_MOUSE_RIGHT_BUTTON_DOWN, SDL_BUTTON_RIGHT, true },
590 { RI_MOUSE_RIGHT_BUTTON_UP, SDL_BUTTON_RIGHT, false },
591 { RI_MOUSE_MIDDLE_BUTTON_DOWN, SDL_BUTTON_MIDDLE, true },
592 { RI_MOUSE_MIDDLE_BUTTON_UP, SDL_BUTTON_MIDDLE, false },
593 { RI_MOUSE_BUTTON_4_DOWN, SDL_BUTTON_X1, true },
594 { RI_MOUSE_BUTTON_4_UP, SDL_BUTTON_X1, false },
595 { RI_MOUSE_BUTTON_5_DOWN, SDL_BUTTON_X2, true },
596 { RI_MOUSE_BUTTON_5_UP, SDL_BUTTON_X2, false }
597 };
598
599 int dx = (int)rawmouse->lLastX;
600 int dy = (int)rawmouse->lLastY;
601 bool haveMotion = (dx || dy);
602 bool haveButton = (rawmouse->usButtonFlags != 0);
603 bool isAbsolute = ((rawmouse->usFlags & MOUSE_MOVE_ABSOLUTE) != 0);
604 SDL_MouseID mouseID = (SDL_MouseID)(uintptr_t)hDevice;
605
606 // Check whether relative mode should also receive events from the rawinput stream
607 if (!data->raw_mouse_enabled) {
608 return;
609 }
610
611 // Relative mouse motion is delivered to the window with keyboard focus
612 SDL_Window *window = SDL_GetKeyboardFocus();
613 if (!window) {
614 return;
615 }
616
617 if (GetMouseMessageSource(rawmouse->ulExtraInformation) != SDL_MOUSE_EVENT_SOURCE_MOUSE) {
618 return;
619 }
620
621 SDL_WindowData *windowdata = window->internal;
622
623 if (haveMotion) {
624 if (!isAbsolute) {
625 SDL_SendMouseMotion(timestamp, window, mouseID, true, (float)dx, (float)dy);
626 } else {
627 /* This is absolute motion, either using a tablet or mouse over RDP
628
629 Notes on how RDP appears to work, as of Windows 10 2004:
630 - 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.
631 - User mouse motion is coalesced with SetCursorPos(), so the WM_INPUT positions we see will not necessarily match the position we requested with SetCursorPos().
632 - SetCursorPos() outside of the bounds of the focus window appears not to do anything.
633 - SetCursorPos() while the cursor is NULL doesn't do anything
634
635 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.
636 */
637 bool remote_desktop = (GetSystemMetrics(SM_REMOTESESSION) == TRUE);
638 bool virtual_desktop = ((rawmouse->usFlags & MOUSE_VIRTUAL_DESKTOP) != 0);
639 bool raw_coordinates = ((rawmouse->usFlags & 0x40) != 0);
640 int w = GetSystemMetrics(virtual_desktop ? SM_CXVIRTUALSCREEN : SM_CXSCREEN);
641 int h = GetSystemMetrics(virtual_desktop ? SM_CYVIRTUALSCREEN : SM_CYSCREEN);
642 int x = raw_coordinates ? dx : (int)(((float)dx / 65535.0f) * w);
643 int y = raw_coordinates ? dy : (int)(((float)dy / 65535.0f) * h);
644 int relX, relY;
645
646 /* Calculate relative motion */
647 if (data->last_raw_mouse_position.x == 0 && data->last_raw_mouse_position.y == 0) {
648 data->last_raw_mouse_position.x = x;
649 data->last_raw_mouse_position.y = y;
650 }
651 relX = x - data->last_raw_mouse_position.x;
652 relY = y - data->last_raw_mouse_position.y;
653
654 if (remote_desktop) {
655 if (!windowdata->in_title_click && !windowdata->focus_click_pending) {
656 static int wobble;
657 float floatX = (float)x / w;
658 float floatY = (float)y / h;
659
660 /* See if the mouse is at the edge of the screen, or in the RDP title bar area */
661 if (floatX <= 0.01f || floatX >= 0.99f || floatY <= 0.01f || floatY >= 0.99f || y < 32) {
662 /* Wobble the cursor position so it's not ignored if the last warp didn't have any effect */
663 RECT rect = windowdata->cursor_clipped_rect;
664 int warpX = rect.left + ((rect.right - rect.left) / 2) + wobble;
665 int warpY = rect.top + ((rect.bottom - rect.top) / 2);
666
667 WIN_SetCursorPos(warpX, warpY);
668
669 ++wobble;
670 if (wobble > 1) {
671 wobble = -1;
672 }
673 } else {
674 /* Send relative motion if we didn't warp last frame (had good position data)
675 We also sometimes get large deltas due to coalesced mouse motion and warping,
676 so ignore those.
677 */
678 const int MAX_RELATIVE_MOTION = (h / 6);
679 if (SDL_abs(relX) < MAX_RELATIVE_MOTION &&
680 SDL_abs(relY) < MAX_RELATIVE_MOTION) {
681 SDL_SendMouseMotion(timestamp, window, mouseID, true, (float)relX, (float)relY);
682 }
683 }
684 }
685 } else {
686 const int MAXIMUM_TABLET_RELATIVE_MOTION = 32;
687 if (SDL_abs(relX) > MAXIMUM_TABLET_RELATIVE_MOTION ||
688 SDL_abs(relY) > MAXIMUM_TABLET_RELATIVE_MOTION) {
689 /* Ignore this motion, probably a pen lift and drop */
690 } else {
691 SDL_SendMouseMotion(timestamp, window, mouseID, true, (float)relX, (float)relY);
692 }
693 }
694
695 data->last_raw_mouse_position.x = x;
696 data->last_raw_mouse_position.y = y;
697 }
698 }
699
700 if (haveButton) {
701 for (int i = 0; i < SDL_arraysize(raw_buttons); ++i) {
702 if (rawmouse->usButtonFlags & raw_buttons[i].usButtonFlags) {
703 Uint8 button = raw_buttons[i].button;
704 bool down = raw_buttons[i].down;
705
706 if (button == SDL_BUTTON_LEFT) {
707 if (WIN_SwapButtons(hDevice)) {
708 button = SDL_BUTTON_RIGHT;
709 }
710 } else if (button == SDL_BUTTON_RIGHT) {
711 if (WIN_SwapButtons(hDevice)) {
712 button = SDL_BUTTON_LEFT;
713 }
714 }
715
716 if (windowdata->focus_click_pending & SDL_BUTTON_MASK(button)) {
717 // Ignore the button click for activation
718 if (!down) {
719 windowdata->focus_click_pending &= ~SDL_BUTTON_MASK(button);
720 WIN_UpdateClipCursor(window);
721 }
722 continue;
723 }
724
725 SDL_SendMouseButton(timestamp, window, mouseID, button, down);
726 }
727 }
728
729 if (rawmouse->usButtonFlags & RI_MOUSE_WHEEL) {
730 SHORT amount = (SHORT)rawmouse->usButtonData;
731 float fAmount = (float)amount / WHEEL_DELTA;
732 SDL_SendMouseWheel(WIN_GetEventTimestamp(), window, mouseID, 0.0f, fAmount, SDL_MOUSEWHEEL_NORMAL);
733 } else if (rawmouse->usButtonFlags & RI_MOUSE_HWHEEL) {
734 SHORT amount = (SHORT)rawmouse->usButtonData;
735 float fAmount = (float)amount / WHEEL_DELTA;
736 SDL_SendMouseWheel(WIN_GetEventTimestamp(), window, mouseID, fAmount, 0.0f, SDL_MOUSEWHEEL_NORMAL);
737 }
738 }
739}
740
741static void WIN_HandleRawKeyboardInput(Uint64 timestamp, SDL_VideoData *data, HANDLE hDevice, RAWKEYBOARD *rawkeyboard)
742{
743 SDL_KeyboardID keyboardID = (SDL_KeyboardID)(uintptr_t)hDevice;
744
745 if (!data->raw_keyboard_enabled) {
746 return;
747 }
748
749 if (rawkeyboard->Flags & RI_KEY_E1) {
750 // First key in a Ctrl+{key} sequence
751 data->pending_E1_key_sequence = true;
752 return;
753 }
754
755 if ((rawkeyboard->Flags & RI_KEY_E0) && rawkeyboard->MakeCode == 0x2A) {
756 // 0xE02A make code prefix, ignored
757 return;
758 }
759
760#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
761 if (!rawkeyboard->MakeCode) {
762 rawkeyboard->MakeCode = LOWORD(MapVirtualKey(rawkeyboard->VKey, WIN_IsWindowsXP() ? MAPVK_VK_TO_VSC : MAPVK_VK_TO_VSC_EX));
763 }
764#endif
765 if (!rawkeyboard->MakeCode) {
766 return;
767 }
768
769 bool down = !(rawkeyboard->Flags & RI_KEY_BREAK);
770 SDL_Scancode code;
771 USHORT rawcode = rawkeyboard->MakeCode;
772 if (data->pending_E1_key_sequence) {
773 rawcode |= 0xE100;
774 if (rawkeyboard->MakeCode == 0x45) {
775 // Ctrl+NumLock == Pause
776 code = SDL_SCANCODE_PAUSE;
777 } else {
778 // Ctrl+ScrollLock == Break (no SDL scancode?)
779 code = SDL_SCANCODE_UNKNOWN;
780 }
781 data->pending_E1_key_sequence = false;
782 } else {
783 // The code is in the lower 7 bits, the high bit is set for the E0 prefix
784 Uint8 index = (Uint8)rawkeyboard->MakeCode;
785 if (rawkeyboard->Flags & RI_KEY_E0) {
786 rawcode |= 0xE000;
787 index |= 0x80;
788 }
789 code = windows_scancode_table[index];
790 }
791
792 if (down) {
793 SDL_Window *focus = SDL_GetKeyboardFocus();
794 if (!focus || focus->text_input_active) {
795 return;
796 }
797 }
798
799 SDL_SendKeyboardKey(timestamp, keyboardID, rawcode, code, down);
800}
801
802void WIN_PollRawInput(SDL_VideoDevice *_this, Uint64 poll_start)
803{
804 SDL_VideoData *data = _this->internal;
805 UINT size, i, count, total = 0;
806 RAWINPUT *input;
807 Uint64 poll_finish;
808
809 if (data->rawinput_offset == 0) {
810 BOOL isWow64;
811
812 data->rawinput_offset = sizeof(RAWINPUTHEADER);
813 if (IsWow64Process(GetCurrentProcess(), &isWow64) && isWow64) {
814 // We're going to get 64-bit data, so use the 64-bit RAWINPUTHEADER size
815 data->rawinput_offset += 8;
816 }
817 }
818
819 // Get all available events
820 input = (RAWINPUT *)data->rawinput;
821 for (;;) {
822 size = data->rawinput_size - (UINT)((BYTE *)input - data->rawinput);
823 count = GetRawInputBuffer(input, &size, sizeof(RAWINPUTHEADER));
824 poll_finish = SDL_GetTicksNS();
825 if (count == 0 || count == (UINT)-1) {
826 if (!data->rawinput || (count == (UINT)-1 && GetLastError() == ERROR_INSUFFICIENT_BUFFER)) {
827 const UINT RAWINPUT_BUFFER_SIZE_INCREMENT = 96; // 2 64-bit raw mouse packets
828 BYTE *rawinput = (BYTE *)SDL_realloc(data->rawinput, data->rawinput_size + RAWINPUT_BUFFER_SIZE_INCREMENT);
829 if (!rawinput) {
830 break;
831 }
832 input = (RAWINPUT *)(rawinput + ((BYTE *)input - data->rawinput));
833 data->rawinput = rawinput;
834 data->rawinput_size += RAWINPUT_BUFFER_SIZE_INCREMENT;
835 } else {
836 break;
837 }
838 } else {
839 total += count;
840
841 // Advance input to the end of the buffer
842 while (count--) {
843 input = NEXTRAWINPUTBLOCK(input);
844 }
845 }
846 }
847
848 if (total > 0) {
849 Uint64 delta = poll_finish - poll_start;
850 UINT mouse_total = 0;
851 for (i = 0, input = (RAWINPUT *)data->rawinput; i < total; ++i, input = NEXTRAWINPUTBLOCK(input)) {
852 if (input->header.dwType == RIM_TYPEMOUSE) {
853 mouse_total += 1;
854 }
855 }
856 int mouse_index = 0;
857 for (i = 0, input = (RAWINPUT *)data->rawinput; i < total; ++i, input = NEXTRAWINPUTBLOCK(input)) {
858 if (input->header.dwType == RIM_TYPEMOUSE) {
859 mouse_index += 1; // increment first so that it starts at one
860 RAWMOUSE *rawmouse = (RAWMOUSE *)((BYTE *)input + data->rawinput_offset);
861 Uint64 time = poll_finish - (delta * (mouse_total - mouse_index)) / mouse_total;
862 WIN_HandleRawMouseInput(time, data, input->header.hDevice, rawmouse);
863 } else if (input->header.dwType == RIM_TYPEKEYBOARD) {
864 RAWKEYBOARD *rawkeyboard = (RAWKEYBOARD *)((BYTE *)input + data->rawinput_offset);
865 WIN_HandleRawKeyboardInput(poll_finish, data, input->header.hDevice, rawkeyboard);
866 }
867 }
868 }
869 data->last_rawinput_poll = poll_finish;
870}
871
872#endif // !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
873
874static void AddDeviceID(Uint32 deviceID, Uint32 **list, int *count)
875{
876 int new_count = (*count + 1);
877 Uint32 *new_list = (Uint32 *)SDL_realloc(*list, new_count * sizeof(*new_list));
878 if (!new_list) {
879 // Oh well, we'll drop this one
880 return;
881 }
882 new_list[new_count - 1] = deviceID;
883
884 *count = new_count;
885 *list = new_list;
886}
887
888static bool HasDeviceID(Uint32 deviceID, const Uint32 *list, int count)
889{
890 for (int i = 0; i < count; ++i) {
891 if (deviceID == list[i]) {
892 return true;
893 }
894 }
895 return false;
896}
897
898#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
899static char *GetDeviceName(HANDLE hDevice, HDEVINFO devinfo, const char *instance, const char *default_name, bool hid_loaded)
900{
901 char *vendor_name = NULL;
902 char *product_name = NULL;
903 char *name = NULL;
904
905 // These are 126 for USB, but can be longer for Bluetooth devices
906 WCHAR vend[256], prod[256];
907 vend[0] = 0;
908 prod[0] = 0;
909
910
911 HIDD_ATTRIBUTES attr;
912 attr.VendorID = 0;
913 attr.ProductID = 0;
914 attr.Size = sizeof(attr);
915
916 if (hid_loaded) {
917 char devName[MAX_PATH + 1];
918 UINT cap = sizeof(devName) - 1;
919 UINT len = GetRawInputDeviceInfoA(hDevice, RIDI_DEVICENAME, devName, &cap);
920 if (len != (UINT)-1) {
921 devName[len] = '\0';
922
923 // important: for devices with exclusive access mode as per
924 // https://learn.microsoft.com/en-us/windows-hardware/drivers/hid/top-level-collections-opened-by-windows-for-system-use
925 // they can only be opened with a desired access of none instead of generic read.
926 HANDLE hFile = CreateFileA(devName, 0, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
927 if (hFile != INVALID_HANDLE_VALUE) {
928 SDL_HidD_GetAttributes(hFile, &attr);
929 SDL_HidD_GetManufacturerString(hFile, vend, sizeof(vend));
930 SDL_HidD_GetProductString(hFile, prod, sizeof(prod));
931 CloseHandle(hFile);
932 }
933 }
934 }
935
936 if (vend[0]) {
937 vendor_name = WIN_StringToUTF8W(vend);
938 }
939
940 if (prod[0]) {
941 product_name = WIN_StringToUTF8W(prod);
942 } else {
943 SP_DEVINFO_DATA data;
944 SDL_zero(data);
945 data.cbSize = sizeof(data);
946 for (DWORD i = 0;; ++i) {
947 if (!SetupDiEnumDeviceInfo(devinfo, i, &data)) {
948 if (GetLastError() == ERROR_NO_MORE_ITEMS) {
949 break;
950 } else {
951 continue;
952 }
953 }
954
955 char DeviceInstanceId[64];
956 if (!SetupDiGetDeviceInstanceIdA(devinfo, &data, DeviceInstanceId, sizeof(DeviceInstanceId), NULL))
957 continue;
958
959 if (SDL_strcasecmp(instance, DeviceInstanceId) == 0) {
960 DWORD size = 0;
961 if (SetupDiGetDeviceRegistryPropertyW(devinfo, &data, SPDRP_DEVICEDESC, NULL, (PBYTE)prod, sizeof(prod), &size)) {
962 // Make sure the device description is null terminated
963 size /= sizeof(*prod);
964 if (size >= SDL_arraysize(prod)) {
965 // Truncated description...
966 size = (SDL_arraysize(prod) - 1);
967 }
968 prod[size] = 0;
969
970 if (attr.VendorID || attr.ProductID) {
971 SDL_asprintf(&product_name, "%S (0x%.4x/0x%.4x)", prod, attr.VendorID, attr.ProductID);
972 } else {
973 product_name = WIN_StringToUTF8W(prod);
974 }
975 }
976 break;
977 }
978 }
979 }
980
981 if (!product_name && (attr.VendorID || attr.ProductID)) {
982 SDL_asprintf(&product_name, "%s (0x%.4x/0x%.4x)", default_name, attr.VendorID, attr.ProductID);
983 }
984 name = SDL_CreateDeviceName(attr.VendorID, attr.ProductID, vendor_name, product_name, default_name);
985 SDL_free(vendor_name);
986 SDL_free(product_name);
987
988 return name;
989}
990
991void WIN_CheckKeyboardAndMouseHotplug(SDL_VideoDevice *_this, bool initial_check)
992{
993 PRAWINPUTDEVICELIST raw_devices = NULL;
994 UINT raw_device_count = 0;
995 int old_keyboard_count = 0;
996 SDL_KeyboardID *old_keyboards = NULL;
997 int new_keyboard_count = 0;
998 SDL_KeyboardID *new_keyboards = NULL;
999 int old_mouse_count = 0;
1000 SDL_MouseID *old_mice = NULL;
1001 int new_mouse_count = 0;
1002 SDL_MouseID *new_mice = NULL;
1003 bool send_event = !initial_check;
1004
1005 // Check to see if anything has changed
1006 static Uint64 s_last_device_change;
1007 Uint64 last_device_change = WIN_GetLastDeviceNotification();
1008 if (!initial_check && last_device_change == s_last_device_change) {
1009 return;
1010 }
1011 s_last_device_change = last_device_change;
1012
1013 if ((GetRawInputDeviceList(NULL, &raw_device_count, sizeof(RAWINPUTDEVICELIST)) == -1) || (!raw_device_count)) {
1014 return; // oh well.
1015 }
1016
1017 raw_devices = (PRAWINPUTDEVICELIST)SDL_malloc(sizeof(RAWINPUTDEVICELIST) * raw_device_count);
1018 if (!raw_devices) {
1019 return; // oh well.
1020 }
1021
1022 raw_device_count = GetRawInputDeviceList(raw_devices, &raw_device_count, sizeof(RAWINPUTDEVICELIST));
1023 if (raw_device_count == (UINT)-1) {
1024 SDL_free(raw_devices);
1025 raw_devices = NULL;
1026 return; // oh well.
1027 }
1028
1029 HDEVINFO devinfo = SetupDiGetClassDevsA(NULL, NULL, NULL, (DIGCF_ALLCLASSES | DIGCF_PRESENT));
1030
1031 old_keyboards = SDL_GetKeyboards(&old_keyboard_count);
1032 old_mice = SDL_GetMice(&old_mouse_count);
1033
1034 bool hid_loaded = WIN_LoadHIDDLL();
1035 for (UINT i = 0; i < raw_device_count; i++) {
1036 RID_DEVICE_INFO rdi;
1037 char devName[MAX_PATH] = { 0 };
1038 UINT rdiSize = sizeof(rdi);
1039 UINT nameSize = SDL_arraysize(devName);
1040 int vendor = 0, product = 0;
1041 DWORD dwType = raw_devices[i].dwType;
1042 char *instance, *ptr, *name;
1043
1044 if (dwType != RIM_TYPEKEYBOARD && dwType != RIM_TYPEMOUSE) {
1045 continue;
1046 }
1047
1048 rdi.cbSize = sizeof(rdi);
1049 if (GetRawInputDeviceInfoA(raw_devices[i].hDevice, RIDI_DEVICEINFO, &rdi, &rdiSize) == ((UINT)-1) ||
1050 GetRawInputDeviceInfoA(raw_devices[i].hDevice, RIDI_DEVICENAME, devName, &nameSize) == ((UINT)-1)) {
1051 continue;
1052 }
1053
1054 // Extract the device instance
1055 instance = devName;
1056 while (*instance == '\\' || *instance == '?') {
1057 ++instance;
1058 }
1059 for (ptr = instance; *ptr; ++ptr) {
1060 if (*ptr == '#') {
1061 *ptr = '\\';
1062 }
1063 if (*ptr == '{') {
1064 if (ptr > instance && ptr[-1] == '\\') {
1065 --ptr;
1066 }
1067 break;
1068 }
1069 }
1070 *ptr = '\0';
1071
1072 SDL_sscanf(instance, "HID\\VID_%X&PID_%X&", &vendor, &product);
1073
1074 switch (dwType) {
1075 case RIM_TYPEKEYBOARD:
1076 if (SDL_IsKeyboard((Uint16)vendor, (Uint16)product, rdi.keyboard.dwNumberOfKeysTotal)) {
1077 SDL_KeyboardID keyboardID = (Uint32)(uintptr_t)raw_devices[i].hDevice;
1078 AddDeviceID(keyboardID, &new_keyboards, &new_keyboard_count);
1079 if (!HasDeviceID(keyboardID, old_keyboards, old_keyboard_count)) {
1080 name = GetDeviceName(raw_devices[i].hDevice, devinfo, instance, "Keyboard", hid_loaded);
1081 SDL_AddKeyboard(keyboardID, name, send_event);
1082 SDL_free(name);
1083 }
1084 }
1085 break;
1086 case RIM_TYPEMOUSE:
1087 if (SDL_IsMouse((Uint16)vendor, (Uint16)product)) {
1088 SDL_MouseID mouseID = (Uint32)(uintptr_t)raw_devices[i].hDevice;
1089 AddDeviceID(mouseID, &new_mice, &new_mouse_count);
1090 if (!HasDeviceID(mouseID, old_mice, old_mouse_count)) {
1091 name = GetDeviceName(raw_devices[i].hDevice, devinfo, instance, "Mouse", hid_loaded);
1092 SDL_AddMouse(mouseID, name, send_event);
1093 SDL_free(name);
1094 }
1095 }
1096 break;
1097 default:
1098 break;
1099 }
1100 }
1101 if (hid_loaded) {
1102 WIN_UnloadHIDDLL();
1103 }
1104
1105 for (int i = old_keyboard_count; i--;) {
1106 if (!HasDeviceID(old_keyboards[i], new_keyboards, new_keyboard_count)) {
1107 SDL_RemoveKeyboard(old_keyboards[i], send_event);
1108 }
1109 }
1110
1111 for (int i = old_mouse_count; i--;) {
1112 if (!HasDeviceID(old_mice[i], new_mice, new_mouse_count)) {
1113 SDL_RemoveMouse(old_mice[i], send_event);
1114 }
1115 }
1116
1117 SDL_free(old_keyboards);
1118 SDL_free(new_keyboards);
1119 SDL_free(old_mice);
1120 SDL_free(new_mice);
1121
1122 SetupDiDestroyDeviceInfoList(devinfo);
1123
1124 SDL_free(raw_devices);
1125}
1126#endif // !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
1127
1128// Return true if spurious LCtrl is pressed
1129// LCtrl is sent when RAltGR is pressed
1130static bool SkipAltGrLeftControl(WPARAM wParam, LPARAM lParam)
1131{
1132 if (wParam != VK_CONTROL) {
1133 return false;
1134 }
1135
1136 // Is this an extended key (i.e. right key)?
1137 if (lParam & 0x01000000) {
1138 return false;
1139 }
1140
1141#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
1142 // Here is a trick: "Alt Gr" sends LCTRL, then RALT. We only
1143 // want the RALT message, so we try to see if the next message
1144 // is a RALT message. In that case, this is a false LCTRL!
1145 MSG next_msg;
1146 DWORD msg_time = GetMessageTime();
1147 if (PeekMessage(&next_msg, NULL, 0, 0, PM_NOREMOVE)) {
1148 if (next_msg.message == WM_KEYDOWN ||
1149 next_msg.message == WM_SYSKEYDOWN) {
1150 if (next_msg.wParam == VK_MENU && (next_msg.lParam & 0x01000000) && next_msg.time == msg_time) {
1151 // Next message is a RALT down message, which means that this is NOT a proper LCTRL message!
1152 return true;
1153 }
1154 }
1155 }
1156#endif // !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
1157
1158 return false;
1159}
1160
1161static bool DispatchModalLoopMessageHook(HWND *hwnd, UINT *msg, WPARAM *wParam, LPARAM *lParam)
1162{
1163 MSG dummy;
1164
1165 SDL_zero(dummy);
1166 dummy.hwnd = *hwnd;
1167 dummy.message = *msg;
1168 dummy.wParam = *wParam;
1169 dummy.lParam = *lParam;
1170 if (g_WindowsMessageHook(g_WindowsMessageHookData, &dummy)) {
1171 // Can't modify the hwnd, but everything else is fair game
1172 *msg = dummy.message;
1173 *wParam = dummy.wParam;
1174 *lParam = dummy.lParam;
1175 return true;
1176 }
1177 return false;
1178}
1179
1180LRESULT CALLBACK WIN_WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
1181{
1182 SDL_WindowData *data;
1183 LRESULT returnCode = -1;
1184
1185 // Get the window data for the window
1186 data = WIN_GetWindowDataFromHWND(hwnd);
1187#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
1188 if (!data) {
1189 // Fallback
1190 data = (SDL_WindowData *)GetProp(hwnd, TEXT("SDL_WindowData"));
1191 }
1192#endif
1193 if (!data) {
1194 return CallWindowProc(DefWindowProc, hwnd, msg, wParam, lParam);
1195 }
1196
1197#ifdef WMMSG_DEBUG
1198 {
1199 char message[1024];
1200 if (msg > MAX_WMMSG) {
1201 SDL_snprintf(message, sizeof(message), "Received windows message: %p UNKNOWN (%d) -- 0x%x, 0x%x\r\n", hwnd, msg, wParam, lParam);
1202 } else {
1203 SDL_snprintf(message, sizeof(message), "Received windows message: %p %s -- 0x%x, 0x%x\r\n", hwnd, wmtab[msg], wParam, lParam);
1204 }
1205 OutputDebugStringA(message);
1206 }
1207#endif // WMMSG_DEBUG
1208
1209
1210 if (g_WindowsMessageHook && data->in_modal_loop) {
1211 // Synthesize a message for window hooks so they can modify the message if desired
1212 if (!DispatchModalLoopMessageHook(&hwnd, &msg, &wParam, &lParam)) {
1213 return 0;
1214 }
1215 }
1216
1217#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
1218 if (WIN_HandleIMEMessage(hwnd, msg, wParam, &lParam, data->videodata)) {
1219 return 0;
1220 }
1221#endif
1222
1223 switch (msg) {
1224
1225 case WM_SHOWWINDOW:
1226 {
1227 if (wParam) {
1228 SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_SHOWN, 0, 0);
1229 } else {
1230 SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_HIDDEN, 0, 0);
1231 }
1232 } break;
1233
1234#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
1235 case WM_NCACTIVATE:
1236 {
1237 // Don't immediately clip the cursor in case we're clicking minimize/maximize buttons
1238 // This is the only place that this flag is set. This causes all subsequent calls to
1239 // WIN_UpdateClipCursor for this window to be no-ops in this frame's message-pumping.
1240 // This flag is unset at the end of message pumping each frame for every window, and
1241 // should never be carried over between frames.
1242 data->skip_update_clipcursor = true;
1243
1244 /* Update the focus here, since it's possible to get WM_ACTIVATE and WM_SETFOCUS without
1245 actually being the foreground window, but this appears to get called in all cases where
1246 the global foreground window changes to and from this window. */
1247 WIN_UpdateFocus(data->window, !!wParam);
1248 } break;
1249
1250 case WM_ACTIVATE:
1251 {
1252 // Update the focus in case we changed focus to a child window and then away from the application
1253 WIN_UpdateFocus(data->window, !!LOWORD(wParam));
1254 } break;
1255
1256 case WM_MOUSEACTIVATE:
1257 {
1258 if (SDL_WINDOW_IS_POPUP(data->window)) {
1259 return MA_NOACTIVATE;
1260 }
1261
1262 // Check parents to see if they are in relative mouse mode and focused
1263 SDL_Window *parent = data->window->parent;
1264 while (parent) {
1265 if ((parent->flags & SDL_WINDOW_INPUT_FOCUS) &&
1266 (parent->flags & SDL_WINDOW_MOUSE_RELATIVE_MODE)) {
1267 return MA_NOACTIVATE;
1268 }
1269 parent = parent->parent;
1270 }
1271 } break;
1272
1273 case WM_SETFOCUS:
1274 {
1275 // Update the focus in case it's changing between top-level windows in the same application
1276 WIN_UpdateFocus(data->window, true);
1277 } break;
1278
1279 case WM_KILLFOCUS:
1280 case WM_ENTERIDLE:
1281 {
1282 // Update the focus in case it's changing between top-level windows in the same application
1283 WIN_UpdateFocus(data->window, false);
1284 } break;
1285
1286 case WM_POINTERENTER:
1287 {
1288 if (!data->videodata->GetPointerType) {
1289 break; // Not on Windows8 or later? We shouldn't get this event, but just in case...
1290 }
1291
1292 const UINT32 pointerid = GET_POINTERID_WPARAM(wParam);
1293 void *hpointer = (void *) (size_t) pointerid;
1294 POINTER_INPUT_TYPE pointer_type = PT_POINTER;
1295 if (!data->videodata->GetPointerType(pointerid, &pointer_type)) {
1296 break; // oh well.
1297 } else if (pointer_type != PT_PEN) {
1298 break; // we only care about pens here.
1299 } else if (SDL_FindPenByHandle(hpointer)) {
1300 break; // we already have this one, don't readd it.
1301 }
1302
1303 // one can use GetPointerPenInfo() to get the current state of the pen, and check POINTER_PEN_INFO::penMask,
1304 // but the docs aren't clear if these masks are _always_ set for pens with specific features, or if they
1305 // could be unset at this moment because Windows is still deciding what capabilities the pen has, and/or
1306 // doesn't yet have valid data for them. As such, just say everything that the interface supports is
1307 // available...we don't expose this information through the public API at the moment anyhow.
1308 SDL_PenInfo info;
1309 SDL_zero(info);
1310 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;
1311 info.max_tilt = 90.0f;
1312 info.num_buttons = 1;
1313 info.subtype = SDL_PEN_TYPE_PENCIL;
1314 SDL_AddPenDevice(0, NULL, &info, hpointer);
1315 returnCode = 0;
1316 } break;
1317
1318 case WM_POINTERCAPTURECHANGED:
1319 case WM_POINTERLEAVE:
1320 {
1321 const UINT32 pointerid = GET_POINTERID_WPARAM(wParam);
1322 void *hpointer = (void *) (size_t) pointerid;
1323 const SDL_PenID pen = SDL_FindPenByHandle(hpointer);
1324 if (pen == 0) {
1325 break; // not a pen, or not a pen we already knew about.
1326 }
1327
1328 // if this just left the _window_, we don't care. If this is no longer visible to the tablet, time to remove it!
1329 if ((msg == WM_POINTERCAPTURECHANGED) || !IS_POINTER_INCONTACT_WPARAM(wParam)) {
1330 SDL_RemovePenDevice(WIN_GetEventTimestamp(), pen);
1331 }
1332 returnCode = 0;
1333 } break;
1334
1335 case WM_POINTERUPDATE: {
1336 POINTER_INPUT_TYPE pointer_type = PT_POINTER;
1337 if (!data->videodata->GetPointerType || !data->videodata->GetPointerType(GET_POINTERID_WPARAM(wParam), &pointer_type)) {
1338 break; // oh well.
1339 }
1340
1341 if (pointer_type == PT_MOUSE) {
1342 data->last_pointer_update = lParam;
1343 returnCode = 0;
1344 break;
1345 }
1346 }
1347 SDL_FALLTHROUGH;
1348
1349 case WM_POINTERDOWN:
1350 case WM_POINTERUP: {
1351 POINTER_PEN_INFO pen_info;
1352 const UINT32 pointerid = GET_POINTERID_WPARAM(wParam);
1353 void *hpointer = (void *) (size_t) pointerid;
1354 const SDL_PenID pen = SDL_FindPenByHandle(hpointer);
1355 if (pen == 0) {
1356 break; // not a pen, or not a pen we already knew about.
1357 } else if (!data->videodata->GetPointerPenInfo || !data->videodata->GetPointerPenInfo(pointerid, &pen_info)) {
1358 break; // oh well.
1359 }
1360
1361 const Uint64 timestamp = WIN_GetEventTimestamp();
1362 SDL_Window *window = data->window;
1363
1364 // if lifting off, do it first, so any motion changes don't cause app issues.
1365 if (msg == WM_POINTERUP) {
1366 SDL_SendPenTouch(timestamp, pen, window, (pen_info.penFlags & PEN_FLAG_INVERTED) != 0, false);
1367 }
1368
1369 POINT position;
1370 position.x = (LONG) GET_X_LPARAM(lParam);
1371 position.y = (LONG) GET_Y_LPARAM(lParam);
1372 ScreenToClient(data->hwnd, &position);
1373
1374 SDL_SendPenMotion(timestamp, pen, window, (float) position.x, (float) position.y);
1375 SDL_SendPenButton(timestamp, pen, window, 1, (pen_info.penFlags & PEN_FLAG_BARREL) != 0);
1376 SDL_SendPenButton(timestamp, pen, window, 2, (pen_info.penFlags & PEN_FLAG_ERASER) != 0);
1377
1378 if (pen_info.penMask & PEN_MASK_PRESSURE) {
1379 SDL_SendPenAxis(timestamp, pen, window, SDL_PEN_AXIS_PRESSURE, ((float) pen_info.pressure) / 1024.0f); // pen_info.pressure is in the range 0..1024.
1380 }
1381
1382 if (pen_info.penMask & PEN_MASK_ROTATION) {
1383 SDL_SendPenAxis(timestamp, pen, window, SDL_PEN_AXIS_ROTATION, ((float) pen_info.rotation)); // it's already in the range of 0 to 359.
1384 }
1385
1386 if (pen_info.penMask & PEN_MASK_TILT_X) {
1387 SDL_SendPenAxis(timestamp, pen, window, SDL_PEN_AXIS_XTILT, ((float) pen_info.tiltX)); // it's already in the range of -90 to 90..
1388 }
1389
1390 if (pen_info.penMask & PEN_MASK_TILT_Y) {
1391 SDL_SendPenAxis(timestamp, pen, window, SDL_PEN_AXIS_YTILT, ((float) pen_info.tiltY)); // it's already in the range of -90 to 90..
1392 }
1393
1394 // if setting down, do it last, so the pen is positioned correctly from the first contact.
1395 if (msg == WM_POINTERDOWN) {
1396 SDL_SendPenTouch(timestamp, pen, window, (pen_info.penFlags & PEN_FLAG_INVERTED) != 0, true);
1397 }
1398
1399 returnCode = 0;
1400 } break;
1401
1402 case WM_MOUSEMOVE:
1403 {
1404 SDL_Window *window = data->window;
1405
1406 if (window->flags & SDL_WINDOW_INPUT_FOCUS) {
1407 bool wish_clip_cursor = (
1408 window->flags & (SDL_WINDOW_MOUSE_RELATIVE_MODE | SDL_WINDOW_MOUSE_GRABBED) ||
1409 (window->mouse_rect.w > 0 && window->mouse_rect.h > 0)
1410 );
1411 if (wish_clip_cursor) {
1412 data->skip_update_clipcursor = false;
1413 WIN_UpdateClipCursor(window);
1414 }
1415 }
1416
1417 if (!data->mouse_tracked) {
1418 TRACKMOUSEEVENT trackMouseEvent;
1419
1420 trackMouseEvent.cbSize = sizeof(TRACKMOUSEEVENT);
1421 trackMouseEvent.dwFlags = TME_LEAVE;
1422 trackMouseEvent.hwndTrack = data->hwnd;
1423
1424 if (TrackMouseEvent(&trackMouseEvent)) {
1425 data->mouse_tracked = true;
1426 }
1427
1428 WIN_CheckAsyncMouseRelease(WIN_GetEventTimestamp(), data);
1429 }
1430
1431 if (!data->videodata->raw_mouse_enabled) {
1432 // Only generate mouse events for real mouse
1433 if (GetMouseMessageSource((ULONG)GetMessageExtraInfo()) == SDL_MOUSE_EVENT_SOURCE_MOUSE &&
1434 lParam != data->last_pointer_update) {
1435 SDL_SendMouseMotion(WIN_GetEventTimestamp(), window, SDL_GLOBAL_MOUSE_ID, false, (float)GET_X_LPARAM(lParam), (float)GET_Y_LPARAM(lParam));
1436 }
1437 }
1438 } break;
1439
1440 case WM_LBUTTONUP:
1441 case WM_RBUTTONUP:
1442 case WM_MBUTTONUP:
1443 case WM_XBUTTONUP:
1444 case WM_LBUTTONDOWN:
1445 case WM_LBUTTONDBLCLK:
1446 case WM_RBUTTONDOWN:
1447 case WM_RBUTTONDBLCLK:
1448 case WM_MBUTTONDOWN:
1449 case WM_MBUTTONDBLCLK:
1450 case WM_XBUTTONDOWN:
1451 case WM_XBUTTONDBLCLK:
1452 {
1453 /* SDL_Mouse *mouse = SDL_GetMouse(); */
1454 if (!data->videodata->raw_mouse_enabled) {
1455 if (GetMouseMessageSource((ULONG)GetMessageExtraInfo()) == SDL_MOUSE_EVENT_SOURCE_MOUSE &&
1456 lParam != data->last_pointer_update) {
1457 WIN_CheckWParamMouseButtons(WIN_GetEventTimestamp(), wParam, data, SDL_GLOBAL_MOUSE_ID);
1458 }
1459 }
1460 } break;
1461
1462#if 0 // We handle raw input all at once instead of using a syscall for each mouse event
1463 case WM_INPUT:
1464 {
1465 HRAWINPUT hRawInput = (HRAWINPUT)lParam;
1466 RAWINPUT inp;
1467 UINT size = sizeof(inp);
1468
1469 // Relative mouse motion is delivered to the window with keyboard focus
1470 if (data->window != SDL_GetKeyboardFocus()) {
1471 break;
1472 }
1473
1474 GetRawInputData(hRawInput, RID_INPUT, &inp, &size, sizeof(RAWINPUTHEADER));
1475 if (inp.header.dwType == RIM_TYPEMOUSE) {
1476 WIN_HandleRawMouseInput(WIN_GetEventTimestamp(), data, inp.header.hDevice, &inp.data.mouse);
1477 } else if (inp.header.dwType == RIM_TYPEKEYBOARD) {
1478 WIN_HandleRawKeyboardInput(WIN_GetEventTimestamp(), data, inp.header.hDevice, &inp.data.keyboard);
1479 }
1480 } break;
1481#endif
1482
1483 case WM_MOUSEWHEEL:
1484 case WM_MOUSEHWHEEL:
1485 {
1486 if (!data->videodata->raw_mouse_enabled) {
1487 short amount = GET_WHEEL_DELTA_WPARAM(wParam);
1488 float fAmount = (float)amount / WHEEL_DELTA;
1489 if (msg == WM_MOUSEWHEEL) {
1490 SDL_SendMouseWheel(WIN_GetEventTimestamp(), data->window, SDL_GLOBAL_MOUSE_ID, 0.0f, fAmount, SDL_MOUSEWHEEL_NORMAL);
1491 } else {
1492 SDL_SendMouseWheel(WIN_GetEventTimestamp(), data->window, SDL_GLOBAL_MOUSE_ID, fAmount, 0.0f, SDL_MOUSEWHEEL_NORMAL);
1493 }
1494 }
1495 } break;
1496
1497 case WM_MOUSELEAVE:
1498 if (!(data->window->flags & SDL_WINDOW_MOUSE_CAPTURE)) {
1499 if (SDL_GetMouseFocus() == data->window && !SDL_GetMouse()->relative_mode && !IsIconic(hwnd)) {
1500 SDL_Mouse *mouse;
1501 POINT cursorPos;
1502 GetCursorPos(&cursorPos);
1503 ScreenToClient(hwnd, &cursorPos);
1504 mouse = SDL_GetMouse();
1505 if (!mouse->was_touch_mouse_events) { // we're not a touch handler causing a mouse leave?
1506 SDL_SendMouseMotion(WIN_GetEventTimestamp(), data->window, SDL_GLOBAL_MOUSE_ID, false, (float)cursorPos.x, (float)cursorPos.y);
1507 } else { // touch handling?
1508 mouse->was_touch_mouse_events = false; // not anymore
1509 if (mouse->touch_mouse_events) { // convert touch to mouse events
1510 SDL_SendMouseMotion(WIN_GetEventTimestamp(), data->window, SDL_TOUCH_MOUSEID, false, (float)cursorPos.x, (float)cursorPos.y);
1511 } else { // normal handling
1512 SDL_SendMouseMotion(WIN_GetEventTimestamp(), data->window, SDL_GLOBAL_MOUSE_ID, false, (float)cursorPos.x, (float)cursorPos.y);
1513 }
1514 }
1515 }
1516
1517 if (!SDL_GetMouse()->relative_mode) {
1518 // When WM_MOUSELEAVE is fired we can be assured that the cursor has left the window
1519 SDL_SetMouseFocus(NULL);
1520 }
1521 }
1522
1523 // Once we get WM_MOUSELEAVE we're guaranteed that the window is no longer tracked
1524 data->mouse_tracked = false;
1525
1526 returnCode = 0;
1527 break;
1528#endif // !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
1529
1530 case WM_KEYDOWN:
1531 case WM_SYSKEYDOWN:
1532 {
1533 if (SkipAltGrLeftControl(wParam, lParam)) {
1534 returnCode = 0;
1535 break;
1536 }
1537
1538 bool virtual_key = false;
1539 Uint16 rawcode = 0;
1540 SDL_Scancode code = WindowsScanCodeToSDLScanCode(lParam, wParam, &rawcode, &virtual_key);
1541
1542 // Detect relevant keyboard shortcuts
1543 if (code == SDL_SCANCODE_F4 && (SDL_GetModState() & SDL_KMOD_ALT)) {
1544 // ALT+F4: Close window
1545 if (ShouldGenerateWindowCloseOnAltF4()) {
1546 SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_CLOSE_REQUESTED, 0, 0);
1547 }
1548 }
1549
1550 if (virtual_key || !data->videodata->raw_keyboard_enabled || data->window->text_input_active) {
1551 SDL_SendKeyboardKey(WIN_GetEventTimestamp(), SDL_GLOBAL_KEYBOARD_ID, rawcode, code, true);
1552 }
1553 }
1554
1555 returnCode = 0;
1556 break;
1557
1558 case WM_SYSKEYUP:
1559 case WM_KEYUP:
1560 {
1561 if (SkipAltGrLeftControl(wParam, lParam)) {
1562 returnCode = 0;
1563 break;
1564 }
1565
1566 bool virtual_key = false;
1567 Uint16 rawcode = 0;
1568 SDL_Scancode code = WindowsScanCodeToSDLScanCode(lParam, wParam, &rawcode, &virtual_key);
1569 const bool *keyboardState = SDL_GetKeyboardState(NULL);
1570
1571 if (virtual_key || !data->videodata->raw_keyboard_enabled || data->window->text_input_active) {
1572 if (code == SDL_SCANCODE_PRINTSCREEN && !keyboardState[code]) {
1573 SDL_SendKeyboardKey(WIN_GetEventTimestamp(), SDL_GLOBAL_KEYBOARD_ID, rawcode, code, true);
1574 }
1575 SDL_SendKeyboardKey(WIN_GetEventTimestamp(), SDL_GLOBAL_KEYBOARD_ID, rawcode, code, false);
1576 }
1577 }
1578 returnCode = 0;
1579 break;
1580
1581 case WM_UNICHAR:
1582 if (wParam == UNICODE_NOCHAR) {
1583 returnCode = 1;
1584 } else {
1585 if (SDL_TextInputActive(data->window)) {
1586 char text[5];
1587 char *end = SDL_UCS4ToUTF8((Uint32)wParam, text);
1588 *end = '\0';
1589 SDL_SendKeyboardText(text);
1590 }
1591 returnCode = 0;
1592 }
1593 break;
1594
1595 case WM_CHAR:
1596 if (SDL_TextInputActive(data->window)) {
1597 /* Characters outside Unicode Basic Multilingual Plane (BMP)
1598 * are coded as so called "surrogate pair" in two separate UTF-16 character events.
1599 * Cache high surrogate until next character event. */
1600 if (IS_HIGH_SURROGATE(wParam)) {
1601 data->high_surrogate = (WCHAR)wParam;
1602 } else {
1603 WCHAR utf16[3];
1604
1605 utf16[0] = data->high_surrogate ? data->high_surrogate : (WCHAR)wParam;
1606 utf16[1] = data->high_surrogate ? (WCHAR)wParam : L'\0';
1607 utf16[2] = L'\0';
1608
1609 char utf8[5];
1610 int result = WIN_WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, utf16, -1, utf8, sizeof(utf8), NULL, NULL);
1611 if (result > 0) {
1612 SDL_SendKeyboardText(utf8);
1613 }
1614 data->high_surrogate = L'\0';
1615 }
1616 } else {
1617 data->high_surrogate = L'\0';
1618 }
1619
1620 returnCode = 0;
1621 break;
1622
1623#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
1624#ifdef WM_INPUTLANGCHANGE
1625 case WM_INPUTLANGCHANGE:
1626 {
1627 WIN_UpdateKeymap(true);
1628 }
1629 returnCode = 1;
1630 break;
1631#endif // WM_INPUTLANGCHANGE
1632
1633 case WM_NCLBUTTONDOWN:
1634 {
1635 data->in_title_click = true;
1636
1637 // Fix for 500ms hang after user clicks on the title bar, but before moving mouse
1638 // Reference: https://gamedev.net/forums/topic/672094-keeping-things-moving-during-win32-moveresize-events/5254386/
1639 if (SendMessage(hwnd, WM_NCHITTEST, wParam, lParam) == HTCAPTION) {
1640 POINT cursorPos;
1641 GetCursorPos(&cursorPos);
1642 ScreenToClient(hwnd, &cursorPos);
1643 PostMessage(hwnd, WM_MOUSEMOVE, 0, cursorPos.x | cursorPos.y << 16);
1644 }
1645 } break;
1646
1647 case WM_CAPTURECHANGED:
1648 {
1649 data->in_title_click = false;
1650
1651 // The mouse may have been released during a modal loop
1652 WIN_CheckAsyncMouseRelease(WIN_GetEventTimestamp(), data);
1653 } break;
1654
1655#ifdef WM_GETMINMAXINFO
1656 case WM_GETMINMAXINFO:
1657 {
1658 MINMAXINFO *info;
1659 RECT size;
1660 int x, y;
1661 int w, h;
1662 int min_w, min_h;
1663 int max_w, max_h;
1664 BOOL constrain_max_size;
1665
1666 // If this is an expected size change, allow it
1667 if (data->expected_resize) {
1668 break;
1669 }
1670
1671 // Get the current position of our window
1672 GetWindowRect(hwnd, &size);
1673 x = size.left;
1674 y = size.top;
1675
1676 // Calculate current size of our window
1677 SDL_GetWindowSize(data->window, &w, &h);
1678 SDL_GetWindowMinimumSize(data->window, &min_w, &min_h);
1679 SDL_GetWindowMaximumSize(data->window, &max_w, &max_h);
1680
1681 /* Store in min_w and min_h difference between current size and minimal
1682 size so we don't need to call AdjustWindowRectEx twice */
1683 min_w -= w;
1684 min_h -= h;
1685 if (max_w && max_h) {
1686 max_w -= w;
1687 max_h -= h;
1688 constrain_max_size = TRUE;
1689 } else {
1690 constrain_max_size = FALSE;
1691 }
1692
1693 if (!(SDL_GetWindowFlags(data->window) & SDL_WINDOW_BORDERLESS) && !SDL_WINDOW_IS_POPUP(data->window)) {
1694 size.top = 0;
1695 size.left = 0;
1696 size.bottom = h;
1697 size.right = w;
1698 WIN_AdjustWindowRectForHWND(hwnd, &size, 0);
1699 w = size.right - size.left;
1700 h = size.bottom - size.top;
1701#ifdef HIGHDPI_DEBUG
1702 SDL_Log("WM_GETMINMAXINFO: max window size: %dx%d using dpi: %u", w, h, dpi);
1703#endif
1704 }
1705
1706 // Fix our size to the current size
1707 info = (MINMAXINFO *)lParam;
1708 if (SDL_GetWindowFlags(data->window) & SDL_WINDOW_RESIZABLE) {
1709 if (SDL_GetWindowFlags(data->window) & SDL_WINDOW_BORDERLESS) {
1710 int screenW = GetSystemMetrics(SM_CXSCREEN);
1711 int screenH = GetSystemMetrics(SM_CYSCREEN);
1712 info->ptMaxSize.x = SDL_max(w, screenW);
1713 info->ptMaxSize.y = SDL_max(h, screenH);
1714 info->ptMaxPosition.x = SDL_min(0, ((screenW - w) / 2));
1715 info->ptMaxPosition.y = SDL_min(0, ((screenH - h) / 2));
1716 }
1717 info->ptMinTrackSize.x = (LONG)w + min_w;
1718 info->ptMinTrackSize.y = (LONG)h + min_h;
1719 if (constrain_max_size) {
1720 info->ptMaxTrackSize.x = (LONG)w + max_w;
1721 info->ptMaxTrackSize.y = (LONG)h + max_h;
1722 }
1723 } else {
1724 info->ptMaxSize.x = w;
1725 info->ptMaxSize.y = h;
1726 info->ptMaxPosition.x = x;
1727 info->ptMaxPosition.y = y;
1728 info->ptMinTrackSize.x = w;
1729 info->ptMinTrackSize.y = h;
1730 info->ptMaxTrackSize.x = w;
1731 info->ptMaxTrackSize.y = h;
1732 }
1733 }
1734 returnCode = 0;
1735 break;
1736#endif // WM_GETMINMAXINFO
1737
1738 case WM_WINDOWPOSCHANGING:
1739
1740 if (data->expected_resize) {
1741 returnCode = 0;
1742 }
1743 break;
1744
1745 case WM_WINDOWPOSCHANGED:
1746 {
1747 SDL_Window *win;
1748 const SDL_DisplayID original_displayID = data->last_displayID;
1749 const WINDOWPOS *windowpos = (WINDOWPOS *)lParam;
1750 const bool iconic = IsIconic(hwnd);
1751 const bool zoomed = IsZoomed(hwnd);
1752 RECT rect;
1753 int x, y;
1754 int w, h;
1755
1756 if (windowpos->flags & SWP_SHOWWINDOW) {
1757 SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_SHOWN, 0, 0);
1758 }
1759
1760 if (iconic) {
1761 SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_MINIMIZED, 0, 0);
1762 } else if (zoomed) {
1763 if (data->window->flags & SDL_WINDOW_MINIMIZED) {
1764 // If going from minimized to maximized, send the restored event first.
1765 SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_RESTORED, 0, 0);
1766 }
1767 SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_MAXIMIZED, 0, 0);
1768 data->force_ws_maximizebox = true;
1769 } else if (data->window->flags & (SDL_WINDOW_MAXIMIZED | SDL_WINDOW_MINIMIZED)) {
1770 SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_RESTORED, 0, 0);
1771
1772 /* If resizable was forced on for the maximized window, clear the style flags now,
1773 * but not if the window is fullscreen, as this needs to be preserved in that case.
1774 */
1775 if (!(data->window->flags & SDL_WINDOW_FULLSCREEN)) {
1776 data->force_ws_maximizebox = false;
1777 WIN_SetWindowResizable(SDL_GetVideoDevice(), data->window, !!(data->window->flags & SDL_WINDOW_RESIZABLE));
1778 }
1779 }
1780
1781 if (windowpos->flags & SWP_HIDEWINDOW) {
1782 SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_HIDDEN, 0, 0);
1783 }
1784
1785 // When the window is minimized it's resized to the dock icon size, ignore this
1786 if (iconic) {
1787 break;
1788 }
1789
1790 if (data->initializing) {
1791 break;
1792 }
1793
1794 if (!data->disable_move_size_events) {
1795 if (GetClientRect(hwnd, &rect) && !WIN_IsRectEmpty(&rect)) {
1796 ClientToScreen(hwnd, (LPPOINT) &rect);
1797 ClientToScreen(hwnd, (LPPOINT) &rect + 1);
1798
1799 x = rect.left;
1800 y = rect.top;
1801
1802 SDL_GlobalToRelativeForWindow(data->window, x, y, &x, &y);
1803 SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_MOVED, x, y);
1804 }
1805
1806 // 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
1807 if (GetClientRect(hwnd, &rect) && !WIN_IsRectEmpty(&rect)) {
1808 w = rect.right;
1809 h = rect.bottom;
1810
1811 SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_RESIZED, w, h);
1812 }
1813 }
1814
1815 WIN_UpdateClipCursor(data->window);
1816
1817 // Update the window display position
1818 data->last_displayID = SDL_GetDisplayForWindow(data->window);
1819
1820 if (data->last_displayID != original_displayID) {
1821 // Display changed, check ICC profile
1822 WIN_UpdateWindowICCProfile(data->window, true);
1823 }
1824
1825 // Update the position of any child windows
1826 for (win = data->window->first_child; win; win = win->next_sibling) {
1827 // Don't update hidden child popup windows, their relative position doesn't change
1828 if (SDL_WINDOW_IS_POPUP(win) && !(win->flags & SDL_WINDOW_HIDDEN)) {
1829 WIN_SetWindowPositionInternal(win, SWP_NOCOPYBITS | SWP_NOACTIVATE, SDL_WINDOWRECT_CURRENT);
1830 }
1831 }
1832
1833 // Forces a WM_PAINT event
1834 InvalidateRect(hwnd, NULL, FALSE);
1835
1836 } break;
1837
1838 case WM_ENTERSIZEMOVE:
1839 case WM_ENTERMENULOOP:
1840 {
1841 if (g_WindowsMessageHook) {
1842 if (!DispatchModalLoopMessageHook(&hwnd, &msg, &wParam, &lParam)) {
1843 return 0;
1844 }
1845 }
1846
1847 ++data->in_modal_loop;
1848 if (data->in_modal_loop == 1) {
1849 data->initial_size_rect.left = data->window->x;
1850 data->initial_size_rect.right = data->window->x + data->window->w;
1851 data->initial_size_rect.top = data->window->y;
1852 data->initial_size_rect.bottom = data->window->y + data->window->h;
1853
1854 SetTimer(hwnd, (UINT_PTR)SDL_IterateMainCallbacks, USER_TIMER_MINIMUM, NULL);
1855 }
1856 } break;
1857
1858 case WM_TIMER:
1859 {
1860 if (wParam == (UINT_PTR)SDL_IterateMainCallbacks) {
1861 SDL_OnWindowLiveResizeUpdate(data->window);
1862 return 0;
1863 }
1864 } break;
1865
1866 case WM_EXITSIZEMOVE:
1867 case WM_EXITMENULOOP:
1868 {
1869 --data->in_modal_loop;
1870 if (data->in_modal_loop == 0) {
1871 KillTimer(hwnd, (UINT_PTR)SDL_IterateMainCallbacks);
1872 }
1873 } break;
1874
1875 case WM_SIZING:
1876 {
1877 WPARAM edge = wParam;
1878 RECT* dragRect = (RECT*)lParam;
1879 RECT clientDragRect = *dragRect;
1880 bool lock_aspect_ratio = (data->window->max_aspect == data->window->min_aspect) ? true : false;
1881 RECT rc;
1882 LONG w, h;
1883 float new_aspect;
1884
1885 // if aspect ratio constraints are not enabled then skip this message
1886 if (data->window->min_aspect <= 0 && data->window->max_aspect <= 0) {
1887 break;
1888 }
1889
1890 // unadjust the dragRect from the window rect to the client rect
1891 SetRectEmpty(&rc);
1892 if (!AdjustWindowRectEx(&rc, GetWindowStyle(hwnd), GetMenu(hwnd) != NULL, GetWindowExStyle(hwnd))) {
1893 break;
1894 }
1895
1896 clientDragRect.left -= rc.left;
1897 clientDragRect.top -= rc.top;
1898 clientDragRect.right -= rc.right;
1899 clientDragRect.bottom -= rc.bottom;
1900
1901 w = clientDragRect.right - clientDragRect.left;
1902 h = clientDragRect.bottom - clientDragRect.top;
1903 new_aspect = w / (float)h;
1904
1905 // handle the special case in which the min ar and max ar are the same so the window can size symmetrically
1906 if (lock_aspect_ratio) {
1907 switch (edge) {
1908 case WMSZ_LEFT:
1909 case WMSZ_RIGHT:
1910 h = (int)SDL_roundf(w / data->window->max_aspect);
1911 break;
1912 default:
1913 // resizing via corners or top or bottom
1914 w = (int)SDL_roundf(h * data->window->max_aspect);
1915 break;
1916 }
1917 } else {
1918 switch (edge) {
1919 case WMSZ_LEFT:
1920 case WMSZ_RIGHT:
1921 if (data->window->max_aspect > 0.0f && new_aspect > data->window->max_aspect) {
1922 w = (int)SDL_roundf(h * data->window->max_aspect);
1923 } else if (data->window->min_aspect > 0.0f && new_aspect < data->window->min_aspect) {
1924 w = (int)SDL_roundf(h * data->window->min_aspect);
1925 }
1926 break;
1927 case WMSZ_TOP:
1928 case WMSZ_BOTTOM:
1929 if (data->window->min_aspect > 0.0f && new_aspect < data->window->min_aspect) {
1930 h = (int)SDL_roundf(w / data->window->min_aspect);
1931 } else if (data->window->max_aspect > 0.0f && new_aspect > data->window->max_aspect) {
1932 h = (int)SDL_roundf(w / data->window->max_aspect);
1933 }
1934 break;
1935
1936 default:
1937 // resizing via corners
1938 if (data->window->max_aspect > 0.0f && new_aspect > data->window->max_aspect) {
1939 w = (int)SDL_roundf(h * data->window->max_aspect);
1940 } else if (data->window->min_aspect > 0.0f && new_aspect < data->window->min_aspect) {
1941 h = (int)SDL_roundf(w / data->window->min_aspect);
1942 }
1943 break;
1944 }
1945 }
1946
1947 switch (edge) {
1948 case WMSZ_LEFT:
1949 clientDragRect.left = clientDragRect.right - w;
1950 if (lock_aspect_ratio) {
1951 clientDragRect.top = (data->initial_size_rect.bottom + data->initial_size_rect.top - h) / 2;
1952 }
1953 clientDragRect.bottom = h + clientDragRect.top;
1954 break;
1955 case WMSZ_BOTTOMLEFT:
1956 clientDragRect.left = clientDragRect.right - w;
1957 clientDragRect.bottom = h + clientDragRect.top;
1958 break;
1959 case WMSZ_RIGHT:
1960 clientDragRect.right = w + clientDragRect.left;
1961 if (lock_aspect_ratio) {
1962 clientDragRect.top = (data->initial_size_rect.bottom + data->initial_size_rect.top - h) / 2;
1963 }
1964 clientDragRect.bottom = h + clientDragRect.top;
1965 break;
1966 case WMSZ_TOPRIGHT:
1967 clientDragRect.right = w + clientDragRect.left;
1968 clientDragRect.top = clientDragRect.bottom - h;
1969 break;
1970 case WMSZ_TOP:
1971 if (lock_aspect_ratio) {
1972 clientDragRect.left = (data->initial_size_rect.right + data->initial_size_rect.left - w) / 2;
1973 }
1974 clientDragRect.right = w + clientDragRect.left;
1975 clientDragRect.top = clientDragRect.bottom - h;
1976 break;
1977 case WMSZ_TOPLEFT:
1978 clientDragRect.left = clientDragRect.right - w;
1979 clientDragRect.top = clientDragRect.bottom - h;
1980 break;
1981 case WMSZ_BOTTOM:
1982 if (lock_aspect_ratio) {
1983 clientDragRect.left = (data->initial_size_rect.right + data->initial_size_rect.left - w) / 2;
1984 }
1985 clientDragRect.right = w + clientDragRect.left;
1986 clientDragRect.bottom = h + clientDragRect.top;
1987 break;
1988 case WMSZ_BOTTOMRIGHT:
1989 clientDragRect.right = w + clientDragRect.left;
1990 clientDragRect.bottom = h + clientDragRect.top;
1991 break;
1992 }
1993
1994 // convert the client rect to a window rect
1995 if (!AdjustWindowRectEx(&clientDragRect, GetWindowStyle(hwnd), GetMenu(hwnd) != NULL, GetWindowExStyle(hwnd))) {
1996 break;
1997 }
1998
1999 *dragRect = clientDragRect;
2000 }
2001 break;
2002
2003 case WM_SETCURSOR:
2004 {
2005 Uint16 hittest;
2006
2007 hittest = LOWORD(lParam);
2008 if (hittest == HTCLIENT) {
2009 SetCursor(SDL_cursor);
2010 returnCode = TRUE;
2011 } else if (!g_WindowFrameUsableWhileCursorHidden && !SDL_cursor) {
2012 SetCursor(NULL);
2013 returnCode = TRUE;
2014 }
2015 } break;
2016
2017 // We were occluded, refresh our display
2018 case WM_PAINT:
2019 {
2020 RECT rect;
2021 if (GetUpdateRect(hwnd, &rect, FALSE)) {
2022 const LONG style = GetWindowLong(hwnd, GWL_EXSTYLE);
2023
2024 /* Composited windows will continue to receive WM_PAINT messages for update
2025 regions until the window is actually painted through Begin/EndPaint */
2026 if (style & WS_EX_COMPOSITED) {
2027 PAINTSTRUCT ps;
2028 BeginPaint(hwnd, &ps);
2029 EndPaint(hwnd, &ps);
2030 }
2031
2032 ValidateRect(hwnd, NULL);
2033 SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_EXPOSED, 0, 0);
2034 }
2035 }
2036 returnCode = 0;
2037 break;
2038
2039 // We'll do our own drawing, prevent flicker
2040 case WM_ERASEBKGND:
2041 if (ShouldClearWindowOnEraseBackground(data)) {
2042 RECT client_rect;
2043 HBRUSH brush;
2044 data->videodata->cleared = true;
2045 GetClientRect(hwnd, &client_rect);
2046 brush = CreateSolidBrush(0);
2047 FillRect(GetDC(hwnd), &client_rect, brush);
2048 DeleteObject(brush);
2049 }
2050 return 1;
2051
2052 case WM_SYSCOMMAND:
2053 {
2054 if (!g_WindowsEnableMenuMnemonics) {
2055 if ((wParam & 0xFFF0) == SC_KEYMENU) {
2056 return 0;
2057 }
2058 }
2059
2060#if defined(SC_SCREENSAVE) || defined(SC_MONITORPOWER)
2061 // Don't start the screensaver or blank the monitor in fullscreen apps
2062 if ((wParam & 0xFFF0) == SC_SCREENSAVE ||
2063 (wParam & 0xFFF0) == SC_MONITORPOWER) {
2064 if (SDL_GetVideoDevice()->suspend_screensaver) {
2065 return 0;
2066 }
2067 }
2068#endif // System has screensaver support
2069 } break;
2070
2071 case WM_CLOSE:
2072 {
2073 SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_CLOSE_REQUESTED, 0, 0);
2074 }
2075 returnCode = 0;
2076 break;
2077
2078 case WM_TOUCH:
2079 if (data->videodata->GetTouchInputInfo && data->videodata->CloseTouchInputHandle) {
2080 UINT i, num_inputs = LOWORD(wParam);
2081 bool isstack;
2082 PTOUCHINPUT inputs = SDL_small_alloc(TOUCHINPUT, num_inputs, &isstack);
2083 if (inputs && data->videodata->GetTouchInputInfo((HTOUCHINPUT)lParam, num_inputs, inputs, sizeof(TOUCHINPUT))) {
2084 RECT rect;
2085 float x, y;
2086
2087 if (!GetClientRect(hwnd, &rect) || WIN_IsRectEmpty(&rect)) {
2088 if (inputs) {
2089 SDL_small_free(inputs, isstack);
2090 }
2091 break;
2092 }
2093 ClientToScreen(hwnd, (LPPOINT)&rect);
2094 ClientToScreen(hwnd, (LPPOINT)&rect + 1);
2095 rect.top *= 100;
2096 rect.left *= 100;
2097 rect.bottom *= 100;
2098 rect.right *= 100;
2099
2100 for (i = 0; i < num_inputs; ++i) {
2101 PTOUCHINPUT input = &inputs[i];
2102 const int w = (rect.right - rect.left);
2103 const int h = (rect.bottom - rect.top);
2104
2105 const SDL_TouchID touchId = (SDL_TouchID)((uintptr_t)input->hSource);
2106 const SDL_FingerID fingerId = (input->dwID + 1);
2107
2108 /* TODO: Can we use GetRawInputDeviceInfo and HID info to
2109 determine if this is a direct or indirect touch device?
2110 */
2111 if (SDL_AddTouch(touchId, SDL_TOUCH_DEVICE_DIRECT, (input->dwFlags & TOUCHEVENTF_PEN) == TOUCHEVENTF_PEN ? "pen" : "touch") < 0) {
2112 continue;
2113 }
2114
2115 // Get the normalized coordinates for the window
2116 if (w <= 1) {
2117 x = 0.5f;
2118 } else {
2119 x = (float)(input->x - rect.left) / (w - 1);
2120 }
2121 if (h <= 1) {
2122 y = 0.5f;
2123 } else {
2124 y = (float)(input->y - rect.top) / (h - 1);
2125 }
2126
2127 // FIXME: Should we use the input->dwTime field for the tick source of the timestamp?
2128 if (input->dwFlags & TOUCHEVENTF_DOWN) {
2129 SDL_SendTouch(WIN_GetEventTimestamp(), touchId, fingerId, data->window, SDL_EVENT_FINGER_DOWN, x, y, 1.0f);
2130 }
2131 if (input->dwFlags & TOUCHEVENTF_MOVE) {
2132 SDL_SendTouchMotion(WIN_GetEventTimestamp(), touchId, fingerId, data->window, x, y, 1.0f);
2133 }
2134 if (input->dwFlags & TOUCHEVENTF_UP) {
2135 SDL_SendTouch(WIN_GetEventTimestamp(), touchId, fingerId, data->window, SDL_EVENT_FINGER_UP, x, y, 1.0f);
2136 }
2137 }
2138 }
2139 SDL_small_free(inputs, isstack);
2140
2141 data->videodata->CloseTouchInputHandle((HTOUCHINPUT)lParam);
2142 return 0;
2143 }
2144 break;
2145
2146#ifdef HAVE_TPCSHRD_H
2147
2148 case WM_TABLET_QUERYSYSTEMGESTURESTATUS:
2149 /* See https://msdn.microsoft.com/en-us/library/windows/desktop/bb969148(v=vs.85).aspx .
2150 * If we're handling our own touches, we don't want any gestures.
2151 * Not all of these settings are documented.
2152 * The use of the undocumented ones was suggested by https://github.com/bjarkeck/GCGJ/blob/master/Monogame/Windows/WinFormsGameForm.cs . */
2153 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
2154 // disables UI feedback on pen up (waves)
2155 // disables UI feedback on pen button down (circle)
2156 // disables pen flicks (back, forward, drag down, drag up)
2157
2158#endif // HAVE_TPCSHRD_H
2159
2160 case WM_DROPFILES:
2161 {
2162 UINT i;
2163 HDROP drop = (HDROP)wParam;
2164 UINT count = DragQueryFile(drop, 0xFFFFFFFF, NULL, 0);
2165 for (i = 0; i < count; ++i) {
2166 UINT size = DragQueryFile(drop, i, NULL, 0) + 1;
2167 LPTSTR buffer = (LPTSTR)SDL_malloc(sizeof(TCHAR) * size);
2168 if (buffer) {
2169 if (DragQueryFile(drop, i, buffer, size)) {
2170 char *file = WIN_StringToUTF8(buffer);
2171 SDL_SendDropFile(data->window, NULL, file);
2172 SDL_free(file);
2173 }
2174 SDL_free(buffer);
2175 }
2176 }
2177 SDL_SendDropComplete(data->window);
2178 DragFinish(drop);
2179 return 0;
2180 } break;
2181
2182 case WM_DISPLAYCHANGE:
2183 {
2184 // Reacquire displays if any were added or removed
2185 WIN_RefreshDisplays(SDL_GetVideoDevice());
2186 } break;
2187
2188 case WM_NCCALCSIZE:
2189 {
2190 SDL_WindowFlags window_flags = SDL_GetWindowFlags(data->window);
2191 if (wParam == TRUE && (window_flags & SDL_WINDOW_BORDERLESS) && !(window_flags & SDL_WINDOW_FULLSCREEN)) {
2192 // When borderless, need to tell windows that the size of the non-client area is 0
2193 NCCALCSIZE_PARAMS *params = (NCCALCSIZE_PARAMS *)lParam;
2194 WINDOWPLACEMENT placement;
2195 if (GetWindowPlacement(hwnd, &placement) && placement.showCmd == SW_MAXIMIZE) {
2196 // Maximized borderless windows should use the monitor work area.
2197 HMONITOR hMonitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONULL);
2198 if (!hMonitor) {
2199 // The returned monitor can be null when restoring from minimized, so use the last coordinates.
2200 const POINT pt = { data->window->windowed.x, data->window->windowed.y };
2201 hMonitor = MonitorFromPoint(pt, MONITOR_DEFAULTTONEAREST);
2202 }
2203 if (hMonitor) {
2204 MONITORINFO info;
2205 SDL_zero(info);
2206 info.cbSize = sizeof(info);
2207 if (GetMonitorInfo(hMonitor, &info)) {
2208 params->rgrc[0] = info.rcWork;
2209 }
2210 }
2211 } else if (!(window_flags & SDL_WINDOW_RESIZABLE) && !data->force_ws_maximizebox) {
2212 int w, h;
2213 if (data->window->last_size_pending) {
2214 w = data->window->pending.w;
2215 h = data->window->pending.h;
2216 } else {
2217 w = data->window->floating.w;
2218 h = data->window->floating.h;
2219 }
2220 params->rgrc[0].right = params->rgrc[0].left + w;
2221 params->rgrc[0].bottom = params->rgrc[0].top + h;
2222 }
2223 return 0;
2224 }
2225 } break;
2226
2227 case WM_NCHITTEST:
2228 {
2229 SDL_Window *window = data->window;
2230
2231 if (window->flags & SDL_WINDOW_TOOLTIP) {
2232 return HTTRANSPARENT;
2233 }
2234
2235 if (window->hit_test) {
2236 POINT winpoint;
2237 winpoint.x = GET_X_LPARAM(lParam);
2238 winpoint.y = GET_Y_LPARAM(lParam);
2239 if (ScreenToClient(hwnd, &winpoint)) {
2240 SDL_Point point;
2241 SDL_HitTestResult rc;
2242 point.x = winpoint.x;
2243 point.y = winpoint.y;
2244 rc = window->hit_test(window, &point, window->hit_test_data);
2245 switch (rc) {
2246#define POST_HIT_TEST(ret) \
2247 { \
2248 SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_HIT_TEST, 0, 0); \
2249 return ret; \
2250 }
2251 case SDL_HITTEST_DRAGGABLE:
2252 {
2253 /* If the mouse button state is something other than none or left button down,
2254 * return HTCLIENT, or Windows will eat the button press.
2255 */
2256 SDL_MouseButtonFlags buttonState = SDL_GetGlobalMouseState(NULL, NULL);
2257 if (buttonState && !(buttonState & SDL_BUTTON_LMASK)) {
2258 // Set focus in case it was lost while previously moving over a draggable area.
2259 SDL_SetMouseFocus(window);
2260 return HTCLIENT;
2261 }
2262
2263 POST_HIT_TEST(HTCAPTION);
2264 }
2265 case SDL_HITTEST_RESIZE_TOPLEFT:
2266 POST_HIT_TEST(HTTOPLEFT);
2267 case SDL_HITTEST_RESIZE_TOP:
2268 POST_HIT_TEST(HTTOP);
2269 case SDL_HITTEST_RESIZE_TOPRIGHT:
2270 POST_HIT_TEST(HTTOPRIGHT);
2271 case SDL_HITTEST_RESIZE_RIGHT:
2272 POST_HIT_TEST(HTRIGHT);
2273 case SDL_HITTEST_RESIZE_BOTTOMRIGHT:
2274 POST_HIT_TEST(HTBOTTOMRIGHT);
2275 case SDL_HITTEST_RESIZE_BOTTOM:
2276 POST_HIT_TEST(HTBOTTOM);
2277 case SDL_HITTEST_RESIZE_BOTTOMLEFT:
2278 POST_HIT_TEST(HTBOTTOMLEFT);
2279 case SDL_HITTEST_RESIZE_LEFT:
2280 POST_HIT_TEST(HTLEFT);
2281#undef POST_HIT_TEST
2282 case SDL_HITTEST_NORMAL:
2283 return HTCLIENT;
2284 }
2285 }
2286 // If we didn't return, this will call DefWindowProc below.
2287 }
2288 } break;
2289
2290 case WM_GETDPISCALEDSIZE:
2291 // Windows 10 Creators Update+
2292 /* Documented as only being sent to windows that are per-monitor V2 DPI aware.
2293
2294 Experimentation shows it's only sent during interactive dragging, not in response to
2295 SetWindowPos. */
2296 if (data->videodata->GetDpiForWindow && data->videodata->AdjustWindowRectExForDpi) {
2297 /* Windows expects applications to scale their window rects linearly
2298 when dragging between monitors with different DPI's.
2299 e.g. a 100x100 window dragged to a 200% scaled monitor
2300 becomes 200x200.
2301
2302 For SDL, we instead want the client size to scale linearly.
2303 This is not the same as the window rect scaling linearly,
2304 because Windows doesn't scale the non-client area (titlebar etc.)
2305 linearly. So, we need to handle this message to request custom
2306 scaling. */
2307
2308 const int nextDPI = (int)wParam;
2309 const int prevDPI = (int)data->videodata->GetDpiForWindow(hwnd);
2310 SIZE *sizeInOut = (SIZE *)lParam;
2311
2312 int frame_w, frame_h;
2313 int query_client_w_win, query_client_h_win;
2314
2315#ifdef HIGHDPI_DEBUG
2316 SDL_Log("WM_GETDPISCALEDSIZE: current DPI: %d potential DPI: %d input size: (%dx%d)",
2317 prevDPI, nextDPI, sizeInOut->cx, sizeInOut->cy);
2318#endif
2319
2320 // Subtract the window frame size that would have been used at prevDPI
2321 {
2322 RECT rect = { 0 };
2323
2324 if (!(data->window->flags & SDL_WINDOW_BORDERLESS) && !SDL_WINDOW_IS_POPUP(data->window)) {
2325 WIN_AdjustWindowRectForHWND(hwnd, &rect, prevDPI);
2326 }
2327
2328 frame_w = -rect.left + rect.right;
2329 frame_h = -rect.top + rect.bottom;
2330
2331 query_client_w_win = sizeInOut->cx - frame_w;
2332 query_client_h_win = sizeInOut->cy - frame_h;
2333 }
2334
2335 // Add the window frame size that would be used at nextDPI
2336 {
2337 RECT rect = { 0 };
2338 rect.right = query_client_w_win;
2339 rect.bottom = query_client_h_win;
2340
2341 if (!(data->window->flags & SDL_WINDOW_BORDERLESS) && !SDL_WINDOW_IS_POPUP(data->window)) {
2342 WIN_AdjustWindowRectForHWND(hwnd, &rect, nextDPI);
2343 }
2344
2345 // This is supposed to control the suggested rect param of WM_DPICHANGED
2346 sizeInOut->cx = rect.right - rect.left;
2347 sizeInOut->cy = rect.bottom - rect.top;
2348 }
2349
2350#ifdef HIGHDPI_DEBUG
2351 SDL_Log("WM_GETDPISCALEDSIZE: output size: (%dx%d)", sizeInOut->cx, sizeInOut->cy);
2352#endif
2353 return TRUE;
2354 }
2355 break;
2356
2357 case WM_DPICHANGED:
2358 // Windows 8.1+
2359 {
2360 const int newDPI = HIWORD(wParam);
2361 RECT *const suggestedRect = (RECT *)lParam;
2362 int w, h;
2363
2364#ifdef HIGHDPI_DEBUG
2365 SDL_Log("WM_DPICHANGED: to %d\tsuggested rect: (%d, %d), (%dx%d)", newDPI,
2366 suggestedRect->left, suggestedRect->top, suggestedRect->right - suggestedRect->left, suggestedRect->bottom - suggestedRect->top);
2367#endif
2368
2369 if (data->expected_resize) {
2370 /* This DPI change is coming from an explicit SetWindowPos call within SDL.
2371 Assume all call sites are calculating the DPI-aware frame correctly, so
2372 we don't need to do any further adjustment. */
2373#ifdef HIGHDPI_DEBUG
2374 SDL_Log("WM_DPICHANGED: Doing nothing, assuming window is already sized correctly");
2375#endif
2376 return 0;
2377 }
2378
2379 // Interactive user-initiated resizing/movement
2380 {
2381 /* Calculate the new frame w/h such that
2382 the client area size is maintained. */
2383 RECT rect = { 0 };
2384 rect.right = data->window->w;
2385 rect.bottom = data->window->h;
2386
2387 if (!(data->window->flags & SDL_WINDOW_BORDERLESS)) {
2388 WIN_AdjustWindowRectForHWND(hwnd, &rect, newDPI);
2389 }
2390
2391 w = rect.right - rect.left;
2392 h = rect.bottom - rect.top;
2393 }
2394
2395#ifdef HIGHDPI_DEBUG
2396 SDL_Log("WM_DPICHANGED: current SDL window size: (%dx%d)\tcalling SetWindowPos: (%d, %d), (%dx%d)",
2397 data->window->w, data->window->h,
2398 suggestedRect->left, suggestedRect->top, w, h);
2399#endif
2400
2401 data->expected_resize = true;
2402 SetWindowPos(hwnd,
2403 NULL,
2404 suggestedRect->left,
2405 suggestedRect->top,
2406 w,
2407 h,
2408 SWP_NOZORDER | SWP_NOACTIVATE);
2409 data->expected_resize = false;
2410 return 0;
2411 }
2412 break;
2413
2414 case WM_SETTINGCHANGE:
2415 if (wParam == 0 && lParam != 0 && SDL_wcscmp((wchar_t *)lParam, L"ImmersiveColorSet") == 0) {
2416 SDL_SetSystemTheme(WIN_GetSystemTheme());
2417 WIN_UpdateDarkModeForHWND(hwnd);
2418 }
2419 if (wParam == SPI_SETMOUSE || wParam == SPI_SETMOUSESPEED) {
2420 WIN_UpdateMouseSystemScale();
2421 }
2422 break;
2423
2424#endif // !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
2425 }
2426
2427 // If there's a window proc, assume it's going to handle messages
2428 if (data->wndproc) {
2429 return CallWindowProc(data->wndproc, hwnd, msg, wParam, lParam);
2430 } else if (returnCode >= 0) {
2431 return returnCode;
2432 } else {
2433 return CallWindowProc(DefWindowProc, hwnd, msg, wParam, lParam);
2434 }
2435}
2436
2437int WIN_WaitEventTimeout(SDL_VideoDevice *_this, Sint64 timeoutNS)
2438{
2439 if (g_WindowsEnableMessageLoop) {
2440#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
2441 DWORD timeout, ret;
2442 timeout = timeoutNS < 0 ? INFINITE : (DWORD)SDL_NS_TO_MS(timeoutNS);
2443 ret = MsgWaitForMultipleObjects(0, NULL, FALSE, timeout, QS_ALLINPUT);
2444 if (ret == WAIT_OBJECT_0) {
2445 return 1;
2446 } else {
2447 return 0;
2448 }
2449#else
2450 // MsgWaitForMultipleObjects is desktop-only.
2451 MSG msg;
2452 BOOL message_result;
2453 UINT_PTR timer_id = 0;
2454 if (timeoutNS > 0) {
2455 timer_id = SetTimer(NULL, 0, (UINT)SDL_NS_TO_MS(timeoutNS), NULL);
2456 message_result = GetMessage(&msg, 0, 0, 0);
2457 KillTimer(NULL, timer_id);
2458 } else if (timeoutNS == 0) {
2459 message_result = PeekMessage(&msg, NULL, 0, 0, PM_REMOVE);
2460 } else {
2461 message_result = GetMessage(&msg, 0, 0, 0);
2462 }
2463 if (message_result) {
2464 if (msg.message == WM_TIMER && !msg.hwnd && msg.wParam == timer_id) {
2465 return 0;
2466 }
2467 if (g_WindowsMessageHook) {
2468 if (!g_WindowsMessageHook(g_WindowsMessageHookData, &msg)) {
2469 return 1;
2470 }
2471 }
2472 // Always translate the message in case it's a non-SDL window (e.g. with Qt integration)
2473 TranslateMessage(&msg);
2474 DispatchMessage(&msg);
2475 return 1;
2476 } else {
2477 return 0;
2478 }
2479#endif // !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
2480 } else {
2481 // Fail the wait so the caller falls back to polling
2482 return -1;
2483 }
2484}
2485
2486void WIN_SendWakeupEvent(SDL_VideoDevice *_this, SDL_Window *window)
2487{
2488 SDL_WindowData *data = window->internal;
2489 PostMessage(data->hwnd, data->videodata->_SDL_WAKEUP, 0, 0);
2490}
2491
2492void WIN_PumpEvents(SDL_VideoDevice *_this)
2493{
2494 MSG msg;
2495#ifdef _MSC_VER // We explicitly want to use GetTickCount(), not GetTickCount64()
2496#pragma warning(push)
2497#pragma warning(disable : 28159)
2498#endif
2499 DWORD end_ticks = GetTickCount() + 1;
2500#ifdef _MSC_VER
2501#pragma warning(pop)
2502#endif
2503 int new_messages = 0;
2504#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
2505 const bool *keystate;
2506 SDL_Window *focusWindow;
2507#endif
2508
2509 if (_this->internal->gameinput_context) {
2510 WIN_UpdateGameInput(_this);
2511 }
2512
2513 if (g_WindowsEnableMessageLoop) {
2514 SDL_processing_messages = true;
2515
2516 while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
2517 if (g_WindowsMessageHook) {
2518 if (!g_WindowsMessageHook(g_WindowsMessageHookData, &msg)) {
2519 continue;
2520 }
2521 }
2522
2523#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
2524 // Don't dispatch any mouse motion queued prior to or including the last mouse warp
2525 if (msg.message == WM_MOUSEMOVE && SDL_last_warp_time) {
2526 if (!SDL_TICKS_PASSED(msg.time, (SDL_last_warp_time + 1))) {
2527 continue;
2528 }
2529
2530 // This mouse message happened after the warp
2531 SDL_last_warp_time = 0;
2532 }
2533#endif // !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
2534
2535 WIN_SetMessageTick(msg.time);
2536
2537 // Always translate the message in case it's a non-SDL window (e.g. with Qt integration)
2538 TranslateMessage(&msg);
2539 DispatchMessage(&msg);
2540
2541 // Make sure we don't busy loop here forever if there are lots of events coming in
2542 if (SDL_TICKS_PASSED(msg.time, end_ticks)) {
2543 /* We might get a few new messages generated by the Steam overlay or other application hooks
2544 In this case those messages will be processed before any pending input, so we want to continue after those messages.
2545 (thanks to Peter Deayton for his investigation here)
2546 */
2547 const int MAX_NEW_MESSAGES = 3;
2548 ++new_messages;
2549 if (new_messages > MAX_NEW_MESSAGES) {
2550 break;
2551 }
2552 }
2553 }
2554
2555 SDL_processing_messages = false;
2556 }
2557
2558#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
2559 /* Windows loses a shift KEYUP event when you have both pressed at once and let go of one.
2560 You won't get a KEYUP until both are released, and that keyup will only be for the second
2561 key you released. Take heroic measures and check the keystate as of the last handled event,
2562 and if we think a key is pressed when Windows doesn't, unstick it in SDL's state. */
2563 keystate = SDL_GetKeyboardState(NULL);
2564 if (keystate[SDL_SCANCODE_LSHIFT] && !(GetKeyState(VK_LSHIFT) & 0x8000)) {
2565 SDL_SendKeyboardKey(0, SDL_GLOBAL_KEYBOARD_ID, 0, SDL_SCANCODE_LSHIFT, false);
2566 }
2567 if (keystate[SDL_SCANCODE_RSHIFT] && !(GetKeyState(VK_RSHIFT) & 0x8000)) {
2568 SDL_SendKeyboardKey(0, SDL_GLOBAL_KEYBOARD_ID, 0, SDL_SCANCODE_RSHIFT, false);
2569 }
2570
2571 /* The Windows key state gets lost when using Windows+Space or Windows+G shortcuts and
2572 not grabbing the keyboard. Note: If we *are* grabbing the keyboard, GetKeyState()
2573 will return inaccurate results for VK_LWIN and VK_RWIN but we don't need it anyway. */
2574 focusWindow = SDL_GetKeyboardFocus();
2575 if (!focusWindow || !(focusWindow->flags & SDL_WINDOW_KEYBOARD_GRABBED)) {
2576 if (keystate[SDL_SCANCODE_LGUI] && !(GetKeyState(VK_LWIN) & 0x8000)) {
2577 SDL_SendKeyboardKey(0, SDL_GLOBAL_KEYBOARD_ID, 0, SDL_SCANCODE_LGUI, false);
2578 }
2579 if (keystate[SDL_SCANCODE_RGUI] && !(GetKeyState(VK_RWIN) & 0x8000)) {
2580 SDL_SendKeyboardKey(0, SDL_GLOBAL_KEYBOARD_ID, 0, SDL_SCANCODE_RGUI, false);
2581 }
2582 }
2583
2584 // Update the clipping rect in case someone else has stolen it
2585 if (_this) {
2586 SDL_Window *window = _this->windows;
2587 while (window) {
2588 SDL_WindowData *data = window->internal;
2589 if (data && data->skip_update_clipcursor) {
2590 data->skip_update_clipcursor = false;
2591 WIN_UpdateClipCursor(window);
2592 }
2593 window = window->next;
2594 }
2595 }
2596
2597 // Update mouse capture
2598 WIN_UpdateMouseCapture();
2599
2600 if (!_this->internal->gameinput_context) {
2601 WIN_CheckKeyboardAndMouseHotplug(_this, false);
2602 }
2603
2604 WIN_UpdateIMECandidates(_this);
2605
2606#endif // !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
2607
2608#ifdef SDL_PLATFORM_GDK
2609 GDK_DispatchTaskQueue();
2610#endif
2611}
2612
2613static int app_registered = 0;
2614LPTSTR SDL_Appname = NULL;
2615Uint32 SDL_Appstyle = 0;
2616HINSTANCE SDL_Instance = NULL;
2617
2618static void WIN_CleanRegisterApp(WNDCLASSEX wcex)
2619{
2620#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
2621 if (wcex.hIcon) {
2622 DestroyIcon(wcex.hIcon);
2623 }
2624 if (wcex.hIconSm) {
2625 DestroyIcon(wcex.hIconSm);
2626 }
2627#endif
2628 SDL_free(SDL_Appname);
2629 SDL_Appname = NULL;
2630}
2631
2632#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
2633static BOOL CALLBACK WIN_ResourceNameCallback(HMODULE hModule, LPCTSTR lpType, LPTSTR lpName, LONG_PTR lParam)
2634{
2635 WNDCLASSEX *wcex = (WNDCLASSEX *)lParam;
2636
2637 (void)lpType; // We already know that the resource type is RT_GROUP_ICON.
2638
2639 /* We leave hIconSm as NULL as it will allow Windows to automatically
2640 choose the appropriate small icon size to suit the current DPI. */
2641 wcex->hIcon = LoadIcon(hModule, lpName);
2642
2643 // Do not bother enumerating any more.
2644 return FALSE;
2645}
2646#endif // !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
2647
2648// Register the class for this application
2649bool SDL_RegisterApp(const char *name, Uint32 style, void *hInst)
2650{
2651 WNDCLASSEX wcex;
2652#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
2653 const char *hint;
2654#endif
2655
2656 // Only do this once...
2657 if (app_registered) {
2658 ++app_registered;
2659 return true;
2660 }
2661 SDL_assert(!SDL_Appname);
2662 if (!name) {
2663 name = "SDL_app";
2664#if defined(CS_BYTEALIGNCLIENT) || defined(CS_OWNDC)
2665 style = (CS_BYTEALIGNCLIENT | CS_OWNDC);
2666#endif
2667 }
2668 SDL_Appname = WIN_UTF8ToString(name);
2669 SDL_Appstyle = style;
2670 SDL_Instance = hInst ? (HINSTANCE)hInst : GetModuleHandle(NULL);
2671
2672 // Register the application class
2673 wcex.cbSize = sizeof(WNDCLASSEX);
2674 wcex.hCursor = NULL;
2675 wcex.hIcon = NULL;
2676 wcex.hIconSm = NULL;
2677 wcex.lpszMenuName = NULL;
2678 wcex.lpszClassName = SDL_Appname;
2679 wcex.style = SDL_Appstyle;
2680 wcex.hbrBackground = NULL;
2681 wcex.lpfnWndProc = WIN_WindowProc;
2682 wcex.hInstance = SDL_Instance;
2683 wcex.cbClsExtra = 0;
2684 wcex.cbWndExtra = 0;
2685
2686#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
2687 hint = SDL_GetHint(SDL_HINT_WINDOWS_INTRESOURCE_ICON);
2688 if (hint && *hint) {
2689 wcex.hIcon = LoadIcon(SDL_Instance, MAKEINTRESOURCE(SDL_atoi(hint)));
2690
2691 hint = SDL_GetHint(SDL_HINT_WINDOWS_INTRESOURCE_ICON_SMALL);
2692 if (hint && *hint) {
2693 wcex.hIconSm = LoadIcon(SDL_Instance, MAKEINTRESOURCE(SDL_atoi(hint)));
2694 }
2695 } else {
2696 // Use the first icon as a default icon, like in the Explorer.
2697 EnumResourceNames(SDL_Instance, RT_GROUP_ICON, WIN_ResourceNameCallback, (LONG_PTR)&wcex);
2698 }
2699#endif // !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
2700
2701 if (!RegisterClassEx(&wcex)) {
2702 WIN_CleanRegisterApp(wcex);
2703 return SDL_SetError("Couldn't register application class");
2704 }
2705
2706 app_registered = 1;
2707 return true;
2708}
2709
2710// Unregisters the windowclass registered in SDL_RegisterApp above.
2711void SDL_UnregisterApp(void)
2712{
2713 WNDCLASSEX wcex;
2714
2715 // SDL_RegisterApp might not have been called before
2716 if (!app_registered) {
2717 return;
2718 }
2719 --app_registered;
2720 if (app_registered == 0) {
2721 // Ensure the icons are initialized.
2722 wcex.hIcon = NULL;
2723 wcex.hIconSm = NULL;
2724 // Check for any registered window classes.
2725#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
2726 if (GetClassInfoEx(SDL_Instance, SDL_Appname, &wcex)) {
2727 UnregisterClass(SDL_Appname, SDL_Instance);
2728 }
2729#endif
2730 WIN_CleanRegisterApp(wcex);
2731 }
2732}
2733
2734#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 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#ifndef SDL_windowsevents_h_
24#define SDL_windowsevents_h_
25
26extern LPTSTR SDL_Appname;
27extern Uint32 SDL_Appstyle;
28extern HINSTANCE SDL_Instance;
29
30extern LRESULT CALLBACK WIN_KeyboardHookProc(int nCode, WPARAM wParam, LPARAM lParam);
31extern LRESULT CALLBACK WIN_WindowProc(HWND hwnd, UINT msg, WPARAM wParam,
32 LPARAM lParam);
33extern void WIN_PollRawInput(SDL_VideoDevice *_this, Uint64 poll_start);
34extern void WIN_CheckKeyboardAndMouseHotplug(SDL_VideoDevice *_this, bool initial_check);
35extern void WIN_PumpEvents(SDL_VideoDevice *_this);
36extern void WIN_SendWakeupEvent(SDL_VideoDevice *_this, SDL_Window *window);
37extern int WIN_WaitEventTimeout(SDL_VideoDevice *_this, Sint64 timeoutNS);
38
39#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 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#if defined(SDL_VIDEO_DRIVER_WINDOWS) && !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
24
25#include "SDL_windowsvideo.h"
26
27bool WIN_CreateWindowFramebuffer(SDL_VideoDevice *_this, SDL_Window *window, SDL_PixelFormat *format, void **pixels, int *pitch)
28{
29 SDL_WindowData *data = window->internal;
30 bool isstack;
31 size_t size;
32 LPBITMAPINFO info;
33 HBITMAP hbm;
34 int w, h;
35
36 SDL_GetWindowSizeInPixels(window, &w, &h);
37
38 // Free the old framebuffer surface
39 if (data->mdc) {
40 DeleteDC(data->mdc);
41 }
42 if (data->hbm) {
43 DeleteObject(data->hbm);
44 }
45
46 // Find out the format of the screen
47 size = sizeof(BITMAPINFOHEADER) + 256 * sizeof(RGBQUAD);
48 info = (LPBITMAPINFO)SDL_small_alloc(Uint8, size, &isstack);
49 if (!info) {
50 return false;
51 }
52
53 SDL_memset(info, 0, size);
54 info->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
55
56 // The second call to GetDIBits() fills in the bitfields
57 hbm = CreateCompatibleBitmap(data->hdc, 1, 1);
58 GetDIBits(data->hdc, hbm, 0, 0, NULL, info, DIB_RGB_COLORS);
59 GetDIBits(data->hdc, hbm, 0, 0, NULL, info, DIB_RGB_COLORS);
60 DeleteObject(hbm);
61
62 *format = SDL_PIXELFORMAT_UNKNOWN;
63 if (info->bmiHeader.biCompression == BI_BITFIELDS) {
64 int bpp;
65 Uint32 *masks;
66
67 bpp = info->bmiHeader.biPlanes * info->bmiHeader.biBitCount;
68 masks = (Uint32 *)((Uint8 *)info + info->bmiHeader.biSize);
69 *format = SDL_GetPixelFormatForMasks(bpp, masks[0], masks[1], masks[2], 0);
70 }
71 if (*format == SDL_PIXELFORMAT_UNKNOWN) {
72 // We'll use RGB format for now
73 *format = SDL_PIXELFORMAT_XRGB8888;
74
75 // Create a new one
76 SDL_memset(info, 0, size);
77 info->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
78 info->bmiHeader.biPlanes = 1;
79 info->bmiHeader.biBitCount = 32;
80 info->bmiHeader.biCompression = BI_RGB;
81 }
82
83 // Fill in the size information
84 *pitch = (((w * SDL_BYTESPERPIXEL(*format)) + 3) & ~3);
85 info->bmiHeader.biWidth = w;
86 info->bmiHeader.biHeight = -h; // negative for topdown bitmap
87 info->bmiHeader.biSizeImage = (DWORD)h * (*pitch);
88
89 data->mdc = CreateCompatibleDC(data->hdc);
90 data->hbm = CreateDIBSection(data->hdc, info, DIB_RGB_COLORS, pixels, NULL, 0);
91 SDL_small_free(info, isstack);
92
93 if (!data->hbm) {
94 return WIN_SetError("Unable to create DIB");
95 }
96 SelectObject(data->mdc, data->hbm);
97
98 return true;
99}
100
101bool WIN_UpdateWindowFramebuffer(SDL_VideoDevice *_this, SDL_Window *window, const SDL_Rect *rects, int numrects)
102{
103 SDL_WindowData *data = window->internal;
104 int i;
105
106 for (i = 0; i < numrects; ++i) {
107 BitBlt(data->hdc, rects[i].x, rects[i].y, rects[i].w, rects[i].h,
108 data->mdc, rects[i].x, rects[i].y, SRCCOPY);
109 }
110 return true;
111}
112
113void WIN_DestroyWindowFramebuffer(SDL_VideoDevice *_this, SDL_Window *window)
114{
115 SDL_WindowData *data = window->internal;
116
117 if (!data) {
118 // The window wasn't fully initialized
119 return;
120 }
121
122 if (data->mdc) {
123 DeleteDC(data->mdc);
124 data->mdc = NULL;
125 }
126 if (data->hbm) {
127 DeleteObject(data->hbm);
128 data->hbm = NULL;
129 }
130}
131
132#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 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23extern bool WIN_CreateWindowFramebuffer(SDL_VideoDevice *_this, SDL_Window *window, SDL_PixelFormat *format, void **pixels, int *pitch);
24extern bool WIN_UpdateWindowFramebuffer(SDL_VideoDevice *_this, SDL_Window *window, const SDL_Rect *rects, int numrects);
25extern 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 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#include "SDL_windowsvideo.h"
24
25// 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.
26#undef HAVE_GAMEINPUT_H
27
28#ifdef HAVE_GAMEINPUT_H
29
30#include "../../core/windows/SDL_gameinput.h"
31#include "../../events/SDL_mouse_c.h"
32#include "../../events/SDL_keyboard_c.h"
33#include "../../events/scancodes_windows.h"
34
35
36#define MAX_GAMEINPUT_BUTTONS 7 // GameInputMouseWheelTiltRight is the highest button
37
38static const Uint8 GAMEINPUT_button_map[MAX_GAMEINPUT_BUTTONS] = {
39 SDL_BUTTON_LEFT,
40 SDL_BUTTON_RIGHT,
41 SDL_BUTTON_MIDDLE,
42 SDL_BUTTON_X1,
43 SDL_BUTTON_X2,
44 6,
45 7
46};
47
48typedef struct GAMEINPUT_Device
49{
50 IGameInputDevice *pDevice;
51 const GameInputDeviceInfo *info;
52 char *name;
53 Uint32 instance_id; // generated by SDL
54 bool registered;
55 bool delete_requested;
56 IGameInputReading *last_mouse_reading;
57 IGameInputReading *last_keyboard_reading;
58} GAMEINPUT_Device;
59
60struct WIN_GameInputData
61{
62 IGameInput *pGameInput;
63 GameInputCallbackToken gameinput_callback_token;
64 int num_devices;
65 GAMEINPUT_Device **devices;
66 GameInputKind enabled_input;
67 SDL_Mutex *lock;
68 uint64_t timestamp_offset;
69};
70
71static bool GAMEINPUT_InternalAddOrFind(WIN_GameInputData *data, IGameInputDevice *pDevice)
72{
73 GAMEINPUT_Device **devicelist = NULL;
74 GAMEINPUT_Device *device = NULL;
75 const GameInputDeviceInfo *info;
76 bool result = false;
77
78 info = IGameInputDevice_GetDeviceInfo(pDevice);
79
80 SDL_LockMutex(data->lock);
81 {
82 for (int i = 0; i < data->num_devices; ++i) {
83 device = data->devices[i];
84 if (device && device->pDevice == pDevice) {
85 // we're already added
86 device->delete_requested = false;
87 result = true;
88 goto done;
89 }
90 }
91
92 device = (GAMEINPUT_Device *)SDL_calloc(1, sizeof(*device));
93 if (!device) {
94 goto done;
95 }
96
97 devicelist = (GAMEINPUT_Device **)SDL_realloc(data->devices, (data->num_devices + 1) * sizeof(*devicelist));
98 if (!devicelist) {
99 SDL_free(device);
100 goto done;
101 }
102
103 if (info->deviceStrings) {
104 // In theory we could get the manufacturer and product strings here, but they're NULL for all the devices I've tested
105 }
106
107 if (info->displayName) {
108 // This could give us a product string, but it's NULL for all the devices I've tested
109 }
110
111 IGameInputDevice_AddRef(pDevice);
112 device->pDevice = pDevice;
113 device->instance_id = SDL_GetNextObjectID();
114 device->info = info;
115
116 data->devices = devicelist;
117 data->devices[data->num_devices++] = device;
118
119 result = true;
120 }
121done:
122 SDL_UnlockMutex(data->lock);
123
124 return result;
125}
126
127static bool GAMEINPUT_InternalRemoveByIndex(WIN_GameInputData *data, int idx)
128{
129 GAMEINPUT_Device **devicelist = NULL;
130 GAMEINPUT_Device *device;
131 bool result = false;
132
133 SDL_LockMutex(data->lock);
134 {
135 if (idx < 0 || idx >= data->num_devices) {
136 result = SDL_SetError("GAMEINPUT_InternalRemoveByIndex argument idx %d is out of range", idx);
137 goto done;
138 }
139
140 device = data->devices[idx];
141 if (device) {
142 if (device->registered) {
143 if (device->info->supportedInput & GameInputKindMouse) {
144 SDL_RemoveMouse(device->instance_id, true);
145 }
146 if (device->info->supportedInput & GameInputKindKeyboard) {
147 SDL_RemoveKeyboard(device->instance_id, true);
148 }
149 if (device->last_mouse_reading) {
150 IGameInputReading_Release(device->last_mouse_reading);
151 device->last_mouse_reading = NULL;
152 }
153 if (device->last_keyboard_reading) {
154 IGameInputReading_Release(device->last_keyboard_reading);
155 device->last_keyboard_reading = NULL;
156 }
157 }
158 IGameInputDevice_Release(device->pDevice);
159 SDL_free(device->name);
160 SDL_free(device);
161 }
162 data->devices[idx] = NULL;
163
164 if (data->num_devices == 1) {
165 // last element in the list, free the entire list then
166 SDL_free(data->devices);
167 data->devices = NULL;
168 } else {
169 if (idx != data->num_devices - 1) {
170 size_t bytes = sizeof(*devicelist) * (data->num_devices - idx - 1);
171 SDL_memmove(&data->devices[idx], &data->devices[idx + 1], bytes);
172 }
173 }
174
175 // decrement the count and return
176 --data->num_devices;
177 result = true;
178 }
179done:
180 SDL_UnlockMutex(data->lock);
181
182 return result;
183}
184
185static void CALLBACK GAMEINPUT_InternalDeviceCallback(
186 _In_ GameInputCallbackToken callbackToken,
187 _In_ void* context,
188 _In_ IGameInputDevice *pDevice,
189 _In_ uint64_t timestamp,
190 _In_ GameInputDeviceStatus currentStatus,
191 _In_ GameInputDeviceStatus previousStatus)
192{
193 WIN_GameInputData *data = (WIN_GameInputData *)context;
194 int idx = 0;
195 GAMEINPUT_Device *device = NULL;
196
197 if (!pDevice) {
198 // This should never happen, but ignore it if it does
199 return;
200 }
201
202 if (currentStatus & GameInputDeviceConnected) {
203 GAMEINPUT_InternalAddOrFind(data, pDevice);
204 } else {
205 for (idx = 0; idx < data->num_devices; ++idx) {
206 device = data->devices[idx];
207 if (device && device->pDevice == pDevice) {
208 // will be deleted on the next Detect call
209 device->delete_requested = true;
210 break;
211 }
212 }
213 }
214}
215
216bool WIN_InitGameInput(SDL_VideoDevice *_this)
217{
218 WIN_GameInputData *data;
219 HRESULT hr;
220 bool result = false;
221
222 if (_this->internal->gameinput_context) {
223 return true;
224 }
225
226 data = (WIN_GameInputData *)SDL_calloc(1, sizeof(*data));
227 if (!data) {
228 goto done;
229 }
230 _this->internal->gameinput_context = data;
231
232 data->lock = SDL_CreateMutex();
233 if (!data->lock) {
234 goto done;
235 }
236
237 if (!SDL_InitGameInput(&data->pGameInput)) {
238 goto done;
239 }
240
241 hr = IGameInput_RegisterDeviceCallback(data->pGameInput,
242 NULL,
243 (GameInputKindMouse | GameInputKindKeyboard),
244 GameInputDeviceConnected,
245 GameInputBlockingEnumeration,
246 data,
247 GAMEINPUT_InternalDeviceCallback,
248 &data->gameinput_callback_token);
249 if (FAILED(hr)) {
250 SDL_SetError("IGameInput::RegisterDeviceCallback failure with HRESULT of %08X", hr);
251 goto done;
252 }
253
254 // Calculate the relative offset between SDL timestamps and GameInput timestamps
255 Uint64 now = SDL_GetTicksNS();
256 uint64_t timestampUS = IGameInput_GetCurrentTimestamp(data->pGameInput);
257 data->timestamp_offset = (SDL_NS_TO_US(now) - timestampUS);
258
259 result = true;
260
261done:
262 if (!result) {
263 WIN_QuitGameInput(_this);
264 }
265 return result;
266}
267
268static void GAMEINPUT_InitialMouseReading(WIN_GameInputData *data, SDL_Window *window, GAMEINPUT_Device *device, IGameInputReading *reading)
269{
270 GameInputMouseState state;
271 if (SUCCEEDED(IGameInputReading_GetMouseState(reading, &state))) {
272 Uint64 timestamp = SDL_US_TO_NS(IGameInputReading_GetTimestamp(reading) + data->timestamp_offset);
273 SDL_MouseID mouseID = device->instance_id;
274
275 for (int i = 0; i < MAX_GAMEINPUT_BUTTONS; ++i) {
276 const GameInputMouseButtons mask = (1 << i);
277 bool down = ((state.buttons & mask) != 0);
278 SDL_SendMouseButton(timestamp, window, mouseID, GAMEINPUT_button_map[i], down);
279 }
280 }
281}
282
283static void GAMEINPUT_HandleMouseDelta(WIN_GameInputData *data, SDL_Window *window, GAMEINPUT_Device *device, IGameInputReading *last_reading, IGameInputReading *reading)
284{
285 GameInputMouseState last;
286 GameInputMouseState state;
287 if (SUCCEEDED(IGameInputReading_GetMouseState(last_reading, &last)) &&
288 SUCCEEDED(IGameInputReading_GetMouseState(reading, &state))) {
289 Uint64 timestamp = SDL_US_TO_NS(IGameInputReading_GetTimestamp(reading) + data->timestamp_offset);
290 SDL_MouseID mouseID = device->instance_id;
291
292 GameInputMouseState delta;
293 delta.buttons = (state.buttons ^ last.buttons);
294 delta.positionX = (state.positionX - last.positionX);
295 delta.positionY = (state.positionY - last.positionY);
296 delta.wheelX = (state.wheelX - last.wheelX);
297 delta.wheelY = (state.wheelY - last.wheelY);
298
299 if (delta.positionX || delta.positionY) {
300 SDL_SendMouseMotion(timestamp, window, mouseID, true, (float)delta.positionX, (float)delta.positionY);
301 }
302 if (delta.buttons) {
303 for (int i = 0; i < MAX_GAMEINPUT_BUTTONS; ++i) {
304 const GameInputMouseButtons mask = (1 << i);
305 if (delta.buttons & mask) {
306 bool down = ((state.buttons & mask) != 0);
307 SDL_SendMouseButton(timestamp, window, mouseID, GAMEINPUT_button_map[i], down);
308 }
309 }
310 }
311 if (delta.wheelX || delta.wheelY) {
312 float fAmountX = (float)delta.wheelX / WHEEL_DELTA;
313 float fAmountY = (float)delta.wheelY / WHEEL_DELTA;
314 SDL_SendMouseWheel(timestamp, SDL_GetMouseFocus(), device->instance_id, fAmountX, fAmountY, SDL_MOUSEWHEEL_NORMAL);
315 }
316 }
317}
318
319static SDL_Scancode GetScancodeFromKeyState(const GameInputKeyState *state)
320{
321 Uint8 index = (Uint8)(state->scanCode & 0xFF);
322 if ((state->scanCode & 0xFF00) == 0xE000) {
323 index |= 0x80;
324 }
325 return windows_scancode_table[index];
326}
327
328static bool KeysHaveScancode(const GameInputKeyState *keys, uint32_t count, SDL_Scancode scancode)
329{
330 for (uint32_t i = 0; i < count; ++i) {
331 if (GetScancodeFromKeyState(&keys[i]) == scancode) {
332 return true;
333 }
334 }
335 return false;
336}
337
338static void GAMEINPUT_InitialKeyboardReading(WIN_GameInputData *data, SDL_Window *window, GAMEINPUT_Device *device, IGameInputReading *reading)
339{
340 Uint64 timestamp = SDL_US_TO_NS(IGameInputReading_GetTimestamp(reading) + data->timestamp_offset);
341 SDL_KeyboardID keyboardID = device->instance_id;
342
343 uint32_t max_keys = device->info->keyboardInfo->maxSimultaneousKeys;
344 GameInputKeyState *keys = SDL_stack_alloc(GameInputKeyState, max_keys);
345 if (!keys) {
346 return;
347 }
348
349 uint32_t num_keys = IGameInputReading_GetKeyState(reading, max_keys, keys);
350 if (!num_keys) {
351 // FIXME: We probably need to track key state by keyboardID
352 SDL_ResetKeyboard();
353 return;
354 }
355
356 // Go through and send key up events for any key that's not held down
357 int num_scancodes;
358 const bool *keyboard_state = SDL_GetKeyboardState(&num_scancodes);
359 for (int i = 0; i < num_scancodes; ++i) {
360 if (keyboard_state[i] && !KeysHaveScancode(keys, num_keys, (SDL_Scancode)i)) {
361 SDL_SendKeyboardKey(timestamp, keyboardID, keys[i].scanCode, (SDL_Scancode)i, false);
362 }
363 }
364
365 // Go through and send key down events for any key that's held down
366 for (uint32_t i = 0; i < num_keys; ++i) {
367 SDL_SendKeyboardKey(timestamp, keyboardID, keys[i].scanCode, GetScancodeFromKeyState(&keys[i]), true);
368 }
369}
370
371#ifdef DEBUG_KEYS
372static void DumpKeys(const char *prefix, GameInputKeyState *keys, uint32_t count)
373{
374 SDL_Log("%s", prefix);
375 for (uint32_t i = 0; i < count; ++i) {
376 char str[5];
377 *SDL_UCS4ToUTF8(keys[i].codePoint, str) = '\0';
378 SDL_Log(" Key 0x%.2x (%s)", keys[i].scanCode, str);
379 }
380}
381#endif // DEBUG_KEYS
382
383static void GAMEINPUT_HandleKeyboardDelta(WIN_GameInputData *data, SDL_Window *window, GAMEINPUT_Device *device, IGameInputReading *last_reading, IGameInputReading *reading)
384{
385 Uint64 timestamp = SDL_US_TO_NS(IGameInputReading_GetTimestamp(reading) + data->timestamp_offset);
386 SDL_KeyboardID keyboardID = device->instance_id;
387
388 uint32_t max_keys = device->info->keyboardInfo->maxSimultaneousKeys;
389 GameInputKeyState *last = SDL_stack_alloc(GameInputKeyState, max_keys);
390 GameInputKeyState *keys = SDL_stack_alloc(GameInputKeyState, max_keys);
391 if (!last || !keys) {
392 return;
393 }
394
395 uint32_t index_last = 0;
396 uint32_t index_keys = 0;
397 uint32_t num_last = IGameInputReading_GetKeyState(last_reading, max_keys, last);
398 uint32_t num_keys = IGameInputReading_GetKeyState(reading, max_keys, keys);
399#ifdef DEBUG_KEYS
400 SDL_Log("Timestamp: %llu", timestamp);
401 DumpKeys("Last keys:", last, num_last);
402 DumpKeys("New keys:", keys, num_keys);
403#endif
404 while (index_last < num_last || index_keys < num_keys) {
405 if (index_last < num_last && index_keys < num_keys) {
406 if (last[index_last].scanCode == keys[index_keys].scanCode) {
407 // No change
408 ++index_last;
409 ++index_keys;
410 } else {
411 // This key was released
412 SDL_SendKeyboardKey(timestamp, keyboardID, last[index_last].scanCode, GetScancodeFromKeyState(&last[index_last]), false);
413 ++index_last;
414 }
415 } else if (index_last < num_last) {
416 // This key was released
417 SDL_SendKeyboardKey(timestamp, keyboardID, last[index_last].scanCode, GetScancodeFromKeyState(&last[index_last]), false);
418 ++index_last;
419 } else {
420 // This key was pressed
421 SDL_SendKeyboardKey(timestamp, keyboardID, keys[index_keys].scanCode, GetScancodeFromKeyState(&keys[index_keys]), true);
422 ++index_keys;
423 }
424 }
425}
426
427void WIN_UpdateGameInput(SDL_VideoDevice *_this)
428{
429 WIN_GameInputData *data = _this->internal->gameinput_context;
430
431 SDL_LockMutex(data->lock);
432 {
433 // Key events and relative mouse motion both go to the window with keyboard focus
434 SDL_Window *window = SDL_GetKeyboardFocus();
435
436 for (int i = 0; i < data->num_devices; ++i) {
437 GAMEINPUT_Device *device = data->devices[i];
438 IGameInputReading *reading;
439
440 if (!device->registered) {
441 if (device->info->supportedInput & GameInputKindMouse) {
442 SDL_AddMouse(device->instance_id, device->name, true);
443 }
444 if (device->info->supportedInput & GameInputKindKeyboard) {
445 SDL_AddKeyboard(device->instance_id, device->name, true);
446 }
447 device->registered = true;
448 }
449
450 if (device->delete_requested) {
451 GAMEINPUT_InternalRemoveByIndex(data, i--);
452 continue;
453 }
454
455 if (!(device->info->supportedInput & data->enabled_input)) {
456 continue;
457 }
458
459 if (!window) {
460 continue;
461 }
462
463 if (data->enabled_input & GameInputKindMouse) {
464 if (device->last_mouse_reading) {
465 HRESULT hr;
466 while (SUCCEEDED(hr = IGameInput_GetNextReading(data->pGameInput, device->last_mouse_reading, GameInputKindMouse, device->pDevice, &reading))) {
467 GAMEINPUT_HandleMouseDelta(data, window, device, device->last_mouse_reading, reading);
468 IGameInputReading_Release(device->last_mouse_reading);
469 device->last_mouse_reading = reading;
470 }
471 if (hr != GAMEINPUT_E_READING_NOT_FOUND) {
472 if (SUCCEEDED(IGameInput_GetCurrentReading(data->pGameInput, GameInputKindMouse, device->pDevice, &reading))) {
473 GAMEINPUT_HandleMouseDelta(data, window, device, device->last_mouse_reading, reading);
474 IGameInputReading_Release(device->last_mouse_reading);
475 device->last_mouse_reading = reading;
476 }
477 }
478 } else {
479 if (SUCCEEDED(IGameInput_GetCurrentReading(data->pGameInput, GameInputKindMouse, device->pDevice, &reading))) {
480 GAMEINPUT_InitialMouseReading(data, window, device, reading);
481 device->last_mouse_reading = reading;
482 }
483 }
484 }
485
486 if (data->enabled_input & GameInputKindKeyboard) {
487 if (window->text_input_active) {
488 // Reset raw input while text input is active
489 if (device->last_keyboard_reading) {
490 IGameInputReading_Release(device->last_keyboard_reading);
491 device->last_keyboard_reading = NULL;
492 }
493 } else {
494 if (device->last_keyboard_reading) {
495 HRESULT hr;
496 while (SUCCEEDED(hr = IGameInput_GetNextReading(data->pGameInput, device->last_keyboard_reading, GameInputKindKeyboard, device->pDevice, &reading))) {
497 GAMEINPUT_HandleKeyboardDelta(data, window, device, device->last_keyboard_reading, reading);
498 IGameInputReading_Release(device->last_keyboard_reading);
499 device->last_keyboard_reading = reading;
500 }
501 if (hr != GAMEINPUT_E_READING_NOT_FOUND) {
502 if (SUCCEEDED(IGameInput_GetCurrentReading(data->pGameInput, GameInputKindKeyboard, device->pDevice, &reading))) {
503 GAMEINPUT_HandleKeyboardDelta(data, window, device, device->last_keyboard_reading, reading);
504 IGameInputReading_Release(device->last_keyboard_reading);
505 device->last_keyboard_reading = reading;
506 }
507 }
508 } else {
509 if (SUCCEEDED(IGameInput_GetCurrentReading(data->pGameInput, GameInputKindKeyboard, device->pDevice, &reading))) {
510 GAMEINPUT_InitialKeyboardReading(data, window, device, reading);
511 device->last_keyboard_reading = reading;
512 }
513 }
514 }
515 }
516 }
517 }
518 SDL_UnlockMutex(data->lock);
519}
520
521bool WIN_UpdateGameInputEnabled(SDL_VideoDevice *_this)
522{
523 WIN_GameInputData *data = _this->internal->gameinput_context;
524 bool raw_mouse_enabled = _this->internal->raw_mouse_enabled;
525 bool raw_keyboard_enabled = _this->internal->raw_keyboard_enabled;
526
527 SDL_LockMutex(data->lock);
528 {
529 data->enabled_input = (raw_mouse_enabled ? GameInputKindMouse : GameInputKindUnknown) |
530 (raw_keyboard_enabled ? GameInputKindKeyboard : GameInputKindUnknown);
531
532 // Reset input if not enabled
533 for (int i = 0; i < data->num_devices; ++i) {
534 GAMEINPUT_Device *device = data->devices[i];
535
536 if (device->last_mouse_reading && !raw_mouse_enabled) {
537 IGameInputReading_Release(device->last_mouse_reading);
538 device->last_mouse_reading = NULL;
539 }
540
541 if (device->last_keyboard_reading && !raw_keyboard_enabled) {
542 IGameInputReading_Release(device->last_keyboard_reading);
543 device->last_keyboard_reading = NULL;
544 }
545 }
546 }
547 SDL_UnlockMutex(data->lock);
548
549 return true;
550}
551
552void WIN_QuitGameInput(SDL_VideoDevice *_this)
553{
554 WIN_GameInputData *data = _this->internal->gameinput_context;
555
556 if (!data) {
557 return;
558 }
559
560 if (data->pGameInput) {
561 // free the callback
562 if (data->gameinput_callback_token != GAMEINPUT_INVALID_CALLBACK_TOKEN_VALUE) {
563 IGameInput_UnregisterCallback(data->pGameInput, data->gameinput_callback_token, /*timeoutInUs:*/ 10000);
564 data->gameinput_callback_token = GAMEINPUT_INVALID_CALLBACK_TOKEN_VALUE;
565 }
566
567 // free the list
568 while (data->num_devices > 0) {
569 GAMEINPUT_InternalRemoveByIndex(data, 0);
570 }
571
572 IGameInput_Release(data->pGameInput);
573 data->pGameInput = NULL;
574 }
575
576 if (data->pGameInput) {
577 SDL_QuitGameInput();
578 data->pGameInput = NULL;
579 }
580
581 if (data->lock) {
582 SDL_DestroyMutex(data->lock);
583 data->lock = NULL;
584 }
585
586 SDL_free(data);
587 _this->internal->gameinput_context = NULL;
588}
589
590#else // !HAVE_GAMEINPUT_H
591
592bool WIN_InitGameInput(SDL_VideoDevice* _this)
593{
594 return SDL_Unsupported();
595}
596
597bool WIN_UpdateGameInputEnabled(SDL_VideoDevice *_this)
598{
599 return SDL_Unsupported();
600}
601
602void WIN_UpdateGameInput(SDL_VideoDevice* _this)
603{
604 return;
605}
606
607void WIN_QuitGameInput(SDL_VideoDevice* _this)
608{
609 return;
610}
611
612#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 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23typedef struct WIN_GameInputData WIN_GameInputData;
24
25extern bool WIN_InitGameInput(SDL_VideoDevice *_this);
26extern bool WIN_UpdateGameInputEnabled(SDL_VideoDevice *_this);
27extern void WIN_UpdateGameInput(SDL_VideoDevice *_this);
28extern void WIN_QuitGameInput(SDL_VideoDevice *_this);
29
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 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#if defined(SDL_VIDEO_DRIVER_WINDOWS) && !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
24
25#include "SDL_windowsvideo.h"
26
27#include "../../events/SDL_keyboard_c.h"
28#include "../../events/scancodes_windows.h"
29
30#include <imm.h>
31#include <oleauto.h>
32
33#ifndef SDL_DISABLE_WINDOWS_IME
34#if 0
35#define SDL_DebugIMELog SDL_Log
36#else
37#define SDL_DebugIMELog(...)
38#endif
39static bool IME_Init(SDL_VideoData *videodata, SDL_Window *window);
40static void IME_Enable(SDL_VideoData *videodata, HWND hwnd);
41static void IME_Disable(SDL_VideoData *videodata, HWND hwnd);
42static void IME_SetTextInputArea(SDL_VideoData *videodata, HWND hwnd, const SDL_Rect *rect, int cursor);
43static void IME_ClearComposition(SDL_VideoData *videodata);
44static void IME_GetCandidateList(SDL_VideoData *videodata, HWND hwnd);
45static void IME_Quit(SDL_VideoData *videodata);
46#else
47static void IME_SetTextInputArea(SDL_VideoData *videodata, HWND hwnd, const SDL_Rect *rect, int cursor);
48#endif // !SDL_DISABLE_WINDOWS_IME
49
50#ifndef MAPVK_VK_TO_VSC
51#define MAPVK_VK_TO_VSC 0
52#endif
53#ifndef MAPVK_VSC_TO_VK
54#define MAPVK_VSC_TO_VK 1
55#endif
56
57// Alphabetic scancodes for PC keyboards
58void WIN_InitKeyboard(SDL_VideoDevice *_this)
59{
60#ifndef SDL_DISABLE_WINDOWS_IME
61 SDL_VideoData *data = _this->internal;
62
63 data->ime_candlistindexbase = 1;
64 data->ime_composition_length = 32 * sizeof(WCHAR);
65 data->ime_composition = (WCHAR *)SDL_calloc(data->ime_composition_length, sizeof(WCHAR));
66#endif // !SDL_DISABLE_WINDOWS_IME
67
68 WIN_UpdateKeymap(false);
69
70 SDL_SetScancodeName(SDL_SCANCODE_APPLICATION, "Menu");
71 SDL_SetScancodeName(SDL_SCANCODE_LGUI, "Left Windows");
72 SDL_SetScancodeName(SDL_SCANCODE_RGUI, "Right Windows");
73
74 // Are system caps/num/scroll lock active? Set our state to match.
75 SDL_ToggleModState(SDL_KMOD_CAPS, (GetKeyState(VK_CAPITAL) & 0x0001) ? true : false);
76 SDL_ToggleModState(SDL_KMOD_NUM, (GetKeyState(VK_NUMLOCK) & 0x0001) ? true : false);
77 SDL_ToggleModState(SDL_KMOD_SCROLL, (GetKeyState(VK_SCROLL) & 0x0001) ? true : false);
78}
79
80void WIN_UpdateKeymap(bool send_event)
81{
82 SDL_Scancode scancode;
83 SDL_Keymap *keymap;
84 BYTE keyboardState[256] = { 0 };
85 WCHAR buffer[16];
86 SDL_Keymod mods[] = {
87 SDL_KMOD_NONE,
88 SDL_KMOD_SHIFT,
89 SDL_KMOD_CAPS,
90 (SDL_KMOD_SHIFT | SDL_KMOD_CAPS),
91 SDL_KMOD_MODE,
92 (SDL_KMOD_MODE | SDL_KMOD_SHIFT),
93 (SDL_KMOD_MODE | SDL_KMOD_CAPS),
94 (SDL_KMOD_MODE | SDL_KMOD_SHIFT | SDL_KMOD_CAPS)
95 };
96
97 WIN_ResetDeadKeys();
98
99 keymap = SDL_CreateKeymap();
100
101 for (int m = 0; m < SDL_arraysize(mods); ++m) {
102 for (int i = 0; i < SDL_arraysize(windows_scancode_table); i++) {
103 int vk, sc, result;
104 Uint32 *ch = 0;
105
106 // Make sure this scancode is a valid character scancode
107 scancode = windows_scancode_table[i];
108 if (scancode == SDL_SCANCODE_UNKNOWN ||
109 scancode == SDL_SCANCODE_DELETE ||
110 (SDL_GetKeymapKeycode(NULL, scancode, SDL_KMOD_NONE) & SDLK_SCANCODE_MASK)) {
111
112 // The Colemak mapping swaps Backspace and CapsLock
113 if (mods[m] == SDL_KMOD_NONE &&
114 (scancode == SDL_SCANCODE_CAPSLOCK ||
115 scancode == SDL_SCANCODE_BACKSPACE)) {
116 vk = LOBYTE(MapVirtualKey(i, MAPVK_VSC_TO_VK));
117 if (vk == VK_CAPITAL) {
118 SDL_SetKeymapEntry(keymap, scancode, mods[m], SDLK_CAPSLOCK);
119 } else if (vk == VK_BACK) {
120 SDL_SetKeymapEntry(keymap, scancode, mods[m], SDLK_BACKSPACE);
121 }
122 }
123 continue;
124 }
125
126 // Unpack the single byte index to make the scan code.
127 sc = MAKEWORD(i & 0x7f, (i & 0x80) ? 0xe0 : 0x00);
128 vk = LOBYTE(MapVirtualKey(sc, MAPVK_VSC_TO_VK));
129 if (!vk) {
130 continue;
131 }
132
133 // Update the keyboard state for the modifiers
134 keyboardState[VK_SHIFT] = (mods[m] & SDL_KMOD_SHIFT) ? 0x80 : 0x00;
135 keyboardState[VK_CAPITAL] = (mods[m] & SDL_KMOD_CAPS) ? 0x01 : 0x00;
136 keyboardState[VK_CONTROL] = (mods[m] & SDL_KMOD_MODE) ? 0x80 : 0x00;
137 keyboardState[VK_MENU] = (mods[m] & SDL_KMOD_MODE) ? 0x80 : 0x00;
138
139 result = ToUnicode(vk, sc, keyboardState, buffer, 16, 0);
140 buffer[SDL_abs(result)] = 0;
141
142 // Convert UTF-16 to UTF-32 code points
143 ch = (Uint32 *)SDL_iconv_string("UTF-32LE", "UTF-16LE", (const char *)buffer, (SDL_abs(result) + 1) * sizeof(WCHAR));
144 if (ch) {
145 /* Windows keyboard layouts can emit several UTF-32 code points on a single key press.
146 * Use <U+FFFD REPLACEMENT CHARACTER> since we cannot fit into single SDL_Keycode value in SDL keymap.
147 * See https://kbdlayout.info/features/ligatures for a list of such keys. */
148 SDL_SetKeymapEntry(keymap, scancode, mods[m], ch[1] == 0 ? ch[0] : 0xfffd);
149 SDL_free(ch);
150 } else {
151 // The default keymap doesn't have any SDL_KMOD_MODE entries, so we don't need to override them
152 if (!(mods[m] & SDL_KMOD_MODE)) {
153 SDL_SetKeymapEntry(keymap, scancode, mods[m], SDLK_UNKNOWN);
154 }
155 }
156
157 if (result < 0) {
158 WIN_ResetDeadKeys();
159 }
160 }
161 }
162
163 SDL_SetKeymap(keymap, send_event);
164}
165
166void WIN_QuitKeyboard(SDL_VideoDevice *_this)
167{
168#ifndef SDL_DISABLE_WINDOWS_IME
169 SDL_VideoData *data = _this->internal;
170
171 IME_Quit(data);
172
173 if (data->ime_composition) {
174 SDL_free(data->ime_composition);
175 data->ime_composition = NULL;
176 }
177#endif // !SDL_DISABLE_WINDOWS_IME
178}
179
180void WIN_ResetDeadKeys(void)
181{
182 /*
183 if a deadkey has been typed, but not the next character (which the deadkey might modify),
184 this tries to undo the effect pressing the deadkey.
185 see: http://archives.miloush.net/michkap/archive/2006/09/10/748775.html
186 */
187 BYTE keyboardState[256];
188 WCHAR buffer[16];
189 int vk, sc, result, i;
190
191 if (!GetKeyboardState(keyboardState)) {
192 return;
193 }
194
195 vk = VK_SPACE;
196 sc = MapVirtualKey(vk, MAPVK_VK_TO_VSC);
197 if (sc == 0) {
198 // the keyboard doesn't have this key
199 return;
200 }
201
202 for (i = 0; i < 5; i++) {
203 result = ToUnicode(vk, sc, keyboardState, buffer, 16, 0);
204 if (result > 0) {
205 // success
206 return;
207 }
208 }
209}
210
211bool WIN_StartTextInput(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID props)
212{
213 WIN_ResetDeadKeys();
214
215#ifndef SDL_DISABLE_WINDOWS_IME
216 HWND hwnd = window->internal->hwnd;
217 SDL_VideoData *videodata = _this->internal;
218 IME_Init(videodata, window);
219 IME_Enable(videodata, hwnd);
220
221 WIN_UpdateTextInputArea(_this, window);
222#endif // !SDL_DISABLE_WINDOWS_IME
223
224 return true;
225}
226
227bool WIN_StopTextInput(SDL_VideoDevice *_this, SDL_Window *window)
228{
229 WIN_ResetDeadKeys();
230
231#ifndef SDL_DISABLE_WINDOWS_IME
232 HWND hwnd = window->internal->hwnd;
233 SDL_VideoData *videodata = _this->internal;
234 IME_Init(videodata, window);
235 IME_Disable(videodata, hwnd);
236#endif // !SDL_DISABLE_WINDOWS_IME
237
238 return true;
239}
240
241bool WIN_UpdateTextInputArea(SDL_VideoDevice *_this, SDL_Window *window)
242{
243 SDL_VideoData *videodata = _this->internal;
244 SDL_WindowData *data = window->internal;
245
246 IME_SetTextInputArea(videodata, data->hwnd, &window->text_input_rect, window->text_input_cursor);
247 return true;
248}
249
250bool WIN_ClearComposition(SDL_VideoDevice *_this, SDL_Window *window)
251{
252#ifndef SDL_DISABLE_WINDOWS_IME
253 SDL_VideoData *videodata = _this->internal;
254
255 IME_ClearComposition(videodata);
256#endif
257 return true;
258}
259
260#ifdef SDL_DISABLE_WINDOWS_IME
261
262bool WIN_HandleIMEMessage(HWND hwnd, UINT msg, WPARAM wParam, LPARAM *lParam, SDL_VideoData *videodata)
263{
264 return false;
265}
266
267void WIN_UpdateIMECandidates(SDL_VideoDevice *_this)
268{
269 return;
270}
271
272#else
273
274#define LANG_CHT MAKELANGID(LANG_CHINESE, SUBLANG_CHINESE_TRADITIONAL)
275#define LANG_CHS MAKELANGID(LANG_CHINESE, SUBLANG_CHINESE_SIMPLIFIED)
276
277#define MAKEIMEVERSION(major, minor) ((DWORD)(((BYTE)(major) << 24) | ((BYTE)(minor) << 16)))
278#define IMEID_VER(id) ((id)&0xffff0000)
279#define IMEID_LANG(id) ((id)&0x0000ffff)
280
281#define CHT_HKL_DAYI ((HKL)(UINT_PTR)0xE0060404)
282#define CHT_HKL_NEW_PHONETIC ((HKL)(UINT_PTR)0xE0080404)
283#define CHT_HKL_NEW_CHANG_JIE ((HKL)(UINT_PTR)0xE0090404)
284#define CHT_HKL_NEW_QUICK ((HKL)(UINT_PTR)0xE00A0404)
285#define CHT_HKL_HK_CANTONESE ((HKL)(UINT_PTR)0xE00B0404)
286#define CHT_IMEFILENAME1 "TINTLGNT.IME"
287#define CHT_IMEFILENAME2 "CINTLGNT.IME"
288#define CHT_IMEFILENAME3 "MSTCIPHA.IME"
289#define IMEID_CHT_VER42 (LANG_CHT | MAKEIMEVERSION(4, 2))
290#define IMEID_CHT_VER43 (LANG_CHT | MAKEIMEVERSION(4, 3))
291#define IMEID_CHT_VER44 (LANG_CHT | MAKEIMEVERSION(4, 4))
292#define IMEID_CHT_VER50 (LANG_CHT | MAKEIMEVERSION(5, 0))
293#define IMEID_CHT_VER51 (LANG_CHT | MAKEIMEVERSION(5, 1))
294#define IMEID_CHT_VER52 (LANG_CHT | MAKEIMEVERSION(5, 2))
295#define IMEID_CHT_VER60 (LANG_CHT | MAKEIMEVERSION(6, 0))
296#define IMEID_CHT_VER_VISTA (LANG_CHT | MAKEIMEVERSION(7, 0))
297
298#define CHS_HKL ((HKL)(UINT_PTR)0xE00E0804)
299#define CHS_IMEFILENAME1 "PINTLGNT.IME"
300#define CHS_IMEFILENAME2 "MSSCIPYA.IME"
301#define IMEID_CHS_VER41 (LANG_CHS | MAKEIMEVERSION(4, 1))
302#define IMEID_CHS_VER42 (LANG_CHS | MAKEIMEVERSION(4, 2))
303#define IMEID_CHS_VER53 (LANG_CHS | MAKEIMEVERSION(5, 3))
304
305#define LANG() LOWORD((videodata->ime_hkl))
306#define PRIMLANG() ((WORD)PRIMARYLANGID(LANG()))
307#define SUBLANG() SUBLANGID(LANG())
308
309static void IME_UpdateInputLocale(SDL_VideoData *videodata);
310static void IME_SetWindow(SDL_VideoData *videodata, SDL_Window *window);
311static void IME_SetupAPI(SDL_VideoData *videodata);
312static DWORD IME_GetId(SDL_VideoData *videodata, UINT uIndex);
313static void IME_SendEditingEvent(SDL_VideoData *videodata);
314static void IME_SendClearComposition(SDL_VideoData *videodata);
315
316static bool IME_Init(SDL_VideoData *videodata, SDL_Window *window)
317{
318 HWND hwnd = window->internal->hwnd;
319
320 if (videodata->ime_initialized) {
321 return true;
322 }
323
324 const char *hint = SDL_GetHint(SDL_HINT_IME_IMPLEMENTED_UI);
325 if (hint && SDL_strstr(hint, "composition")) {
326 videodata->ime_internal_composition = true;
327 }
328 if (hint && SDL_strstr(hint, "candidates")) {
329 videodata->ime_internal_candidates = true;
330 }
331
332 videodata->ime_hwnd_main = hwnd;
333 videodata->ime_initialized = true;
334 videodata->ime_himm32 = SDL_LoadObject("imm32.dll");
335 if (!videodata->ime_himm32) {
336 videodata->ime_available = false;
337 SDL_ClearError();
338 return true;
339 }
340 /* *INDENT-OFF* */ // clang-format off
341 videodata->ImmLockIMC = (LPINPUTCONTEXT2 (WINAPI *)(HIMC))SDL_LoadFunction(videodata->ime_himm32, "ImmLockIMC");
342 videodata->ImmUnlockIMC = (BOOL (WINAPI *)(HIMC))SDL_LoadFunction(videodata->ime_himm32, "ImmUnlockIMC");
343 videodata->ImmLockIMCC = (LPVOID (WINAPI *)(HIMCC))SDL_LoadFunction(videodata->ime_himm32, "ImmLockIMCC");
344 videodata->ImmUnlockIMCC = (BOOL (WINAPI *)(HIMCC))SDL_LoadFunction(videodata->ime_himm32, "ImmUnlockIMCC");
345 /* *INDENT-ON* */ // clang-format on
346
347 IME_SetWindow(videodata, window);
348 videodata->ime_himc = ImmGetContext(hwnd);
349 ImmReleaseContext(hwnd, videodata->ime_himc);
350 if (!videodata->ime_himc) {
351 videodata->ime_available = false;
352 IME_Disable(videodata, hwnd);
353 return true;
354 }
355 videodata->ime_available = true;
356 IME_UpdateInputLocale(videodata);
357 IME_SetupAPI(videodata);
358 IME_UpdateInputLocale(videodata);
359 IME_Disable(videodata, hwnd);
360 return true;
361}
362
363static void IME_Enable(SDL_VideoData *videodata, HWND hwnd)
364{
365 if (!videodata->ime_initialized || !videodata->ime_hwnd_current) {
366 return;
367 }
368
369 if (!videodata->ime_available) {
370 IME_Disable(videodata, hwnd);
371 return;
372 }
373 if (videodata->ime_hwnd_current == videodata->ime_hwnd_main) {
374 ImmAssociateContext(videodata->ime_hwnd_current, videodata->ime_himc);
375 }
376
377 videodata->ime_enabled = true;
378 IME_UpdateInputLocale(videodata);
379}
380
381static void IME_Disable(SDL_VideoData *videodata, HWND hwnd)
382{
383 if (!videodata->ime_initialized || !videodata->ime_hwnd_current) {
384 return;
385 }
386
387 IME_ClearComposition(videodata);
388 if (videodata->ime_hwnd_current == videodata->ime_hwnd_main) {
389 ImmAssociateContext(videodata->ime_hwnd_current, (HIMC)0);
390 }
391
392 videodata->ime_enabled = false;
393}
394
395static void IME_Quit(SDL_VideoData *videodata)
396{
397 if (!videodata->ime_initialized) {
398 return;
399 }
400
401 if (videodata->ime_hwnd_main) {
402 ImmAssociateContext(videodata->ime_hwnd_main, videodata->ime_himc);
403 }
404
405 videodata->ime_hwnd_main = 0;
406 videodata->ime_himc = 0;
407 if (videodata->ime_himm32) {
408 SDL_UnloadObject(videodata->ime_himm32);
409 videodata->ime_himm32 = 0;
410 }
411 for (int i = 0; i < videodata->ime_candcount; ++i) {
412 SDL_free(videodata->ime_candidates[i]);
413 videodata->ime_candidates[i] = NULL;
414 }
415 videodata->ime_initialized = false;
416}
417
418static void IME_GetReadingString(SDL_VideoData *videodata, HWND hwnd)
419{
420 DWORD id = 0;
421 HIMC himc = 0;
422 WCHAR buffer[16];
423 WCHAR *s = buffer;
424 DWORD len = 0;
425 INT err = 0;
426 BOOL vertical = FALSE;
427 UINT maxuilen = 0;
428
429 videodata->ime_readingstring[0] = 0;
430
431 id = IME_GetId(videodata, 0);
432 if (!id) {
433 return;
434 }
435
436 himc = ImmGetContext(hwnd);
437 if (!himc) {
438 return;
439 }
440
441 if (videodata->GetReadingString) {
442 len = videodata->GetReadingString(himc, 0, 0, &err, &vertical, &maxuilen);
443 if (len) {
444 if (len > SDL_arraysize(buffer)) {
445 len = SDL_arraysize(buffer);
446 }
447
448 len = videodata->GetReadingString(himc, len, s, &err, &vertical, &maxuilen);
449 }
450 SDL_wcslcpy(videodata->ime_readingstring, s, len);
451 } else {
452 LPINPUTCONTEXT2 lpimc = videodata->ImmLockIMC(himc);
453 LPBYTE p = 0;
454 s = 0;
455 switch (id) {
456 case IMEID_CHT_VER42:
457 case IMEID_CHT_VER43:
458 case IMEID_CHT_VER44:
459 p = *(LPBYTE *)((LPBYTE)videodata->ImmLockIMCC(lpimc->hPrivate) + 24);
460 if (!p) {
461 break;
462 }
463
464 len = *(DWORD *)(p + 7 * 4 + 32 * 4);
465 s = (WCHAR *)(p + 56);
466 break;
467 case IMEID_CHT_VER51:
468 case IMEID_CHT_VER52:
469 case IMEID_CHS_VER53:
470 p = *(LPBYTE *)((LPBYTE)videodata->ImmLockIMCC(lpimc->hPrivate) + 4);
471 if (!p) {
472 break;
473 }
474
475 p = *(LPBYTE *)(p + 1 * 4 + 5 * 4);
476 if (!p) {
477 break;
478 }
479
480 len = *(DWORD *)(p + 1 * 4 + (16 * 2 + 2 * 4) + 5 * 4 + 16 * 2);
481 s = (WCHAR *)(p + 1 * 4 + (16 * 2 + 2 * 4) + 5 * 4);
482 break;
483 case IMEID_CHS_VER41:
484 {
485 int offset = (IME_GetId(videodata, 1) >= 0x00000002) ? 8 : 7;
486 p = *(LPBYTE *)((LPBYTE)videodata->ImmLockIMCC(lpimc->hPrivate) + offset * 4);
487 if (!p) {
488 break;
489 }
490
491 len = *(DWORD *)(p + 7 * 4 + 16 * 2 * 4);
492 s = (WCHAR *)(p + 6 * 4 + 16 * 2 * 1);
493 } break;
494 case IMEID_CHS_VER42:
495 p = *(LPBYTE *)((LPBYTE)videodata->ImmLockIMCC(lpimc->hPrivate) + 1 * 4 + 1 * 4 + 6 * 4);
496 if (!p) {
497 break;
498 }
499
500 len = *(DWORD *)(p + 1 * 4 + (16 * 2 + 2 * 4) + 5 * 4 + 16 * 2);
501 s = (WCHAR *)(p + 1 * 4 + (16 * 2 + 2 * 4) + 5 * 4);
502 break;
503 }
504 if (s) {
505 size_t size = SDL_min((size_t)(len + 1), SDL_arraysize(videodata->ime_readingstring));
506 SDL_wcslcpy(videodata->ime_readingstring, s, size);
507 }
508
509 videodata->ImmUnlockIMCC(lpimc->hPrivate);
510 videodata->ImmUnlockIMC(himc);
511 }
512 ImmReleaseContext(hwnd, himc);
513 IME_SendEditingEvent(videodata);
514}
515
516static void IME_InputLangChanged(SDL_VideoData *videodata)
517{
518 UINT lang = PRIMLANG();
519 IME_UpdateInputLocale(videodata);
520
521 IME_SetupAPI(videodata);
522 if (lang != PRIMLANG()) {
523 IME_ClearComposition(videodata);
524 }
525}
526
527static DWORD IME_GetId(SDL_VideoData *videodata, UINT uIndex)
528{
529 static HKL hklprev = 0;
530 static DWORD dwRet[2] = { 0 };
531 DWORD dwVerSize = 0;
532 DWORD dwVerHandle = 0;
533 LPVOID lpVerBuffer = 0;
534 LPVOID lpVerData = 0;
535 UINT cbVerData = 0;
536 char szTemp[256];
537 HKL hkl = 0;
538 DWORD dwLang = 0;
539 SDL_assert(uIndex < sizeof(dwRet) / sizeof(dwRet[0]));
540
541 hkl = videodata->ime_hkl;
542 if (hklprev == hkl) {
543 return dwRet[uIndex];
544 }
545 hklprev = hkl;
546
547 SDL_assert(uIndex == 0);
548 dwLang = ((DWORD_PTR)hkl & 0xffff);
549 // FIXME: What does this do?
550 if (videodata->ime_internal_candidates && dwLang == LANG_CHT) {
551 dwRet[0] = IMEID_CHT_VER_VISTA;
552 dwRet[1] = 0;
553 return dwRet[0];
554 }
555 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) {
556 dwRet[0] = dwRet[1] = 0;
557 return dwRet[0];
558 }
559 if (!ImmGetIMEFileNameA(hkl, szTemp, sizeof(szTemp) - 1)) {
560 dwRet[0] = dwRet[1] = 0;
561 return dwRet[0];
562 }
563 if (!videodata->GetReadingString) {
564#define LCID_INVARIANT MAKELCID(MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), SORT_DEFAULT)
565 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) {
566 dwRet[0] = dwRet[1] = 0;
567 return dwRet[0];
568 }
569#undef LCID_INVARIANT
570 dwVerSize = GetFileVersionInfoSizeA(szTemp, &dwVerHandle);
571 if (dwVerSize) {
572 lpVerBuffer = SDL_malloc(dwVerSize);
573 if (lpVerBuffer) {
574 if (GetFileVersionInfoA(szTemp, dwVerHandle, dwVerSize, lpVerBuffer)) {
575 if (VerQueryValueA(lpVerBuffer, "\\", &lpVerData, &cbVerData)) {
576#define pVerFixedInfo ((VS_FIXEDFILEINFO FAR *)lpVerData)
577 DWORD dwVer = pVerFixedInfo->dwFileVersionMS;
578 dwVer = (dwVer & 0x00ff0000) << 8 | (dwVer & 0x000000ff) << 16;
579 if ((videodata->GetReadingString) ||
580 ((dwLang == LANG_CHT) && (dwVer == MAKEIMEVERSION(4, 2) ||
581 dwVer == MAKEIMEVERSION(4, 3) ||
582 dwVer == MAKEIMEVERSION(4, 4) ||
583 dwVer == MAKEIMEVERSION(5, 0) ||
584 dwVer == MAKEIMEVERSION(5, 1) ||
585 dwVer == MAKEIMEVERSION(5, 2) ||
586 dwVer == MAKEIMEVERSION(6, 0))) ||
587 ((dwLang == LANG_CHS) && (dwVer == MAKEIMEVERSION(4, 1) ||
588 dwVer == MAKEIMEVERSION(4, 2) ||
589 dwVer == MAKEIMEVERSION(5, 3)))) {
590 dwRet[0] = dwVer | dwLang;
591 dwRet[1] = pVerFixedInfo->dwFileVersionLS;
592 SDL_free(lpVerBuffer);
593 return dwRet[0];
594 }
595#undef pVerFixedInfo
596 }
597 }
598 }
599 SDL_free(lpVerBuffer);
600 }
601 }
602 dwRet[0] = dwRet[1] = 0;
603 return dwRet[0];
604}
605
606static void IME_SetupAPI(SDL_VideoData *videodata)
607{
608 char ime_file[MAX_PATH + 1];
609 SDL_SharedObject *hime = 0;
610 HKL hkl = 0;
611 videodata->GetReadingString = NULL;
612 videodata->ShowReadingWindow = NULL;
613
614 hkl = videodata->ime_hkl;
615 if (!ImmGetIMEFileNameA(hkl, ime_file, sizeof(ime_file) - 1)) {
616 return;
617 }
618
619 hime = SDL_LoadObject(ime_file);
620 if (!hime) {
621 return;
622 }
623
624 /* *INDENT-OFF* */ // clang-format off
625 videodata->GetReadingString = (UINT (WINAPI *)(HIMC, UINT, LPWSTR, PINT, BOOL*, PUINT))
626 SDL_LoadFunction(hime, "GetReadingString");
627 videodata->ShowReadingWindow = (BOOL (WINAPI *)(HIMC, BOOL))
628 SDL_LoadFunction(hime, "ShowReadingWindow");
629 /* *INDENT-ON* */ // clang-format on
630
631 if (videodata->ShowReadingWindow) {
632 HIMC himc = ImmGetContext(videodata->ime_hwnd_current);
633 if (himc) {
634 videodata->ShowReadingWindow(himc, FALSE);
635 ImmReleaseContext(videodata->ime_hwnd_current, himc);
636 }
637 }
638}
639
640static void IME_SetWindow(SDL_VideoData *videodata, SDL_Window *window)
641{
642 HWND hwnd = window->internal->hwnd;
643
644 if (hwnd != videodata->ime_hwnd_current) {
645 videodata->ime_hwnd_current = hwnd;
646 SDL_zero(videodata->ime_composition_area);
647 SDL_zero(videodata->ime_candidate_area);
648 }
649
650 IME_SetTextInputArea(videodata, hwnd, &window->text_input_rect, window->text_input_cursor);
651}
652
653#endif
654
655static void IME_SetTextInputArea(SDL_VideoData *videodata, HWND hwnd, const SDL_Rect *rect, int cursor)
656{
657 HIMC himc;
658
659 himc = ImmGetContext(hwnd);
660 if (himc) {
661 COMPOSITIONFORM cof;
662 CANDIDATEFORM caf;
663 int font_height = rect->h;
664
665 LOGFONTW font;
666 if (ImmGetCompositionFontW(himc, &font)) {
667 font_height = font.lfHeight;
668 }
669
670 SDL_zero(cof);
671 cof.dwStyle = CFS_RECT;
672 cof.ptCurrentPos.x = rect->x + cursor;
673 cof.ptCurrentPos.y = rect->y + (rect->h - font_height) / 2;
674 cof.rcArea.left = rect->x;
675 cof.rcArea.right = (LONG)rect->x + rect->w;
676 cof.rcArea.top = rect->y;
677 cof.rcArea.bottom = (LONG)rect->y + rect->h;
678 if (SDL_memcmp(&cof, &videodata->ime_composition_area, sizeof(cof)) != 0) {
679 SDL_copyp(&videodata->ime_composition_area, &cof);
680 ImmSetCompositionWindow(himc, &cof);
681 }
682
683 SDL_zero(caf);
684 caf.dwIndex = 0;
685 caf.dwStyle = CFS_EXCLUDE;
686 caf.ptCurrentPos.x = rect->x + cursor;
687 caf.ptCurrentPos.y = rect->y;
688 caf.rcArea.left = rect->x;
689 caf.rcArea.right = (LONG)rect->x + rect->w;
690 caf.rcArea.top = rect->y;
691 caf.rcArea.bottom = (LONG)rect->y + rect->h;
692 if (SDL_memcmp(&caf, &videodata->ime_candidate_area, sizeof(caf)) != 0) {
693 SDL_copyp(&videodata->ime_candidate_area, &caf);
694 ImmSetCandidateWindow(himc, &caf);
695 }
696
697 ImmReleaseContext(hwnd, himc);
698 }
699}
700
701#ifndef SDL_DISABLE_WINDOWS_IME
702
703static void IME_UpdateInputLocale(SDL_VideoData *videodata)
704{
705 HKL hklnext = GetKeyboardLayout(0);
706
707 if (hklnext == videodata->ime_hkl) {
708 return;
709 }
710
711 videodata->ime_hkl = hklnext;
712 videodata->ime_horizontal_candidates = (PRIMLANG() == LANG_KOREAN || LANG() == LANG_CHS);
713 videodata->ime_candlistindexbase = (videodata->ime_hkl == CHT_HKL_DAYI) ? 0 : 1;
714}
715
716static void IME_ClearComposition(SDL_VideoData *videodata)
717{
718 HIMC himc = 0;
719 if (!videodata->ime_initialized) {
720 return;
721 }
722
723 himc = ImmGetContext(videodata->ime_hwnd_current);
724 if (!himc) {
725 return;
726 }
727
728 ImmNotifyIME(himc, NI_COMPOSITIONSTR, CPS_CANCEL, 0);
729 ImmSetCompositionString(himc, SCS_SETSTR, TEXT(""), sizeof(TCHAR), TEXT(""), sizeof(TCHAR));
730
731 ImmNotifyIME(himc, NI_CLOSECANDIDATE, 0, 0);
732 ImmReleaseContext(videodata->ime_hwnd_current, himc);
733 IME_SendClearComposition(videodata);
734}
735
736static void IME_GetCompositionString(SDL_VideoData *videodata, HIMC himc, DWORD string)
737{
738 LONG length;
739 DWORD dwLang = ((DWORD_PTR)videodata->ime_hkl & 0xffff);
740
741 videodata->ime_cursor = LOWORD(ImmGetCompositionStringW(himc, GCS_CURSORPOS, 0, 0));
742 videodata->ime_selected_start = 0;
743 videodata->ime_selected_length = 0;
744 SDL_DebugIMELog("Cursor = %d", videodata->ime_cursor);
745
746 length = ImmGetCompositionStringW(himc, string, NULL, 0);
747 if (length > 0 && videodata->ime_composition_length < length) {
748 if (videodata->ime_composition) {
749 SDL_free(videodata->ime_composition);
750 }
751
752 videodata->ime_composition = (WCHAR *)SDL_malloc(length + sizeof(WCHAR));
753 videodata->ime_composition_length = length;
754 }
755
756 length = ImmGetCompositionStringW(himc, string, videodata->ime_composition, videodata->ime_composition_length);
757 if (length < 0) {
758 length = 0;
759 }
760 length /= sizeof(WCHAR);
761
762 if ((dwLang == LANG_CHT || dwLang == LANG_CHS) &&
763 videodata->ime_cursor > 0 &&
764 videodata->ime_cursor < (int)(videodata->ime_composition_length / sizeof(WCHAR)) &&
765 (videodata->ime_composition[0] == 0x3000 || videodata->ime_composition[0] == 0x0020)) {
766 // Traditional Chinese IMEs add a placeholder U+3000
767 // Simplified Chinese IMEs seem to add a placeholder U+0020 sometimes
768 for (int i = videodata->ime_cursor + 1; i < length; ++i) {
769 videodata->ime_composition[i - 1] = videodata->ime_composition[i];
770 }
771 --length;
772 }
773
774 videodata->ime_composition[length] = 0;
775
776 length = ImmGetCompositionStringW(himc, GCS_COMPATTR, NULL, 0);
777 if (length > 0) {
778 Uint8 *attributes = (Uint8 *)SDL_malloc(length);
779 if (attributes) {
780 int start = 0;
781 int end = 0;
782
783 length = ImmGetCompositionString(himc, GCS_COMPATTR, attributes, length);
784 if (length < 0) {
785 length = 0;
786 }
787
788 for (LONG i = 0; i < length; ++i) {
789 SDL_DebugIMELog("attrib[%d] = %d", i, attributes[i]);
790 }
791
792 for (start = 0; start < length; ++start) {
793 if (attributes[start] == ATTR_TARGET_CONVERTED || attributes[start] == ATTR_TARGET_NOTCONVERTED) {
794 break;
795 }
796 }
797
798 for (end = start; end < length; ++end) {
799 if (attributes[end] != ATTR_TARGET_CONVERTED && attributes[end] != ATTR_TARGET_NOTCONVERTED) {
800 break;
801 }
802 }
803
804 if (end > start) {
805 videodata->ime_selected_start = start;
806 videodata->ime_selected_length = end - start;
807 }
808
809 SDL_free(attributes);
810 }
811 }
812}
813
814static void IME_SendInputEvent(SDL_VideoData *videodata)
815{
816 char *s = 0;
817 s = WIN_StringToUTF8W(videodata->ime_composition);
818 SDL_SendKeyboardText(s);
819 SDL_free(s);
820
821 videodata->ime_composition[0] = 0;
822 videodata->ime_readingstring[0] = 0;
823 videodata->ime_cursor = 0;
824}
825
826static void IME_SendEditingEvent(SDL_VideoData *videodata)
827{
828 char *s = NULL;
829 WCHAR *buffer = NULL;
830 size_t size = videodata->ime_composition_length;
831 if (videodata->ime_readingstring[0]) {
832 size_t len = SDL_min(SDL_wcslen(videodata->ime_composition), (size_t)videodata->ime_cursor);
833
834 size += sizeof(videodata->ime_readingstring);
835 buffer = (WCHAR *)SDL_malloc(size + sizeof(WCHAR));
836 if (!buffer) {
837 return;
838 }
839 buffer[0] = 0;
840
841 SDL_wcslcpy(buffer, videodata->ime_composition, len + 1);
842 SDL_wcslcat(buffer, videodata->ime_readingstring, size);
843 SDL_wcslcat(buffer, &videodata->ime_composition[len], size);
844 } else {
845 buffer = (WCHAR *)SDL_malloc(size + sizeof(WCHAR));
846 if (!buffer) {
847 return;
848 }
849 buffer[0] = 0;
850 SDL_wcslcpy(buffer, videodata->ime_composition, size);
851 }
852
853 s = WIN_StringToUTF8W(buffer);
854 if (s) {
855 if (videodata->ime_readingstring[0]) {
856 SDL_SendEditingText(s, videodata->ime_cursor, (int)SDL_wcslen(videodata->ime_readingstring));
857 } else if (videodata->ime_cursor == videodata->ime_selected_start) {
858 SDL_SendEditingText(s, videodata->ime_selected_start, videodata->ime_selected_length);
859 } else {
860 SDL_SendEditingText(s, videodata->ime_cursor, 0);
861 }
862 if (*s) {
863 videodata->ime_needs_clear_composition = true;
864 }
865 SDL_free(s);
866 }
867 SDL_free(buffer);
868}
869
870static void IME_SendClearComposition(SDL_VideoData *videodata)
871{
872 if (videodata->ime_needs_clear_composition) {
873 SDL_SendEditingText("", 0, 0);
874 videodata->ime_needs_clear_composition = false;
875 }
876}
877
878static bool IME_OpenCandidateList(SDL_VideoData *videodata)
879{
880 videodata->ime_candidates_open = true;
881 videodata->ime_candcount = 0;
882 return true;
883}
884
885static void IME_AddCandidate(SDL_VideoData *videodata, UINT i, LPCWSTR candidate)
886{
887 if (videodata->ime_candidates[i]) {
888 SDL_free(videodata->ime_candidates[i]);
889 videodata->ime_candidates[i] = NULL;
890 }
891
892 SDL_COMPILE_TIME_ASSERT(IME_CANDIDATE_INDEXING_REQUIRES, MAX_CANDLIST == 10);
893 char *candidate_utf8 = WIN_StringToUTF8W(candidate);
894 SDL_asprintf(&videodata->ime_candidates[i], "%d %s", ((i + videodata->ime_candlistindexbase) % 10), candidate_utf8);
895 SDL_free(candidate_utf8);
896
897 videodata->ime_candcount = (i + 1);
898}
899
900static void IME_SendCandidateList(SDL_VideoData *videodata)
901{
902 SDL_SendEditingTextCandidates(videodata->ime_candidates, videodata->ime_candcount, videodata->ime_candsel, videodata->ime_horizontal_candidates);
903}
904
905static void IME_CloseCandidateList(SDL_VideoData *videodata)
906{
907 videodata->ime_candidates_open = false;
908
909 if (videodata->ime_candcount > 0) {
910 for (int i = 0; i < videodata->ime_candcount; ++i) {
911 SDL_free(videodata->ime_candidates[i]);
912 videodata->ime_candidates[i] = NULL;
913 }
914 videodata->ime_candcount = 0;
915
916 SDL_SendEditingTextCandidates(NULL, 0, -1, false);
917 }
918}
919
920static void IME_GetCandidateList(SDL_VideoData *videodata, HWND hwnd)
921{
922 HIMC himc;
923 DWORD size;
924 LPCANDIDATELIST cand_list;
925 bool has_candidates = false;
926
927 himc = ImmGetContext(hwnd);
928 if (himc) {
929 size = ImmGetCandidateListW(himc, 0, NULL, 0);
930 if (size != 0) {
931 cand_list = (LPCANDIDATELIST)SDL_malloc(size);
932 if (cand_list != NULL) {
933 size = ImmGetCandidateListW(himc, 0, cand_list, size);
934 if (size != 0) {
935 if (IME_OpenCandidateList(videodata)) {
936 UINT i, j;
937 UINT page_start = 0;
938 UINT page_size = 0;
939
940 videodata->ime_candsel = cand_list->dwSelection;
941
942 if (LANG() == LANG_CHS && IME_GetId(videodata, 0)) {
943 const UINT maxcandchar = 18;
944 size_t cchars = 0;
945
946 for (i = 0; i < cand_list->dwCount; ++i) {
947 size_t len = SDL_wcslen((LPWSTR)((DWORD_PTR)cand_list + cand_list->dwOffset[i])) + 1;
948 if (len + cchars > maxcandchar) {
949 if (i > cand_list->dwSelection) {
950 break;
951 }
952
953 page_start = i;
954 cchars = len;
955 } else {
956 cchars += len;
957 }
958 }
959 page_size = i - page_start;
960 } else {
961 page_size = SDL_min(cand_list->dwPageSize == 0 ? MAX_CANDLIST : cand_list->dwPageSize, MAX_CANDLIST);
962 page_start = (cand_list->dwSelection / page_size) * page_size;
963 }
964 for (i = page_start, j = 0; (DWORD)i < cand_list->dwCount && j < page_size; i++, j++) {
965 LPCWSTR candidate = (LPCWSTR)((DWORD_PTR)cand_list + cand_list->dwOffset[i]);
966 IME_AddCandidate(videodata, j, candidate);
967 }
968
969 has_candidates = true;
970 IME_SendCandidateList(videodata);
971 }
972 }
973 SDL_free(cand_list);
974 }
975 }
976 ImmReleaseContext(hwnd, himc);
977 }
978
979 if (!has_candidates) {
980 IME_CloseCandidateList(videodata);
981 }
982}
983
984bool WIN_HandleIMEMessage(HWND hwnd, UINT msg, WPARAM wParam, LPARAM *lParam, SDL_VideoData *videodata)
985{
986 bool trap = false;
987 HIMC himc = 0;
988
989 if (msg == WM_IME_SETCONTEXT) {
990 SDL_DebugIMELog("WM_IME_SETCONTEXT");
991
992 LPARAM element_mask;
993 if (videodata->ime_internal_composition && videodata->ime_internal_candidates) {
994 element_mask = 0;
995 } else {
996 element_mask = ISC_SHOWUIALL;
997 if (videodata->ime_internal_composition) {
998 element_mask &= ~ISC_SHOWUICOMPOSITIONWINDOW;
999 }
1000 if (videodata->ime_internal_candidates) {
1001 element_mask &= ~ISC_SHOWUIALLCANDIDATEWINDOW;
1002 }
1003 }
1004 *lParam &= element_mask;
1005
1006 return false;
1007 }
1008
1009 if (!videodata->ime_initialized || !videodata->ime_available || !videodata->ime_enabled) {
1010 return false;
1011 }
1012
1013 switch (msg) {
1014 case WM_KEYDOWN:
1015 if (wParam == VK_PROCESSKEY) {
1016 SDL_DebugIMELog("WM_KEYDOWN VK_PROCESSKEY");
1017 trap = true;
1018 } else {
1019 SDL_DebugIMELog("WM_KEYDOWN normal");
1020 }
1021 break;
1022 case WM_INPUTLANGCHANGE:
1023 SDL_DebugIMELog("WM_INPUTLANGCHANGE");
1024 IME_InputLangChanged(videodata);
1025 break;
1026 case WM_IME_STARTCOMPOSITION:
1027 SDL_DebugIMELog("WM_IME_STARTCOMPOSITION");
1028 if (videodata->ime_internal_composition) {
1029 trap = true;
1030 }
1031 break;
1032 case WM_IME_COMPOSITION:
1033 SDL_DebugIMELog("WM_IME_COMPOSITION %x", lParam);
1034 if (videodata->ime_internal_composition) {
1035 trap = true;
1036 himc = ImmGetContext(hwnd);
1037 if (*lParam & GCS_RESULTSTR) {
1038 SDL_DebugIMELog("GCS_RESULTSTR");
1039 IME_GetCompositionString(videodata, himc, GCS_RESULTSTR);
1040 IME_SendClearComposition(videodata);
1041 IME_SendInputEvent(videodata);
1042 }
1043 if (*lParam & GCS_COMPSTR) {
1044 SDL_DebugIMELog("GCS_COMPSTR");
1045 videodata->ime_readingstring[0] = 0;
1046 IME_GetCompositionString(videodata, himc, GCS_COMPSTR);
1047 IME_SendEditingEvent(videodata);
1048 }
1049 ImmReleaseContext(hwnd, himc);
1050 }
1051 break;
1052 case WM_IME_ENDCOMPOSITION:
1053 SDL_DebugIMELog("WM_IME_ENDCOMPOSITION");
1054 if (videodata->ime_internal_composition) {
1055 trap = true;
1056 videodata->ime_composition[0] = 0;
1057 videodata->ime_readingstring[0] = 0;
1058 videodata->ime_cursor = 0;
1059 videodata->ime_selected_start = 0;
1060 videodata->ime_selected_length = 0;
1061 IME_SendClearComposition(videodata);
1062 }
1063 break;
1064 case WM_IME_NOTIFY:
1065 SDL_DebugIMELog("WM_IME_NOTIFY %x", wParam);
1066 switch (wParam) {
1067 case IMN_SETCOMPOSITIONWINDOW:
1068 SDL_DebugIMELog("IMN_SETCOMPOSITIONWINDOW");
1069 break;
1070 case IMN_SETCOMPOSITIONFONT:
1071 SDL_DebugIMELog("IMN_SETCOMPOSITIONFONT");
1072 break;
1073 case IMN_SETCANDIDATEPOS:
1074 SDL_DebugIMELog("IMN_SETCANDIDATEPOS");
1075 break;
1076 case IMN_SETCONVERSIONMODE:
1077 case IMN_SETOPENSTATUS:
1078 SDL_DebugIMELog("%s", wParam == IMN_SETCONVERSIONMODE ? "IMN_SETCONVERSIONMODE" : "IMN_SETOPENSTATUS");
1079 IME_UpdateInputLocale(videodata);
1080 break;
1081 case IMN_OPENCANDIDATE:
1082 case IMN_CHANGECANDIDATE:
1083 SDL_DebugIMELog("%s", wParam == IMN_OPENCANDIDATE ? "IMN_OPENCANDIDATE" : "IMN_CHANGECANDIDATE");
1084 if (videodata->ime_internal_candidates) {
1085 trap = true;
1086 videodata->ime_update_candidates = true;
1087 }
1088 break;
1089 case IMN_CLOSECANDIDATE:
1090 SDL_DebugIMELog("IMN_CLOSECANDIDATE");
1091 if (videodata->ime_internal_candidates) {
1092 trap = true;
1093 videodata->ime_update_candidates = false;
1094 IME_CloseCandidateList(videodata);
1095 }
1096 break;
1097 case IMN_PRIVATE:
1098 {
1099 DWORD dwId = IME_GetId(videodata, 0);
1100 SDL_DebugIMELog("IMN_PRIVATE %u", dwId);
1101 IME_GetReadingString(videodata, hwnd);
1102 switch (dwId) {
1103 case IMEID_CHT_VER42:
1104 case IMEID_CHT_VER43:
1105 case IMEID_CHT_VER44:
1106 case IMEID_CHS_VER41:
1107 case IMEID_CHS_VER42:
1108 if (*lParam == 1 || *lParam == 2) {
1109 trap = true;
1110 }
1111
1112 break;
1113 case IMEID_CHT_VER50:
1114 case IMEID_CHT_VER51:
1115 case IMEID_CHT_VER52:
1116 case IMEID_CHT_VER60:
1117 case IMEID_CHS_VER53:
1118 if (*lParam == 16 || *lParam == 17 || *lParam == 26 || *lParam == 27 || *lParam == 28) {
1119 trap = true;
1120 }
1121 break;
1122 }
1123 } break;
1124 default:
1125 trap = true;
1126 break;
1127 }
1128 break;
1129 }
1130 return trap;
1131}
1132
1133void WIN_UpdateIMECandidates(SDL_VideoDevice *_this)
1134{
1135 SDL_VideoData *videodata = _this->internal;
1136
1137 if (videodata->ime_update_candidates) {
1138 IME_GetCandidateList(videodata, videodata->ime_hwnd_current);
1139 videodata->ime_update_candidates = false;
1140 }
1141}
1142
1143#endif // SDL_DISABLE_WINDOWS_IME
1144
1145#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 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#ifndef SDL_windowskeyboard_h_
24#define SDL_windowskeyboard_h_
25
26extern void WIN_InitKeyboard(SDL_VideoDevice *_this);
27extern void WIN_UpdateKeymap(bool send_event);
28extern void WIN_QuitKeyboard(SDL_VideoDevice *_this);
29
30extern void WIN_ResetDeadKeys(void);
31
32extern bool WIN_StartTextInput(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID props);
33extern bool WIN_StopTextInput(SDL_VideoDevice *_this, SDL_Window *window);
34extern bool WIN_UpdateTextInputArea(SDL_VideoDevice *_this, SDL_Window *window);
35extern bool WIN_ClearComposition(SDL_VideoDevice *_this, SDL_Window *window);
36
37extern bool WIN_HandleIMEMessage(HWND hwnd, UINT msg, WPARAM wParam, LPARAM *lParam, struct SDL_VideoData *videodata);
38extern void WIN_UpdateIMECandidates(SDL_VideoDevice *_this);
39
40#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 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#if defined(SDL_VIDEO_DRIVER_WINDOWS) && !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
24
25#ifdef HAVE_LIMITS_H
26#include <limits.h>
27#endif
28#ifndef SIZE_MAX
29#define SIZE_MAX ((size_t)-1)
30#endif
31
32#include "../../core/windows/SDL_windows.h"
33
34#include "SDL_windowsvideo.h"
35
36#ifndef SS_EDITCONTROL
37#define SS_EDITCONTROL 0x2000
38#endif
39
40#ifndef IDOK
41#define IDOK 1
42#endif
43
44#ifndef IDCANCEL
45#define IDCANCEL 2
46#endif
47
48// Custom dialog return codes
49#define IDCLOSED 20
50#define IDINVALPTRINIT 50
51#define IDINVALPTRCOMMAND 51
52#define IDINVALPTRSETFOCUS 52
53#define IDINVALPTRDLGITEM 53
54// First button ID
55#define IDBUTTONINDEX0 100
56
57#define DLGITEMTYPEBUTTON 0x0080
58#define DLGITEMTYPESTATIC 0x0082
59
60/* Windows only sends the lower 16 bits of the control ID when a button
61 * gets clicked. There are also some predefined and custom IDs that lower
62 * the available number further. 2^16 - 101 buttons should be enough for
63 * everyone, no need to make the code more complex.
64 */
65#define MAX_BUTTONS (0xffff - 100)
66
67// Display a Windows message box
68
69typedef HRESULT(CALLBACK *PFTASKDIALOGCALLBACK)(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, LONG_PTR lpRefData);
70
71enum _TASKDIALOG_FLAGS
72{
73 TDF_ENABLE_HYPERLINKS = 0x0001,
74 TDF_USE_HICON_MAIN = 0x0002,
75 TDF_USE_HICON_FOOTER = 0x0004,
76 TDF_ALLOW_DIALOG_CANCELLATION = 0x0008,
77 TDF_USE_COMMAND_LINKS = 0x0010,
78 TDF_USE_COMMAND_LINKS_NO_ICON = 0x0020,
79 TDF_EXPAND_FOOTER_AREA = 0x0040,
80 TDF_EXPANDED_BY_DEFAULT = 0x0080,
81 TDF_VERIFICATION_FLAG_CHECKED = 0x0100,
82 TDF_SHOW_PROGRESS_BAR = 0x0200,
83 TDF_SHOW_MARQUEE_PROGRESS_BAR = 0x0400,
84 TDF_CALLBACK_TIMER = 0x0800,
85 TDF_POSITION_RELATIVE_TO_WINDOW = 0x1000,
86 TDF_RTL_LAYOUT = 0x2000,
87 TDF_NO_DEFAULT_RADIO_BUTTON = 0x4000,
88 TDF_CAN_BE_MINIMIZED = 0x8000,
89 // #if (NTDDI_VERSION >= NTDDI_WIN8)
90 TDF_NO_SET_FOREGROUND = 0x00010000, // Don't call SetForegroundWindow() when activating the dialog
91 // #endif // (NTDDI_VERSION >= NTDDI_WIN8)
92 TDF_SIZE_TO_CONTENT = 0x01000000 // used by ShellMessageBox to emulate MessageBox sizing behavior
93};
94typedef int TASKDIALOG_FLAGS; // Note: _TASKDIALOG_FLAGS is an int
95
96typedef enum _TASKDIALOG_MESSAGES
97{
98 TDM_NAVIGATE_PAGE = WM_USER + 101,
99 TDM_CLICK_BUTTON = WM_USER + 102, // wParam = Button ID
100 TDM_SET_MARQUEE_PROGRESS_BAR = WM_USER + 103, // wParam = 0 (nonMarque) wParam != 0 (Marquee)
101 TDM_SET_PROGRESS_BAR_STATE = WM_USER + 104, // wParam = new progress state
102 TDM_SET_PROGRESS_BAR_RANGE = WM_USER + 105, // lParam = MAKELPARAM(nMinRange, nMaxRange)
103 TDM_SET_PROGRESS_BAR_POS = WM_USER + 106, // wParam = new position
104 TDM_SET_PROGRESS_BAR_MARQUEE = WM_USER + 107, // wParam = 0 (stop marquee), wParam != 0 (start marquee), lparam = speed (milliseconds between repaints)
105 TDM_SET_ELEMENT_TEXT = WM_USER + 108, // wParam = element (TASKDIALOG_ELEMENTS), lParam = new element text (LPCWSTR)
106 TDM_CLICK_RADIO_BUTTON = WM_USER + 110, // wParam = Radio Button ID
107 TDM_ENABLE_BUTTON = WM_USER + 111, // lParam = 0 (disable), lParam != 0 (enable), wParam = Button ID
108 TDM_ENABLE_RADIO_BUTTON = WM_USER + 112, // lParam = 0 (disable), lParam != 0 (enable), wParam = Radio Button ID
109 TDM_CLICK_VERIFICATION = WM_USER + 113, // wParam = 0 (unchecked), 1 (checked), lParam = 1 (set key focus)
110 TDM_UPDATE_ELEMENT_TEXT = WM_USER + 114, // wParam = element (TASKDIALOG_ELEMENTS), lParam = new element text (LPCWSTR)
111 TDM_SET_BUTTON_ELEVATION_REQUIRED_STATE = WM_USER + 115, // wParam = Button ID, lParam = 0 (elevation not required), lParam != 0 (elevation required)
112 TDM_UPDATE_ICON = WM_USER + 116 // wParam = icon element (TASKDIALOG_ICON_ELEMENTS), lParam = new icon (hIcon if TDF_USE_HICON_* was set, PCWSTR otherwise)
113} TASKDIALOG_MESSAGES;
114
115typedef enum _TASKDIALOG_NOTIFICATIONS
116{
117 TDN_CREATED = 0,
118 TDN_NAVIGATED = 1,
119 TDN_BUTTON_CLICKED = 2, // wParam = Button ID
120 TDN_HYPERLINK_CLICKED = 3, // lParam = (LPCWSTR)pszHREF
121 TDN_TIMER = 4, // wParam = Milliseconds since dialog created or timer reset
122 TDN_DESTROYED = 5,
123 TDN_RADIO_BUTTON_CLICKED = 6, // wParam = Radio Button ID
124 TDN_DIALOG_CONSTRUCTED = 7,
125 TDN_VERIFICATION_CLICKED = 8, // wParam = 1 if checkbox checked, 0 if not, lParam is unused and always 0
126 TDN_HELP = 9,
127 TDN_EXPANDO_BUTTON_CLICKED = 10 // wParam = 0 (dialog is now collapsed), wParam != 0 (dialog is now expanded)
128} TASKDIALOG_NOTIFICATIONS;
129
130typedef enum _TASKDIALOG_ELEMENTS
131{
132 TDE_CONTENT,
133 TDE_EXPANDED_INFORMATION,
134 TDE_FOOTER,
135 TDE_MAIN_INSTRUCTION
136} TASKDIALOG_ELEMENTS;
137
138typedef enum _TASKDIALOG_ICON_ELEMENTS
139{
140 TDIE_ICON_MAIN,
141 TDIE_ICON_FOOTER
142} TASKDIALOG_ICON_ELEMENTS;
143
144#define TD_WARNING_ICON MAKEINTRESOURCEW(-1)
145#define TD_ERROR_ICON MAKEINTRESOURCEW(-2)
146#define TD_INFORMATION_ICON MAKEINTRESOURCEW(-3)
147#define TD_SHIELD_ICON MAKEINTRESOURCEW(-4)
148
149enum _TASKDIALOG_COMMON_BUTTON_FLAGS
150{
151 TDCBF_OK_BUTTON = 0x0001, // selected control return value IDOK
152 TDCBF_YES_BUTTON = 0x0002, // selected control return value IDYES
153 TDCBF_NO_BUTTON = 0x0004, // selected control return value IDNO
154 TDCBF_CANCEL_BUTTON = 0x0008, // selected control return value IDCANCEL
155 TDCBF_RETRY_BUTTON = 0x0010, // selected control return value IDRETRY
156 TDCBF_CLOSE_BUTTON = 0x0020 // selected control return value IDCLOSE
157};
158typedef int TASKDIALOG_COMMON_BUTTON_FLAGS; // Note: _TASKDIALOG_COMMON_BUTTON_FLAGS is an int
159
160#pragma pack(push, 1)
161
162typedef struct _TASKDIALOG_BUTTON
163{
164 int nButtonID;
165 PCWSTR pszButtonText;
166} TASKDIALOG_BUTTON;
167
168typedef struct _TASKDIALOGCONFIG
169{
170 UINT cbSize;
171 HWND hwndParent; // incorrectly named, this is the owner window, not a parent.
172 HINSTANCE hInstance; // used for MAKEINTRESOURCE() strings
173 TASKDIALOG_FLAGS dwFlags; // TASKDIALOG_FLAGS (TDF_XXX) flags
174 TASKDIALOG_COMMON_BUTTON_FLAGS dwCommonButtons; // TASKDIALOG_COMMON_BUTTON (TDCBF_XXX) flags
175 PCWSTR pszWindowTitle; // string or MAKEINTRESOURCE()
176 union
177 {
178 HICON hMainIcon;
179 PCWSTR pszMainIcon;
180 } /*DUMMYUNIONNAME*/;
181 PCWSTR pszMainInstruction;
182 PCWSTR pszContent;
183 UINT cButtons;
184 const TASKDIALOG_BUTTON *pButtons;
185 int nDefaultButton;
186 UINT cRadioButtons;
187 const TASKDIALOG_BUTTON *pRadioButtons;
188 int nDefaultRadioButton;
189 PCWSTR pszVerificationText;
190 PCWSTR pszExpandedInformation;
191 PCWSTR pszExpandedControlText;
192 PCWSTR pszCollapsedControlText;
193 union
194 {
195 HICON hFooterIcon;
196 PCWSTR pszFooterIcon;
197 } /*DUMMYUNIONNAME2*/;
198 PCWSTR pszFooter;
199 PFTASKDIALOGCALLBACK pfCallback;
200 LONG_PTR lpCallbackData;
201 UINT cxWidth; // width of the Task Dialog's client area in DLU's. If 0, Task Dialog will calculate the ideal width.
202} TASKDIALOGCONFIG;
203
204typedef struct
205{
206 WORD dlgVer;
207 WORD signature;
208 DWORD helpID;
209 DWORD exStyle;
210 DWORD style;
211 WORD cDlgItems;
212 short x;
213 short y;
214 short cx;
215 short cy;
216} DLGTEMPLATEEX;
217
218typedef struct
219{
220 DWORD helpID;
221 DWORD exStyle;
222 DWORD style;
223 short x;
224 short y;
225 short cx;
226 short cy;
227 DWORD id;
228} DLGITEMTEMPLATEEX;
229
230#pragma pack(pop)
231
232typedef struct
233{
234 DLGTEMPLATEEX *lpDialog;
235 void *data;
236 size_t size;
237 size_t used;
238 WORD numbuttons;
239} WIN_DialogData;
240
241static bool GetButtonIndex(const SDL_MessageBoxData *messageboxdata, SDL_MessageBoxButtonFlags flags, size_t *i)
242{
243 for (*i = 0; *i < (size_t)messageboxdata->numbuttons; ++*i) {
244 if (messageboxdata->buttons[*i].flags & flags) {
245 return true;
246 }
247 }
248 return false;
249}
250
251static INT_PTR CALLBACK MessageBoxDialogProc(HWND hDlg, UINT iMessage, WPARAM wParam, LPARAM lParam)
252{
253 const SDL_MessageBoxData *messageboxdata;
254 size_t buttonindex;
255
256 switch (iMessage) {
257 case WM_INITDIALOG:
258 if (lParam == 0) {
259 EndDialog(hDlg, IDINVALPTRINIT);
260 return TRUE;
261 }
262 messageboxdata = (const SDL_MessageBoxData *)lParam;
263 SetWindowLongPtr(hDlg, GWLP_USERDATA, lParam);
264
265 if (GetButtonIndex(messageboxdata, SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT, &buttonindex)) {
266 // Focus on the first default return-key button
267 HWND buttonctl = GetDlgItem(hDlg, (int)(IDBUTTONINDEX0 + buttonindex));
268 if (!buttonctl) {
269 EndDialog(hDlg, IDINVALPTRDLGITEM);
270 }
271 PostMessage(hDlg, WM_NEXTDLGCTL, (WPARAM)buttonctl, TRUE);
272 } else {
273 // Give the focus to the dialog window instead
274 SetFocus(hDlg);
275 }
276 return FALSE;
277 case WM_SETFOCUS:
278 messageboxdata = (const SDL_MessageBoxData *)GetWindowLongPtr(hDlg, GWLP_USERDATA);
279 if (!messageboxdata) {
280 EndDialog(hDlg, IDINVALPTRSETFOCUS);
281 return TRUE;
282 }
283
284 // Let the default button be focused if there is one. Otherwise, prevent any initial focus.
285 if (GetButtonIndex(messageboxdata, SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT, &buttonindex)) {
286 return FALSE;
287 }
288 return TRUE;
289 case WM_COMMAND:
290 messageboxdata = (const SDL_MessageBoxData *)GetWindowLongPtr(hDlg, GWLP_USERDATA);
291 if (!messageboxdata) {
292 EndDialog(hDlg, IDINVALPTRCOMMAND);
293 return TRUE;
294 }
295
296 // Return the ID of the button that was pushed
297 if (wParam == IDOK) {
298 if (GetButtonIndex(messageboxdata, SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT, &buttonindex)) {
299 EndDialog(hDlg, IDBUTTONINDEX0 + buttonindex);
300 }
301 } else if (wParam == IDCANCEL) {
302 if (GetButtonIndex(messageboxdata, SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT, &buttonindex)) {
303 EndDialog(hDlg, IDBUTTONINDEX0 + buttonindex);
304 } else {
305 // Closing of window was requested by user or system. It would be rude not to comply.
306 EndDialog(hDlg, IDCLOSED);
307 }
308 } else if (wParam >= IDBUTTONINDEX0 && (int)wParam - IDBUTTONINDEX0 < messageboxdata->numbuttons) {
309 EndDialog(hDlg, wParam);
310 }
311 return TRUE;
312
313 default:
314 break;
315 }
316 return FALSE;
317}
318
319static bool ExpandDialogSpace(WIN_DialogData *dialog, size_t space)
320{
321 // Growing memory in 64 KiB steps.
322 const size_t sizestep = 0x10000;
323 size_t size = dialog->size;
324
325 if (size == 0) {
326 // Start with 4 KiB or a multiple of 64 KiB to fit the data.
327 size = 0x1000;
328 if (SIZE_MAX - sizestep < space) {
329 size = space;
330 } else if (space > size) {
331 size = (space + sizestep) & ~(sizestep - 1);
332 }
333 } else if (SIZE_MAX - dialog->used < space) {
334 SDL_OutOfMemory();
335 return false;
336 } else if (SIZE_MAX - (dialog->used + space) < sizestep) {
337 // Close to the maximum.
338 size = dialog->used + space;
339 } else if (size < dialog->used + space) {
340 // Round up to the next 64 KiB block.
341 size = dialog->used + space;
342 size += sizestep - size % sizestep;
343 }
344
345 if (size > dialog->size) {
346 void *data = SDL_realloc(dialog->data, size);
347 if (!data) {
348 return false;
349 }
350 dialog->data = data;
351 dialog->size = size;
352 dialog->lpDialog = (DLGTEMPLATEEX *)dialog->data;
353 }
354 return true;
355}
356
357static bool AlignDialogData(WIN_DialogData *dialog, size_t size)
358{
359 size_t padding = (dialog->used % size);
360
361 if (!ExpandDialogSpace(dialog, padding)) {
362 return false;
363 }
364
365 dialog->used += padding;
366
367 return true;
368}
369
370static bool AddDialogData(WIN_DialogData *dialog, const void *data, size_t size)
371{
372 if (!ExpandDialogSpace(dialog, size)) {
373 return false;
374 }
375
376 SDL_memcpy((Uint8 *)dialog->data + dialog->used, data, size);
377 dialog->used += size;
378
379 return true;
380}
381
382static bool AddDialogString(WIN_DialogData *dialog, const char *string)
383{
384 WCHAR *wstring;
385 WCHAR *p;
386 size_t count;
387 bool status;
388
389 if (!string) {
390 string = "";
391 }
392
393 wstring = WIN_UTF8ToStringW(string);
394 if (!wstring) {
395 return false;
396 }
397
398 // Find out how many characters we have, including null terminator
399 count = 0;
400 for (p = wstring; *p; ++p) {
401 ++count;
402 }
403 ++count;
404
405 status = AddDialogData(dialog, wstring, count * sizeof(WCHAR));
406 SDL_free(wstring);
407 return status;
408}
409
410static int s_BaseUnitsX;
411static int s_BaseUnitsY;
412static void Vec2ToDLU(short *x, short *y)
413{
414 SDL_assert(s_BaseUnitsX != 0); // we init in WIN_ShowMessageBox(), which is the only public function...
415
416 *x = (short)MulDiv(*x, 4, s_BaseUnitsX);
417 *y = (short)MulDiv(*y, 8, s_BaseUnitsY);
418}
419
420static 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)
421{
422 DLGITEMTEMPLATEEX item;
423 WORD marker = 0xFFFF;
424 WORD extraData = 0;
425
426 SDL_zero(item);
427 item.style = style;
428 item.exStyle = exStyle;
429 item.x = (short)x;
430 item.y = (short)y;
431 item.cx = (short)w;
432 item.cy = (short)h;
433 item.id = id;
434
435 Vec2ToDLU(&item.x, &item.y);
436 Vec2ToDLU(&item.cx, &item.cy);
437
438 if (!AlignDialogData(dialog, sizeof(DWORD))) {
439 return false;
440 }
441 if (!AddDialogData(dialog, &item, sizeof(item))) {
442 return false;
443 }
444 if (!AddDialogData(dialog, &marker, sizeof(marker))) {
445 return false;
446 }
447 if (!AddDialogData(dialog, &type, sizeof(type))) {
448 return false;
449 }
450 if (type == DLGITEMTYPEBUTTON || (type == DLGITEMTYPESTATIC && caption)) {
451 if (!AddDialogString(dialog, caption)) {
452 return false;
453 }
454 } else {
455 if (!AddDialogData(dialog, &marker, sizeof(marker))) {
456 return false;
457 }
458 if (!AddDialogData(dialog, &ordinal, sizeof(ordinal))) {
459 return false;
460 }
461 }
462 if (!AddDialogData(dialog, &extraData, sizeof(extraData))) {
463 return false;
464 }
465 if (type == DLGITEMTYPEBUTTON) {
466 dialog->numbuttons++;
467 }
468 ++dialog->lpDialog->cDlgItems;
469
470 return true;
471}
472
473static bool AddDialogStaticText(WIN_DialogData *dialog, int x, int y, int w, int h, const char *text)
474{
475 DWORD style = WS_VISIBLE | WS_CHILD | SS_LEFT | SS_NOPREFIX | SS_EDITCONTROL | WS_GROUP;
476 return AddDialogControl(dialog, DLGITEMTYPESTATIC, style, 0, x, y, w, h, -1, text, 0);
477}
478
479static bool AddDialogStaticIcon(WIN_DialogData *dialog, int x, int y, int w, int h, Uint16 ordinal)
480{
481 DWORD style = WS_VISIBLE | WS_CHILD | SS_ICON | WS_GROUP;
482 return AddDialogControl(dialog, DLGITEMTYPESTATIC, style, 0, x, y, w, h, -2, NULL, ordinal);
483}
484
485static bool AddDialogButton(WIN_DialogData *dialog, int x, int y, int w, int h, const char *text, int id, bool isDefault)
486{
487 DWORD style = WS_VISIBLE | WS_CHILD | WS_TABSTOP;
488 if (isDefault) {
489 style |= BS_DEFPUSHBUTTON;
490 } else {
491 style |= BS_PUSHBUTTON;
492 }
493 // The first button marks the start of the group.
494 if (dialog->numbuttons == 0) {
495 style |= WS_GROUP;
496 }
497 return AddDialogControl(dialog, DLGITEMTYPEBUTTON, style, 0, x, y, w, h, id, text, 0);
498}
499
500static void FreeDialogData(WIN_DialogData *dialog)
501{
502 SDL_free(dialog->data);
503 SDL_free(dialog);
504}
505
506static WIN_DialogData *CreateDialogData(int w, int h, const char *caption)
507{
508 WIN_DialogData *dialog;
509 DLGTEMPLATEEX dialogTemplate;
510 WORD WordToPass;
511
512 SDL_zero(dialogTemplate);
513 dialogTemplate.dlgVer = 1;
514 dialogTemplate.signature = 0xffff;
515 dialogTemplate.style = (WS_CAPTION | DS_CENTER | DS_SHELLFONT);
516 dialogTemplate.x = 0;
517 dialogTemplate.y = 0;
518 dialogTemplate.cx = (short)w;
519 dialogTemplate.cy = (short)h;
520 Vec2ToDLU(&dialogTemplate.cx, &dialogTemplate.cy);
521
522 dialog = (WIN_DialogData *)SDL_calloc(1, sizeof(*dialog));
523 if (!dialog) {
524 return NULL;
525 }
526
527 if (!AddDialogData(dialog, &dialogTemplate, sizeof(dialogTemplate))) {
528 FreeDialogData(dialog);
529 return NULL;
530 }
531
532 // No menu
533 WordToPass = 0;
534 if (!AddDialogData(dialog, &WordToPass, 2)) {
535 FreeDialogData(dialog);
536 return NULL;
537 }
538
539 // No custom class
540 if (!AddDialogData(dialog, &WordToPass, 2)) {
541 FreeDialogData(dialog);
542 return NULL;
543 }
544
545 // title
546 if (!AddDialogString(dialog, caption)) {
547 FreeDialogData(dialog);
548 return NULL;
549 }
550
551 // Font stuff
552 {
553 /*
554 * We want to use the system messagebox font.
555 */
556 BYTE ToPass;
557
558 NONCLIENTMETRICSA NCM;
559 NCM.cbSize = sizeof(NCM);
560 SystemParametersInfoA(SPI_GETNONCLIENTMETRICS, 0, &NCM, 0);
561
562 // Font size - convert to logical font size for dialog parameter.
563 {
564 HDC ScreenDC = GetDC(NULL);
565 int LogicalPixelsY = GetDeviceCaps(ScreenDC, LOGPIXELSY);
566 if (!LogicalPixelsY) {
567 LogicalPixelsY = 72; // This can happen if the application runs out of GDI handles
568 }
569
570 WordToPass = (WORD)(-72 * NCM.lfMessageFont.lfHeight / LogicalPixelsY);
571 ReleaseDC(NULL, ScreenDC);
572 }
573
574 if (!AddDialogData(dialog, &WordToPass, 2)) {
575 FreeDialogData(dialog);
576 return NULL;
577 }
578
579 // Font weight
580 WordToPass = (WORD)NCM.lfMessageFont.lfWeight;
581 if (!AddDialogData(dialog, &WordToPass, 2)) {
582 FreeDialogData(dialog);
583 return NULL;
584 }
585
586 // italic?
587 ToPass = NCM.lfMessageFont.lfItalic;
588 if (!AddDialogData(dialog, &ToPass, 1)) {
589 FreeDialogData(dialog);
590 return NULL;
591 }
592
593 // charset?
594 ToPass = NCM.lfMessageFont.lfCharSet;
595 if (!AddDialogData(dialog, &ToPass, 1)) {
596 FreeDialogData(dialog);
597 return NULL;
598 }
599
600 // font typeface.
601 if (!AddDialogString(dialog, NCM.lfMessageFont.lfFaceName)) {
602 FreeDialogData(dialog);
603 return NULL;
604 }
605 }
606
607 return dialog;
608}
609
610/* Escaping ampersands is necessary to disable mnemonics in dialog controls.
611 * The caller provides a char** for dst and a size_t* for dstlen where the
612 * address of the work buffer and its size will be stored. Their values must be
613 * NULL and 0 on the first call. src is the string to be escaped. On error, the
614 * function returns NULL and, on success, returns a pointer to the escaped
615 * sequence as a read-only string that is valid until the next call or until the
616 * work buffer is freed. Once all strings have been processed, it's the caller's
617 * responsibility to free the work buffer with SDL_free, even on errors.
618 */
619static const char *EscapeAmpersands(char **dst, size_t *dstlen, const char *src)
620{
621 char *newdst;
622 size_t ampcount = 0;
623 size_t srclen = 0;
624
625 if (!src) {
626 return NULL;
627 }
628
629 while (src[srclen]) {
630 if (src[srclen] == '&') {
631 ampcount++;
632 }
633 srclen++;
634 }
635 srclen++;
636
637 if (ampcount == 0) {
638 // Nothing to do.
639 return src;
640 }
641 if (SIZE_MAX - srclen < ampcount) {
642 return NULL;
643 }
644 if (!*dst || *dstlen < srclen + ampcount) {
645 // Allocating extra space in case the next strings are a bit longer.
646 size_t extraspace = SIZE_MAX - (srclen + ampcount);
647 if (extraspace > 512) {
648 extraspace = 512;
649 }
650 *dstlen = srclen + ampcount + extraspace;
651 SDL_free(*dst);
652 *dst = NULL;
653 newdst = (char *)SDL_malloc(*dstlen);
654 if (!newdst) {
655 return NULL;
656 }
657 *dst = newdst;
658 } else {
659 newdst = *dst;
660 }
661
662 // The escape character is the ampersand itself.
663 while (srclen--) {
664 if (*src == '&') {
665 *newdst++ = '&';
666 }
667 *newdst++ = *src++;
668 }
669
670 return *dst;
671}
672
673static float WIN_GetContentScale(void)
674{
675 int dpi = 0;
676
677#if 0 // We don't know what monitor the dialog will be shown on
678 UINT hdpi_uint, vdpi_uint;
679 if (GetDpiForMonitor(hMonitor, MDT_EFFECTIVE_DPI, &hdpi_uint, &vdpi_uint) == S_OK) {
680 dpi = (int)hdpi_uint;
681 }
682#endif
683 if (dpi == 0) {
684 // Window 8.0 and below: same DPI for all monitors
685 HDC hdc = GetDC(NULL);
686 if (hdc) {
687 dpi = GetDeviceCaps(hdc, LOGPIXELSX);
688 ReleaseDC(NULL, hdc);
689 }
690 }
691 if (dpi == 0) {
692 // Safe default
693 dpi = USER_DEFAULT_SCREEN_DPI;
694 }
695 return dpi / (float)USER_DEFAULT_SCREEN_DPI;
696}
697
698// This function is called if a Task Dialog is unsupported.
699static bool WIN_ShowOldMessageBox(const SDL_MessageBoxData *messageboxdata, int *buttonID)
700{
701 WIN_DialogData *dialog;
702 int i, x, y;
703 HFONT DialogFont;
704 SIZE Size;
705 RECT TextSize;
706 wchar_t *wmessage;
707 TEXTMETRIC TM;
708 HDC FontDC;
709 INT_PTR rc;
710 char *ampescape = NULL;
711 size_t ampescapesize = 0;
712 Uint16 defbuttoncount = 0;
713 Uint16 icon = 0;
714 bool result;
715
716 HWND ParentWindow = NULL;
717
718 const float scale = WIN_GetContentScale();
719 const int ButtonWidth = (int)SDL_roundf(88 * scale);
720 const int ButtonHeight = (int)SDL_roundf(26 * scale);
721 const int TextMargin = (int)SDL_roundf(16 * scale);
722 const int ButtonMargin = (int)SDL_roundf(12 * scale);
723 const int IconWidth = GetSystemMetrics(SM_CXICON);
724 const int IconHeight = GetSystemMetrics(SM_CYICON);
725 const int IconMargin = (int)SDL_roundf(20 * scale);
726
727 if (messageboxdata->numbuttons > MAX_BUTTONS) {
728 return SDL_SetError("Number of buttons exceeds limit of %d", MAX_BUTTONS);
729 }
730
731 switch (messageboxdata->flags & (SDL_MESSAGEBOX_ERROR | SDL_MESSAGEBOX_WARNING | SDL_MESSAGEBOX_INFORMATION)) {
732 case SDL_MESSAGEBOX_ERROR:
733 icon = (Uint16)(size_t)IDI_ERROR;
734 break;
735 case SDL_MESSAGEBOX_WARNING:
736 icon = (Uint16)(size_t)IDI_WARNING;
737 break;
738 case SDL_MESSAGEBOX_INFORMATION:
739 icon = (Uint16)(size_t)IDI_INFORMATION;
740 break;
741 }
742
743 /* Jan 25th, 2013 - dant@fleetsa.com
744 *
745 * I've tried to make this more reasonable, but I've run in to a lot
746 * of nonsense.
747 *
748 * The original issue is the code was written in pixels and not
749 * dialog units (DLUs). All DialogBox functions use DLUs, which
750 * vary based on the selected font (yay).
751 *
752 * According to MSDN, the most reliable way to convert is via
753 * MapDialogUnits, which requires an HWND, which we don't have
754 * at time of template creation.
755 *
756 * We do however have:
757 * The system font (DLU width 8 for me)
758 * The font we select for the dialog (DLU width 6 for me)
759 *
760 * Based on experimentation, *neither* of these return the value
761 * actually used. Stepping in to MapDialogUnits(), the conversion
762 * is fairly clear, and uses 7 for me.
763 *
764 * As a result, some of this is hacky to ensure the sizing is
765 * somewhat correct.
766 *
767 * Honestly, a long term solution is to use CreateWindow, not CreateDialog.
768 *
769 * In order to get text dimensions we need to have a DC with the desired font.
770 * I'm assuming a dialog box in SDL is rare enough we can to the create.
771 */
772 FontDC = CreateCompatibleDC(0);
773
774 {
775 // Create a duplicate of the font used in system message boxes.
776 LOGFONT lf;
777 NONCLIENTMETRICS NCM;
778 NCM.cbSize = sizeof(NCM);
779 SystemParametersInfo(SPI_GETNONCLIENTMETRICS, 0, &NCM, 0);
780 lf = NCM.lfMessageFont;
781 DialogFont = CreateFontIndirect(&lf);
782 }
783
784 // Select the font in to our DC
785 SelectObject(FontDC, DialogFont);
786
787 {
788 // Get the metrics to try and figure our DLU conversion.
789 GetTextMetrics(FontDC, &TM);
790
791 /* Calculation from the following documentation:
792 * https://support.microsoft.com/en-gb/help/125681/how-to-calculate-dialog-base-units-with-non-system-based-font
793 * This fixes bug 2137, dialog box calculation with a fixed-width system font
794 */
795 {
796 SIZE extent;
797 GetTextExtentPoint32A(FontDC, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz", 52, &extent);
798 s_BaseUnitsX = (extent.cx / 26 + 1) / 2;
799 }
800 // s_BaseUnitsX = TM.tmAveCharWidth + 1;
801 s_BaseUnitsY = TM.tmHeight;
802 }
803
804 /* Measure the *pixel* size of the string. */
805 wmessage = WIN_UTF8ToStringW(messageboxdata->message);
806 SDL_zero(TextSize);
807 DrawTextW(FontDC, wmessage, -1, &TextSize, DT_CALCRECT | DT_LEFT | DT_NOPREFIX | DT_EDITCONTROL);
808
809 // Add margins and some padding for hangs, etc.
810 TextSize.left += TextMargin;
811 TextSize.right += TextMargin + 2;
812 TextSize.top += TextMargin;
813 TextSize.bottom += TextMargin + 2;
814
815 // Done with the DC, and the string
816 DeleteDC(FontDC);
817 SDL_free(wmessage);
818
819 // Increase the size of the dialog by some border spacing around the text.
820 Size.cx = TextSize.right - TextSize.left;
821 Size.cy = TextSize.bottom - TextSize.top;
822 Size.cx += TextMargin * 2;
823 Size.cy += TextMargin * 2;
824
825 // Make dialog wider and shift text over for the icon.
826 if (icon) {
827 Size.cx += IconMargin + IconWidth;
828 TextSize.left += IconMargin + IconWidth;
829 TextSize.right += IconMargin + IconWidth;
830 }
831
832 // Ensure the size is wide enough for all of the buttons.
833 if (Size.cx < (LONG)messageboxdata->numbuttons * (ButtonWidth + ButtonMargin) + ButtonMargin) {
834 Size.cx = (LONG)messageboxdata->numbuttons * (ButtonWidth + ButtonMargin) + ButtonMargin;
835 }
836
837 // Reset the height to the icon size if it is actually bigger than the text.
838 if (icon && Size.cy < (LONG)IconMargin * 2 + IconHeight) {
839 Size.cy = (LONG)IconMargin * 2 + IconHeight;
840 }
841
842 // Add vertical space for the buttons and border.
843 Size.cy += ButtonHeight + TextMargin;
844
845 dialog = CreateDialogData(Size.cx, Size.cy, messageboxdata->title);
846 if (!dialog) {
847 return false;
848 }
849
850 if (icon && !AddDialogStaticIcon(dialog, IconMargin, IconMargin, IconWidth, IconHeight, icon)) {
851 FreeDialogData(dialog);
852 return false;
853 }
854
855 if (!AddDialogStaticText(dialog, TextSize.left, TextSize.top, TextSize.right - TextSize.left, TextSize.bottom - TextSize.top, messageboxdata->message)) {
856 FreeDialogData(dialog);
857 return false;
858 }
859
860 // Align the buttons to the right/bottom.
861 x = Size.cx - (ButtonWidth + ButtonMargin) * messageboxdata->numbuttons;
862 y = Size.cy - ButtonHeight - ButtonMargin;
863 for (i = 0; i < messageboxdata->numbuttons; i++) {
864 bool isdefault = false;
865 const char *buttontext;
866 const SDL_MessageBoxButtonData *sdlButton;
867
868 /* We always have to create the dialog buttons from left to right
869 * so that the tab order is correct. Select the info to use
870 * depending on which order was requested. */
871 if (messageboxdata->flags & SDL_MESSAGEBOX_BUTTONS_LEFT_TO_RIGHT) {
872 sdlButton = &messageboxdata->buttons[i];
873 } else {
874 sdlButton = &messageboxdata->buttons[messageboxdata->numbuttons - 1 - i];
875 }
876
877 if (sdlButton->flags & SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT) {
878 defbuttoncount++;
879 if (defbuttoncount == 1) {
880 isdefault = true;
881 }
882 }
883
884 buttontext = EscapeAmpersands(&ampescape, &ampescapesize, sdlButton->text);
885 /* Make sure to provide the correct ID to keep buttons indexed in the
886 * same order as how they are in messageboxdata. */
887 if (!buttontext || !AddDialogButton(dialog, x, y, ButtonWidth, ButtonHeight, buttontext, IDBUTTONINDEX0 + (int)(sdlButton - messageboxdata->buttons), isdefault)) {
888 FreeDialogData(dialog);
889 SDL_free(ampescape);
890 return false;
891 }
892
893 x += ButtonWidth + ButtonMargin;
894 }
895 SDL_free(ampescape);
896
897 /* If we have a parent window, get the Instance and HWND for them
898 * so that our little dialog gets exclusive focus at all times. */
899 if (messageboxdata->window) {
900 ParentWindow = messageboxdata->window->internal->hwnd;
901 }
902
903 rc = DialogBoxIndirectParam(NULL, (DLGTEMPLATE *)dialog->lpDialog, ParentWindow, MessageBoxDialogProc, (LPARAM)messageboxdata);
904 if (rc >= IDBUTTONINDEX0 && rc - IDBUTTONINDEX0 < messageboxdata->numbuttons) {
905 *buttonID = messageboxdata->buttons[rc - IDBUTTONINDEX0].buttonID;
906 result = true;
907 } else if (rc == IDCLOSED) {
908 // Dialog window closed by user or system.
909 // This could use a special return code.
910 result = true;
911 *buttonID = -1;
912 } else {
913 if (rc == 0) {
914 SDL_SetError("Invalid parent window handle");
915 } else if (rc == -1) {
916 SDL_SetError("The message box encountered an error.");
917 } else if (rc == IDINVALPTRINIT || rc == IDINVALPTRSETFOCUS || rc == IDINVALPTRCOMMAND) {
918 SDL_SetError("Invalid message box pointer in dialog procedure");
919 } else if (rc == IDINVALPTRDLGITEM) {
920 SDL_SetError("Couldn't find dialog control of the default enter-key button");
921 } else {
922 SDL_SetError("An unknown error occurred");
923 }
924 result = false;
925 }
926
927 FreeDialogData(dialog);
928 return result;
929}
930
931/* TaskDialogIndirect procedure
932 * This is because SDL targets Windows XP (0x501), so this is not defined in the platform SDK.
933 */
934/* *INDENT-OFF* */ // clang-format off
935typedef HRESULT (FAR WINAPI *TASKDIALOGINDIRECTPROC)(const TASKDIALOGCONFIG *pTaskConfig, int *pnButton, int *pnRadioButton, BOOL *pfVerificationFlagChecked);
936/* *INDENT-ON* */ // clang-format on
937
938bool WIN_ShowMessageBox(const SDL_MessageBoxData *messageboxdata, int *buttonID)
939{
940 HWND ParentWindow = NULL;
941 wchar_t *wmessage;
942 wchar_t *wtitle;
943 TASKDIALOGCONFIG TaskConfig;
944 TASKDIALOG_BUTTON *pButtons;
945 TASKDIALOG_BUTTON *pButton;
946 HMODULE hComctl32;
947 TASKDIALOGINDIRECTPROC pfnTaskDialogIndirect;
948 HRESULT hr;
949 char *ampescape = NULL;
950 size_t ampescapesize = 0;
951 int nButton;
952 int nCancelButton;
953 int i;
954 bool result = false;
955
956 if (SIZE_MAX / sizeof(TASKDIALOG_BUTTON) < messageboxdata->numbuttons) {
957 return SDL_OutOfMemory();
958 }
959
960 HMODULE hUser32 = GetModuleHandle(TEXT("user32.dll"));
961 typedef DPI_AWARENESS_CONTEXT (WINAPI * SetThreadDpiAwarenessContext_t)(DPI_AWARENESS_CONTEXT);
962 SetThreadDpiAwarenessContext_t SetThreadDpiAwarenessContextFunc = (SetThreadDpiAwarenessContext_t)GetProcAddress(hUser32, "SetThreadDpiAwarenessContext");
963 DPI_AWARENESS_CONTEXT previous_context = DPI_AWARENESS_CONTEXT_UNAWARE;
964 if (SetThreadDpiAwarenessContextFunc) {
965 previous_context = SetThreadDpiAwarenessContextFunc(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);
966 }
967
968 // If we cannot load comctl32.dll use the old messagebox!
969 hComctl32 = LoadLibrary(TEXT("comctl32.dll"));
970 if (!hComctl32) {
971 result = WIN_ShowOldMessageBox(messageboxdata, buttonID);
972 goto done;
973 }
974
975 /* If TaskDialogIndirect doesn't exist use the old messagebox!
976 This will fail prior to Windows Vista.
977 The manifest file in the application may require targeting version 6 of comctl32.dll, even
978 when we use LoadLibrary here!
979 If you don't want to bother with manifests, put this #pragma in your app's source code somewhere:
980 #pragma comment(linker,"\"/manifestdependency:type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"")
981 */
982 pfnTaskDialogIndirect = (TASKDIALOGINDIRECTPROC)GetProcAddress(hComctl32, "TaskDialogIndirect");
983 if (!pfnTaskDialogIndirect) {
984 FreeLibrary(hComctl32);
985 result = WIN_ShowOldMessageBox(messageboxdata, buttonID);
986 goto done;
987 }
988
989 /* If we have a parent window, get the Instance and HWND for them
990 so that our little dialog gets exclusive focus at all times. */
991 if (messageboxdata->window) {
992 ParentWindow = messageboxdata->window->internal->hwnd;
993 }
994
995 wmessage = WIN_UTF8ToStringW(messageboxdata->message);
996 wtitle = WIN_UTF8ToStringW(messageboxdata->title);
997
998 SDL_zero(TaskConfig);
999 TaskConfig.cbSize = sizeof(TASKDIALOGCONFIG);
1000 TaskConfig.hwndParent = ParentWindow;
1001 TaskConfig.dwFlags = TDF_SIZE_TO_CONTENT;
1002 TaskConfig.pszWindowTitle = wtitle;
1003 if (messageboxdata->flags & SDL_MESSAGEBOX_ERROR) {
1004 TaskConfig.pszMainIcon = TD_ERROR_ICON;
1005 } else if (messageboxdata->flags & SDL_MESSAGEBOX_WARNING) {
1006 TaskConfig.pszMainIcon = TD_WARNING_ICON;
1007 } else if (messageboxdata->flags & SDL_MESSAGEBOX_INFORMATION) {
1008 TaskConfig.pszMainIcon = TD_INFORMATION_ICON;
1009 } else {
1010 TaskConfig.pszMainIcon = NULL;
1011 }
1012
1013 TaskConfig.pszContent = wmessage;
1014 TaskConfig.cButtons = messageboxdata->numbuttons;
1015 pButtons = (TASKDIALOG_BUTTON *)SDL_malloc(sizeof(TASKDIALOG_BUTTON) * messageboxdata->numbuttons);
1016 TaskConfig.nDefaultButton = 0;
1017 nCancelButton = 0;
1018 for (i = 0; i < messageboxdata->numbuttons; i++) {
1019 const char *buttontext;
1020 if (messageboxdata->flags & SDL_MESSAGEBOX_BUTTONS_LEFT_TO_RIGHT) {
1021 pButton = &pButtons[i];
1022 } else {
1023 pButton = &pButtons[messageboxdata->numbuttons - 1 - i];
1024 }
1025 if (messageboxdata->buttons[i].flags & SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT) {
1026 nCancelButton = messageboxdata->buttons[i].buttonID;
1027 pButton->nButtonID = IDCANCEL;
1028 } else {
1029 pButton->nButtonID = IDBUTTONINDEX0 + i;
1030 }
1031 buttontext = EscapeAmpersands(&ampescape, &ampescapesize, messageboxdata->buttons[i].text);
1032 if (!buttontext) {
1033 int j;
1034 FreeLibrary(hComctl32);
1035 SDL_free(ampescape);
1036 SDL_free(wmessage);
1037 SDL_free(wtitle);
1038 for (j = 0; j < i; j++) {
1039 SDL_free((wchar_t *)pButtons[j].pszButtonText);
1040 }
1041 SDL_free(pButtons);
1042 return false;
1043 }
1044 pButton->pszButtonText = WIN_UTF8ToStringW(buttontext);
1045 if (messageboxdata->buttons[i].flags & SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT) {
1046 TaskConfig.nDefaultButton = pButton->nButtonID;
1047 }
1048 }
1049 TaskConfig.pButtons = pButtons;
1050
1051 // Show the Task Dialog
1052 hr = pfnTaskDialogIndirect(&TaskConfig, &nButton, NULL, NULL);
1053
1054 // Free everything
1055 FreeLibrary(hComctl32);
1056 SDL_free(ampescape);
1057 SDL_free(wmessage);
1058 SDL_free(wtitle);
1059 for (i = 0; i < messageboxdata->numbuttons; i++) {
1060 SDL_free((wchar_t *)pButtons[i].pszButtonText);
1061 }
1062 SDL_free(pButtons);
1063
1064 // Check the Task Dialog was successful and give the result
1065 if (SUCCEEDED(hr)) {
1066 if (nButton == IDCANCEL) {
1067 *buttonID = nCancelButton;
1068 } else if (nButton >= IDBUTTONINDEX0 && nButton < IDBUTTONINDEX0 + messageboxdata->numbuttons) {
1069 *buttonID = messageboxdata->buttons[nButton - IDBUTTONINDEX0].buttonID;
1070 } else {
1071 *buttonID = -1;
1072 }
1073 result = true;
1074 } else {
1075 // We failed showing the Task Dialog, use the old message box!
1076 result = WIN_ShowOldMessageBox(messageboxdata, buttonID);
1077 }
1078
1079done:
1080 if (SetThreadDpiAwarenessContextFunc) {
1081 SetThreadDpiAwarenessContextFunc(previous_context);
1082 }
1083 return result;
1084}
1085
1086#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 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#ifdef SDL_VIDEO_DRIVER_WINDOWS
24
25extern bool WIN_ShowMessageBox(const SDL_MessageBoxData *messageboxdata, int *buttonID);
26
27#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 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#if defined(SDL_VIDEO_DRIVER_WINDOWS) && !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
24
25#include "SDL_windowsvideo.h"
26#include "../../events/SDL_displayevents_c.h"
27
28#ifdef HAVE_DXGI1_6_H
29#define COBJMACROS
30#include <dxgi1_6.h>
31#endif
32
33// Windows CE compatibility
34#ifndef CDS_FULLSCREEN
35#define CDS_FULLSCREEN 0
36#endif
37
38// #define DEBUG_MODES
39// #define HIGHDPI_DEBUG_VERBOSE
40
41static void WIN_UpdateDisplayMode(SDL_VideoDevice *_this, LPCWSTR deviceName, DWORD index, SDL_DisplayMode *mode)
42{
43 SDL_DisplayModeData *data = (SDL_DisplayModeData *)mode->internal;
44 HDC hdc;
45
46 data->DeviceMode.dmFields = (DM_BITSPERPEL | DM_PELSWIDTH | DM_PELSHEIGHT | DM_DISPLAYFREQUENCY | DM_DISPLAYFLAGS);
47
48 // NOLINTNEXTLINE(bugprone-assignment-in-if-condition): No simple way to extract the assignment
49 if (index == ENUM_CURRENT_SETTINGS && (hdc = CreateDC(deviceName, NULL, NULL, NULL)) != NULL) {
50 char bmi_data[sizeof(BITMAPINFOHEADER) + 256 * sizeof(RGBQUAD)];
51 LPBITMAPINFO bmi;
52 HBITMAP hbm;
53
54 SDL_zeroa(bmi_data);
55 bmi = (LPBITMAPINFO)bmi_data;
56 bmi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
57
58 hbm = CreateCompatibleBitmap(hdc, 1, 1);
59 GetDIBits(hdc, hbm, 0, 1, NULL, bmi, DIB_RGB_COLORS);
60 GetDIBits(hdc, hbm, 0, 1, NULL, bmi, DIB_RGB_COLORS);
61 DeleteObject(hbm);
62 DeleteDC(hdc);
63 if (bmi->bmiHeader.biCompression == BI_BITFIELDS) {
64 switch (*(Uint32 *)bmi->bmiColors) {
65 case 0x00FF0000:
66 mode->format = SDL_PIXELFORMAT_XRGB8888;
67 break;
68 case 0x000000FF:
69 mode->format = SDL_PIXELFORMAT_XBGR8888;
70 break;
71 case 0xF800:
72 mode->format = SDL_PIXELFORMAT_RGB565;
73 break;
74 case 0x7C00:
75 mode->format = SDL_PIXELFORMAT_XRGB1555;
76 break;
77 }
78 } else if (bmi->bmiHeader.biCompression == BI_RGB) {
79 if (bmi->bmiHeader.biBitCount == 24) {
80 mode->format = SDL_PIXELFORMAT_RGB24;
81 } else if (bmi->bmiHeader.biBitCount == 8) {
82 mode->format = SDL_PIXELFORMAT_INDEX8;
83 } else if (bmi->bmiHeader.biBitCount == 4) {
84 mode->format = SDL_PIXELFORMAT_INDEX4LSB;
85 }
86 }
87 } else if (mode->format == SDL_PIXELFORMAT_UNKNOWN) {
88 // FIXME: Can we tell what this will be?
89 if ((data->DeviceMode.dmFields & DM_BITSPERPEL) == DM_BITSPERPEL) {
90 switch (data->DeviceMode.dmBitsPerPel) {
91 case 32:
92 mode->format = SDL_PIXELFORMAT_XRGB8888;
93 break;
94 case 24:
95 mode->format = SDL_PIXELFORMAT_RGB24;
96 break;
97 case 16:
98 mode->format = SDL_PIXELFORMAT_RGB565;
99 break;
100 case 15:
101 mode->format = SDL_PIXELFORMAT_XRGB1555;
102 break;
103 case 8:
104 mode->format = SDL_PIXELFORMAT_INDEX8;
105 break;
106 case 4:
107 mode->format = SDL_PIXELFORMAT_INDEX4LSB;
108 break;
109 }
110 }
111 }
112}
113
114static void *WIN_GetDXGIOutput(SDL_VideoDevice *_this, const WCHAR *DeviceName)
115{
116 void *result = NULL;
117
118#ifdef HAVE_DXGI_H
119 const SDL_VideoData *videodata = (const SDL_VideoData *)_this->internal;
120 int nAdapter, nOutput;
121 IDXGIAdapter *pDXGIAdapter;
122 IDXGIOutput *pDXGIOutput;
123
124 if (!videodata->pDXGIFactory) {
125 return NULL;
126 }
127
128 nAdapter = 0;
129 while (!result && SUCCEEDED(IDXGIFactory_EnumAdapters(videodata->pDXGIFactory, nAdapter, &pDXGIAdapter))) {
130 nOutput = 0;
131 while (!result && SUCCEEDED(IDXGIAdapter_EnumOutputs(pDXGIAdapter, nOutput, &pDXGIOutput))) {
132 DXGI_OUTPUT_DESC outputDesc;
133 if (SUCCEEDED(IDXGIOutput_GetDesc(pDXGIOutput, &outputDesc))) {
134 if (SDL_wcscmp(outputDesc.DeviceName, DeviceName) == 0) {
135 result = pDXGIOutput;
136 }
137 }
138 if (pDXGIOutput != result) {
139 IDXGIOutput_Release(pDXGIOutput);
140 }
141 nOutput++;
142 }
143 IDXGIAdapter_Release(pDXGIAdapter);
144 nAdapter++;
145 }
146#endif
147 return result;
148}
149
150static void WIN_ReleaseDXGIOutput(void *dxgi_output)
151{
152#ifdef HAVE_DXGI_H
153 IDXGIOutput *pDXGIOutput = (IDXGIOutput *)dxgi_output;
154
155 if (pDXGIOutput) {
156 IDXGIOutput_Release(pDXGIOutput);
157 }
158#endif
159}
160
161static SDL_DisplayOrientation WIN_GetNaturalOrientation(DEVMODE *mode)
162{
163 int width = mode->dmPelsWidth;
164 int height = mode->dmPelsHeight;
165
166 // Use unrotated width/height to guess orientation
167 if (mode->dmDisplayOrientation == DMDO_90 || mode->dmDisplayOrientation == DMDO_270) {
168 int temp = width;
169 width = height;
170 height = temp;
171 }
172
173 if (width >= height) {
174 return SDL_ORIENTATION_LANDSCAPE;
175 } else {
176 return SDL_ORIENTATION_PORTRAIT;
177 }
178}
179
180static SDL_DisplayOrientation WIN_GetDisplayOrientation(DEVMODE *mode)
181{
182 if (WIN_GetNaturalOrientation(mode) == SDL_ORIENTATION_LANDSCAPE) {
183 switch (mode->dmDisplayOrientation) {
184 case DMDO_DEFAULT:
185 return SDL_ORIENTATION_LANDSCAPE;
186 case DMDO_90:
187 return SDL_ORIENTATION_PORTRAIT;
188 case DMDO_180:
189 return SDL_ORIENTATION_LANDSCAPE_FLIPPED;
190 case DMDO_270:
191 return SDL_ORIENTATION_PORTRAIT_FLIPPED;
192 default:
193 return SDL_ORIENTATION_UNKNOWN;
194 }
195 } else {
196 switch (mode->dmDisplayOrientation) {
197 case DMDO_DEFAULT:
198 return SDL_ORIENTATION_PORTRAIT;
199 case DMDO_90:
200 return SDL_ORIENTATION_LANDSCAPE_FLIPPED;
201 case DMDO_180:
202 return SDL_ORIENTATION_PORTRAIT_FLIPPED;
203 case DMDO_270:
204 return SDL_ORIENTATION_LANDSCAPE;
205 default:
206 return SDL_ORIENTATION_UNKNOWN;
207 }
208 }
209}
210
211static void WIN_GetRefreshRate(void *dxgi_output, DEVMODE *mode, int *numerator, int *denominator)
212{
213 // We're not currently using DXGI to query display modes, so fake NTSC timings
214 switch (mode->dmDisplayFrequency) {
215 case 119:
216 case 59:
217 case 29:
218 *numerator = (mode->dmDisplayFrequency + 1) * 1000;
219 *denominator = 1001;
220 break;
221 default:
222 *numerator = mode->dmDisplayFrequency;
223 *denominator = 1;
224 break;
225 }
226
227#ifdef HAVE_DXGI_H
228 if (dxgi_output) {
229 IDXGIOutput *pDXGIOutput = (IDXGIOutput *)dxgi_output;
230 DXGI_MODE_DESC modeToMatch;
231 DXGI_MODE_DESC closestMatch;
232
233 SDL_zero(modeToMatch);
234 modeToMatch.Width = mode->dmPelsWidth;
235 modeToMatch.Height = mode->dmPelsHeight;
236 modeToMatch.RefreshRate.Numerator = *numerator;
237 modeToMatch.RefreshRate.Denominator = *denominator;
238 modeToMatch.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
239
240 if (SUCCEEDED(IDXGIOutput_FindClosestMatchingMode(pDXGIOutput, &modeToMatch, &closestMatch, NULL))) {
241 *numerator = closestMatch.RefreshRate.Numerator;
242 *denominator = closestMatch.RefreshRate.Denominator;
243 }
244 }
245#endif // HAVE_DXGI_H
246}
247
248static float WIN_GetContentScale(SDL_VideoDevice *_this, HMONITOR hMonitor)
249{
250 const SDL_VideoData *videodata = (const SDL_VideoData *)_this->internal;
251 int dpi = 0;
252
253 if (videodata->GetDpiForMonitor) {
254 UINT hdpi_uint, vdpi_uint;
255 if (videodata->GetDpiForMonitor(hMonitor, MDT_EFFECTIVE_DPI, &hdpi_uint, &vdpi_uint) == S_OK) {
256 dpi = (int)hdpi_uint;
257 }
258 }
259 if (dpi == 0) {
260 // Window 8.0 and below: same DPI for all monitors
261 HDC hdc = GetDC(NULL);
262 if (hdc) {
263 dpi = GetDeviceCaps(hdc, LOGPIXELSX);
264 ReleaseDC(NULL, hdc);
265 }
266 }
267 if (dpi == 0) {
268 // Safe default
269 dpi = USER_DEFAULT_SCREEN_DPI;
270 }
271 return dpi / (float)USER_DEFAULT_SCREEN_DPI;
272}
273
274static 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)
275{
276 SDL_DisplayModeData *data;
277 DEVMODE devmode;
278
279 devmode.dmSize = sizeof(devmode);
280 devmode.dmDriverExtra = 0;
281 if (!EnumDisplaySettingsW(deviceName, index, &devmode)) {
282 return false;
283 }
284
285 data = (SDL_DisplayModeData *)SDL_malloc(sizeof(*data));
286 if (!data) {
287 return false;
288 }
289
290 SDL_zerop(mode);
291 mode->internal = data;
292 data->DeviceMode = devmode;
293
294 mode->format = SDL_PIXELFORMAT_UNKNOWN;
295 mode->w = data->DeviceMode.dmPelsWidth;
296 mode->h = data->DeviceMode.dmPelsHeight;
297 WIN_GetRefreshRate(dxgi_output, &data->DeviceMode, &mode->refresh_rate_numerator, &mode->refresh_rate_denominator);
298
299 // Fill in the mode information
300 WIN_UpdateDisplayMode(_this, deviceName, index, mode);
301
302 if (natural_orientation) {
303 *natural_orientation = WIN_GetNaturalOrientation(&devmode);
304 }
305 if (current_orientation) {
306 *current_orientation = WIN_GetDisplayOrientation(&devmode);
307 }
308
309 return true;
310}
311
312static char *WIN_GetDisplayNameVista(SDL_VideoData *videodata, const WCHAR *deviceName)
313{
314 DISPLAYCONFIG_PATH_INFO *paths = NULL;
315 DISPLAYCONFIG_MODE_INFO *modes = NULL;
316 char *result = NULL;
317 UINT32 pathCount = 0;
318 UINT32 modeCount = 0;
319 UINT32 i;
320 LONG rc;
321
322 if (!videodata->GetDisplayConfigBufferSizes || !videodata->QueryDisplayConfig || !videodata->DisplayConfigGetDeviceInfo) {
323 return NULL;
324 }
325
326 do {
327 rc = videodata->GetDisplayConfigBufferSizes(QDC_ONLY_ACTIVE_PATHS, &pathCount, &modeCount);
328 if (rc != ERROR_SUCCESS) {
329 goto WIN_GetDisplayNameVista_failed;
330 }
331
332 SDL_free(paths);
333 SDL_free(modes);
334
335 paths = (DISPLAYCONFIG_PATH_INFO *)SDL_malloc(sizeof(DISPLAYCONFIG_PATH_INFO) * pathCount);
336 modes = (DISPLAYCONFIG_MODE_INFO *)SDL_malloc(sizeof(DISPLAYCONFIG_MODE_INFO) * modeCount);
337 if ((!paths) || (!modes)) {
338 goto WIN_GetDisplayNameVista_failed;
339 }
340
341 rc = videodata->QueryDisplayConfig(QDC_ONLY_ACTIVE_PATHS, &pathCount, paths, &modeCount, modes, 0);
342 } while (rc == ERROR_INSUFFICIENT_BUFFER);
343
344 if (rc == ERROR_SUCCESS) {
345 for (i = 0; i < pathCount; i++) {
346 DISPLAYCONFIG_SOURCE_DEVICE_NAME sourceName;
347 DISPLAYCONFIG_TARGET_DEVICE_NAME targetName;
348
349 SDL_zero(sourceName);
350 sourceName.header.adapterId = paths[i].targetInfo.adapterId;
351 sourceName.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_NAME;
352 sourceName.header.size = sizeof(sourceName);
353 sourceName.header.id = paths[i].sourceInfo.id;
354 rc = videodata->DisplayConfigGetDeviceInfo(&sourceName.header);
355 if (rc != ERROR_SUCCESS) {
356 break;
357 } else if (SDL_wcscmp(deviceName, sourceName.viewGdiDeviceName) != 0) {
358 continue;
359 }
360
361 SDL_zero(targetName);
362 targetName.header.adapterId = paths[i].targetInfo.adapterId;
363 targetName.header.id = paths[i].targetInfo.id;
364 targetName.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_TARGET_NAME;
365 targetName.header.size = sizeof(targetName);
366 rc = videodata->DisplayConfigGetDeviceInfo(&targetName.header);
367 if (rc == ERROR_SUCCESS) {
368 result = WIN_StringToUTF8W(targetName.monitorFriendlyDeviceName);
369 /* if we got an empty string, treat it as failure so we'll fallback
370 to getting the generic name. */
371 if (result && (*result == '\0')) {
372 SDL_free(result);
373 result = NULL;
374 }
375 }
376 break;
377 }
378 }
379
380 SDL_free(paths);
381 SDL_free(modes);
382 return result;
383
384WIN_GetDisplayNameVista_failed:
385 SDL_free(result);
386 SDL_free(paths);
387 SDL_free(modes);
388 return NULL;
389}
390
391#ifdef HAVE_DXGI1_6_H
392static bool WIN_GetMonitorDESC1(HMONITOR hMonitor, DXGI_OUTPUT_DESC1 *desc)
393{
394 typedef HRESULT (WINAPI * PFN_CREATE_DXGI_FACTORY)(REFIID riid, void **ppFactory);
395 PFN_CREATE_DXGI_FACTORY CreateDXGIFactoryFunc = NULL;
396 SDL_SharedObject *hDXGIMod = NULL;
397 bool found = false;
398
399 hDXGIMod = SDL_LoadObject("dxgi.dll");
400 if (hDXGIMod) {
401 CreateDXGIFactoryFunc = (PFN_CREATE_DXGI_FACTORY)SDL_LoadFunction(hDXGIMod, "CreateDXGIFactory1");
402 }
403 if (CreateDXGIFactoryFunc) {
404 static const GUID SDL_IID_IDXGIFactory1 = { 0x770aae78, 0xf26f, 0x4dba, { 0xa8, 0x29, 0x25, 0x3c, 0x83, 0xd1, 0xb3, 0x87 } };
405 static const GUID SDL_IID_IDXGIOutput6 = { 0x068346e8, 0xaaec, 0x4b84, { 0xad, 0xd7, 0x13, 0x7f, 0x51, 0x3f, 0x77, 0xa1 } };
406 IDXGIFactory1 *dxgiFactory;
407
408 if (SUCCEEDED(CreateDXGIFactoryFunc(&SDL_IID_IDXGIFactory1, (void **)&dxgiFactory))) {
409 IDXGIAdapter1 *dxgiAdapter;
410 UINT adapter = 0;
411 while (!found && SUCCEEDED(IDXGIFactory1_EnumAdapters1(dxgiFactory, adapter, &dxgiAdapter))) {
412 IDXGIOutput *dxgiOutput;
413 UINT output = 0;
414 while (!found && SUCCEEDED(IDXGIAdapter1_EnumOutputs(dxgiAdapter, output, &dxgiOutput))) {
415 IDXGIOutput6 *dxgiOutput6;
416 if (SUCCEEDED(IDXGIOutput_QueryInterface(dxgiOutput, &SDL_IID_IDXGIOutput6, (void **)&dxgiOutput6))) {
417 if (SUCCEEDED(IDXGIOutput6_GetDesc1(dxgiOutput6, desc))) {
418 if (desc->Monitor == hMonitor) {
419 found = true;
420 }
421 }
422 IDXGIOutput6_Release(dxgiOutput6);
423 }
424 IDXGIOutput_Release(dxgiOutput);
425 ++output;
426 }
427 IDXGIAdapter1_Release(dxgiAdapter);
428 ++adapter;
429 }
430 IDXGIFactory2_Release(dxgiFactory);
431 }
432 }
433 if (hDXGIMod) {
434 SDL_UnloadObject(hDXGIMod);
435 }
436 return found;
437}
438
439static bool WIN_GetMonitorPathInfo(SDL_VideoData *videodata, HMONITOR hMonitor, DISPLAYCONFIG_PATH_INFO *path_info)
440{
441 LONG result;
442 MONITORINFOEXW view_info;
443 UINT32 i;
444 UINT32 num_path_array_elements = 0;
445 UINT32 num_mode_info_array_elements = 0;
446 DISPLAYCONFIG_PATH_INFO *path_infos = NULL, *new_path_infos;
447 DISPLAYCONFIG_MODE_INFO *mode_infos = NULL, *new_mode_infos;
448 bool found = false;
449
450 if (!videodata->GetDisplayConfigBufferSizes || !videodata->QueryDisplayConfig || !videodata->DisplayConfigGetDeviceInfo) {
451 return false;
452 }
453
454 SDL_zero(view_info);
455 view_info.cbSize = sizeof(view_info);
456 if (!GetMonitorInfoW(hMonitor, (MONITORINFO *)&view_info)) {
457 goto done;
458 }
459
460 do {
461 if (videodata->GetDisplayConfigBufferSizes(QDC_ONLY_ACTIVE_PATHS, &num_path_array_elements, &num_mode_info_array_elements) != ERROR_SUCCESS) {
462 SDL_free(path_infos);
463 SDL_free(mode_infos);
464 return false;
465 }
466
467 new_path_infos = (DISPLAYCONFIG_PATH_INFO *)SDL_realloc(path_infos, num_path_array_elements * sizeof(*path_infos));
468 if (!new_path_infos) {
469 goto done;
470 }
471 path_infos = new_path_infos;
472
473 new_mode_infos = (DISPLAYCONFIG_MODE_INFO *)SDL_realloc(mode_infos, num_mode_info_array_elements * sizeof(*mode_infos));
474 if (!new_mode_infos) {
475 goto done;
476 }
477 mode_infos = new_mode_infos;
478
479 result = videodata->QueryDisplayConfig(QDC_ONLY_ACTIVE_PATHS, &num_path_array_elements, path_infos, &num_mode_info_array_elements, mode_infos, NULL);
480
481 } while (result == ERROR_INSUFFICIENT_BUFFER);
482
483 if (result == ERROR_SUCCESS) {
484 for (i = 0; i < num_path_array_elements; ++i) {
485 DISPLAYCONFIG_SOURCE_DEVICE_NAME device_name;
486
487 SDL_zero(device_name);
488 device_name.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_NAME;
489 device_name.header.size = sizeof(device_name);
490 device_name.header.adapterId = path_infos[i].sourceInfo.adapterId;
491 device_name.header.id = path_infos[i].sourceInfo.id;
492 if (videodata->DisplayConfigGetDeviceInfo(&device_name.header) == ERROR_SUCCESS) {
493 if (SDL_wcscmp(view_info.szDevice, device_name.viewGdiDeviceName) == 0) {
494 SDL_copyp(path_info, &path_infos[i]);
495 found = true;
496 break;
497 }
498 }
499 }
500 }
501
502done:
503 SDL_free(path_infos);
504 SDL_free(mode_infos);
505
506 return found;
507}
508
509static float WIN_GetSDRWhitePoint(SDL_VideoDevice *_this, HMONITOR hMonitor)
510{
511 DISPLAYCONFIG_PATH_INFO path_info;
512 SDL_VideoData *videodata = _this->internal;
513 float SDR_white_level = 1.0f;
514
515 if (WIN_GetMonitorPathInfo(videodata, hMonitor, &path_info)) {
516 /* workarounds for https://github.com/libsdl-org/SDL/issues/11193 */
517 struct SDL_DISPLAYCONFIG_SDR_WHITE_LEVEL {
518 DISPLAYCONFIG_DEVICE_INFO_HEADER header;
519 ULONG SDRWhiteLevel;
520 } white_level;
521 #define DISPLAYCONFIG_DEVICE_INFO_GET_SDR_WHITE_LEVEL 11
522
523 SDL_zero(white_level);
524 white_level.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_SDR_WHITE_LEVEL;
525 white_level.header.size = sizeof(white_level);
526 white_level.header.adapterId = path_info.targetInfo.adapterId;
527 white_level.header.id = path_info.targetInfo.id;
528 // WIN_GetMonitorPathInfo() succeeded: DisplayConfigGetDeviceInfo is not NULL
529 if (videodata->DisplayConfigGetDeviceInfo(&white_level.header) == ERROR_SUCCESS &&
530 white_level.SDRWhiteLevel > 0) {
531 SDR_white_level = (white_level.SDRWhiteLevel / 1000.0f);
532 }
533 }
534 return SDR_white_level;
535}
536
537static void WIN_GetHDRProperties(SDL_VideoDevice *_this, HMONITOR hMonitor, SDL_HDROutputProperties *HDR)
538{
539 DXGI_OUTPUT_DESC1 desc;
540
541 SDL_zerop(HDR);
542
543 if (WIN_GetMonitorDESC1(hMonitor, &desc)) {
544 if (desc.ColorSpace == DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020) {
545 HDR->SDR_white_level = WIN_GetSDRWhitePoint(_this, hMonitor);
546 HDR->HDR_headroom = (desc.MaxLuminance / 80.0f) / HDR->SDR_white_level;
547 }
548 }
549}
550#endif // HAVE_DXGI1_6_H
551
552static void WIN_AddDisplay(SDL_VideoDevice *_this, HMONITOR hMonitor, const MONITORINFOEXW *info, int *display_index)
553{
554 int i, index = *display_index;
555 SDL_VideoDisplay display;
556 SDL_DisplayData *displaydata;
557 void *dxgi_output = NULL;
558 SDL_DisplayMode mode;
559 SDL_DisplayOrientation natural_orientation;
560 SDL_DisplayOrientation current_orientation;
561 float content_scale = WIN_GetContentScale(_this, hMonitor);
562
563#ifdef DEBUG_MODES
564 SDL_Log("Display: %s", WIN_StringToUTF8W(info->szDevice));
565#endif
566
567 dxgi_output = WIN_GetDXGIOutput(_this, info->szDevice);
568 bool found = WIN_GetDisplayMode(_this, dxgi_output, hMonitor, info->szDevice, ENUM_CURRENT_SETTINGS, &mode, &natural_orientation, &current_orientation);
569 WIN_ReleaseDXGIOutput(dxgi_output);
570 if (!found) {
571 return;
572 }
573
574 // Prevent adding duplicate displays. Do this after we know the display is
575 // ready to be added to allow any displays that we can't fully query to be
576 // removed
577 for (i = 0; i < _this->num_displays; ++i) {
578 SDL_DisplayData *internal = _this->displays[i]->internal;
579 if (SDL_wcscmp(internal->DeviceName, info->szDevice) == 0) {
580 bool moved = (index != i);
581 bool changed_bounds = false;
582
583 if (internal->state != DisplayRemoved) {
584 // We've already enumerated this display, don't move it
585 return;
586 }
587
588 if (index >= _this->num_displays) {
589 // This should never happen due to the check above, but just in case...
590 return;
591 }
592
593 if (moved) {
594 SDL_VideoDisplay *tmp;
595
596 tmp = _this->displays[index];
597 _this->displays[index] = _this->displays[i];
598 _this->displays[i] = tmp;
599 i = index;
600 }
601
602 internal->MonitorHandle = hMonitor;
603 internal->state = DisplayUnchanged;
604
605 if (!_this->setting_display_mode) {
606 SDL_VideoDisplay *existing_display = _this->displays[i];
607 SDL_Rect bounds;
608
609 SDL_ResetFullscreenDisplayModes(existing_display);
610 SDL_SetDesktopDisplayMode(existing_display, &mode);
611 if (WIN_GetDisplayBounds(_this, existing_display, &bounds) &&
612 SDL_memcmp(&internal->bounds, &bounds, sizeof(bounds)) != 0) {
613 changed_bounds = true;
614 SDL_copyp(&internal->bounds, &bounds);
615 }
616 if (moved || changed_bounds) {
617 SDL_SendDisplayEvent(existing_display, SDL_EVENT_DISPLAY_MOVED, 0, 0);
618 }
619 SDL_SendDisplayEvent(existing_display, SDL_EVENT_DISPLAY_ORIENTATION, current_orientation, 0);
620 SDL_SetDisplayContentScale(existing_display, content_scale);
621#ifdef HAVE_DXGI1_6_H
622 SDL_HDROutputProperties HDR;
623 WIN_GetHDRProperties(_this, hMonitor, &HDR);
624 SDL_SetDisplayHDRProperties(existing_display, &HDR);
625#endif
626 }
627 goto done;
628 }
629 }
630
631 displaydata = (SDL_DisplayData *)SDL_calloc(1, sizeof(*displaydata));
632 if (!displaydata) {
633 return;
634 }
635 SDL_memcpy(displaydata->DeviceName, info->szDevice, sizeof(displaydata->DeviceName));
636 displaydata->MonitorHandle = hMonitor;
637 displaydata->state = DisplayAdded;
638
639 SDL_zero(display);
640 display.name = WIN_GetDisplayNameVista(_this->internal, info->szDevice);
641 if (!display.name) {
642 DISPLAY_DEVICEW device;
643 SDL_zero(device);
644 device.cb = sizeof(device);
645 if (EnumDisplayDevicesW(info->szDevice, 0, &device, 0)) {
646 display.name = WIN_StringToUTF8W(device.DeviceString);
647 }
648 }
649
650 display.desktop_mode = mode;
651 display.natural_orientation = natural_orientation;
652 display.current_orientation = current_orientation;
653 display.content_scale = content_scale;
654 display.device = _this;
655 display.internal = displaydata;
656 WIN_GetDisplayBounds(_this, &display, &displaydata->bounds);
657#ifdef HAVE_DXGI1_6_H
658 WIN_GetHDRProperties(_this, hMonitor, &display.HDR);
659#endif
660 SDL_AddVideoDisplay(&display, false);
661 SDL_free(display.name);
662
663done:
664 *display_index += 1;
665}
666
667typedef struct _WIN_AddDisplaysData
668{
669 SDL_VideoDevice *video_device;
670 int display_index;
671 bool want_primary;
672} WIN_AddDisplaysData;
673
674static BOOL CALLBACK WIN_AddDisplaysCallback(HMONITOR hMonitor,
675 HDC hdcMonitor,
676 LPRECT lprcMonitor,
677 LPARAM dwData)
678{
679 WIN_AddDisplaysData *data = (WIN_AddDisplaysData *)dwData;
680 MONITORINFOEXW info;
681
682 SDL_zero(info);
683 info.cbSize = sizeof(info);
684
685 if (GetMonitorInfoW(hMonitor, (LPMONITORINFO)&info) != 0) {
686 const bool is_primary = ((info.dwFlags & MONITORINFOF_PRIMARY) == MONITORINFOF_PRIMARY);
687
688 if (is_primary == data->want_primary) {
689 WIN_AddDisplay(data->video_device, hMonitor, &info, &data->display_index);
690 }
691 }
692
693 // continue enumeration
694 return TRUE;
695}
696
697static void WIN_AddDisplays(SDL_VideoDevice *_this)
698{
699 WIN_AddDisplaysData callback_data;
700 callback_data.video_device = _this;
701 callback_data.display_index = 0;
702
703 callback_data.want_primary = true;
704 EnumDisplayMonitors(NULL, NULL, WIN_AddDisplaysCallback, (LPARAM)&callback_data);
705
706 callback_data.want_primary = false;
707 EnumDisplayMonitors(NULL, NULL, WIN_AddDisplaysCallback, (LPARAM)&callback_data);
708}
709
710bool WIN_InitModes(SDL_VideoDevice *_this)
711{
712 WIN_AddDisplays(_this);
713
714 if (_this->num_displays == 0) {
715 return SDL_SetError("No displays available");
716 }
717 return true;
718}
719
720bool WIN_GetDisplayBounds(SDL_VideoDevice *_this, SDL_VideoDisplay *display, SDL_Rect *rect)
721{
722 const SDL_DisplayData *data = display->internal;
723 MONITORINFO minfo;
724 BOOL rc;
725
726 SDL_zero(minfo);
727 minfo.cbSize = sizeof(MONITORINFO);
728 rc = GetMonitorInfo(data->MonitorHandle, &minfo);
729
730 if (!rc) {
731 return SDL_SetError("Couldn't find monitor data");
732 }
733
734 rect->x = minfo.rcMonitor.left;
735 rect->y = minfo.rcMonitor.top;
736 rect->w = minfo.rcMonitor.right - minfo.rcMonitor.left;
737 rect->h = minfo.rcMonitor.bottom - minfo.rcMonitor.top;
738
739 return true;
740}
741
742bool WIN_GetDisplayUsableBounds(SDL_VideoDevice *_this, SDL_VideoDisplay *display, SDL_Rect *rect)
743{
744 const SDL_DisplayData *data = display->internal;
745 MONITORINFO minfo;
746 BOOL rc;
747
748 SDL_zero(minfo);
749 minfo.cbSize = sizeof(MONITORINFO);
750 rc = GetMonitorInfo(data->MonitorHandle, &minfo);
751
752 if (!rc) {
753 return SDL_SetError("Couldn't find monitor data");
754 }
755
756 rect->x = minfo.rcWork.left;
757 rect->y = minfo.rcWork.top;
758 rect->w = minfo.rcWork.right - minfo.rcWork.left;
759 rect->h = minfo.rcWork.bottom - minfo.rcWork.top;
760
761 return true;
762}
763
764bool WIN_GetDisplayModes(SDL_VideoDevice *_this, SDL_VideoDisplay *display)
765{
766 SDL_DisplayData *data = display->internal;
767 void *dxgi_output;
768 DWORD i;
769 SDL_DisplayMode mode;
770
771 dxgi_output = WIN_GetDXGIOutput(_this, data->DeviceName);
772
773 for (i = 0;; ++i) {
774 if (!WIN_GetDisplayMode(_this, dxgi_output, data->MonitorHandle, data->DeviceName, i, &mode, NULL, NULL)) {
775 break;
776 }
777 if (SDL_ISPIXELFORMAT_INDEXED(mode.format)) {
778 // We don't support palettized modes now
779 SDL_free(mode.internal);
780 continue;
781 }
782 if (mode.format != SDL_PIXELFORMAT_UNKNOWN) {
783 if (!SDL_AddFullscreenDisplayMode(display, &mode)) {
784 SDL_free(mode.internal);
785 }
786 } else {
787 SDL_free(mode.internal);
788 }
789 }
790
791 WIN_ReleaseDXGIOutput(dxgi_output);
792
793 return true;
794}
795
796#ifdef DEBUG_MODES
797static void WIN_LogMonitor(SDL_VideoDevice *_this, HMONITOR mon)
798{
799 const SDL_VideoData *vid_data = (const SDL_VideoData *)_this->internal;
800 MONITORINFOEX minfo;
801 UINT xdpi = 0, ydpi = 0;
802 char *name_utf8;
803
804 if (vid_data->GetDpiForMonitor) {
805 vid_data->GetDpiForMonitor(mon, MDT_EFFECTIVE_DPI, &xdpi, &ydpi);
806 }
807
808 SDL_zero(minfo);
809 minfo.cbSize = sizeof(minfo);
810 GetMonitorInfo(mon, (LPMONITORINFO)&minfo);
811
812 name_utf8 = WIN_StringToUTF8(minfo.szDevice);
813
814 SDL_Log("WIN_LogMonitor: monitor \"%s\": dpi: %d windows screen coordinates: %d, %d, %dx%d",
815 name_utf8,
816 xdpi,
817 minfo.rcMonitor.left,
818 minfo.rcMonitor.top,
819 minfo.rcMonitor.right - minfo.rcMonitor.left,
820 minfo.rcMonitor.bottom - minfo.rcMonitor.top);
821
822 SDL_free(name_utf8);
823}
824#endif
825
826bool WIN_SetDisplayMode(SDL_VideoDevice *_this, SDL_VideoDisplay *display, SDL_DisplayMode *mode)
827{
828 SDL_DisplayData *displaydata = display->internal;
829 SDL_DisplayModeData *data = (SDL_DisplayModeData *)mode->internal;
830 LONG status;
831
832#ifdef DEBUG_MODES
833 SDL_Log("WIN_SetDisplayMode: monitor state before mode change:");
834 WIN_LogMonitor(_this, displaydata->MonitorHandle);
835#endif
836
837 /* High-DPI notes:
838
839 - ChangeDisplaySettingsEx always takes pixels.
840 - e.g. if the display is set to 2880x1800 with 200% scaling in Display Settings
841 - calling ChangeDisplaySettingsEx with a dmPelsWidth/Height other than 2880x1800 will
842 change the monitor DPI to 96. (100% scaling)
843 - calling ChangeDisplaySettingsEx with a dmPelsWidth/Height of 2880x1800 (or a NULL DEVMODE*) will
844 reset the monitor DPI to 192. (200% scaling)
845
846 NOTE: these are temporary changes in DPI, not modifications to the Control Panel setting. */
847 if (mode->internal == display->desktop_mode.internal) {
848#ifdef DEBUG_MODES
849 SDL_Log("WIN_SetDisplayMode: resetting to original resolution");
850#endif
851 status = ChangeDisplaySettingsExW(displaydata->DeviceName, NULL, NULL, CDS_FULLSCREEN, NULL);
852 } else {
853#ifdef DEBUG_MODES
854 SDL_Log("WIN_SetDisplayMode: changing to %dx%d pixels", data->DeviceMode.dmPelsWidth, data->DeviceMode.dmPelsHeight);
855#endif
856 status = ChangeDisplaySettingsExW(displaydata->DeviceName, &data->DeviceMode, NULL, CDS_FULLSCREEN, NULL);
857 }
858 if (status != DISP_CHANGE_SUCCESSFUL) {
859 const char *reason = "Unknown reason";
860 switch (status) {
861 case DISP_CHANGE_BADFLAGS:
862 reason = "DISP_CHANGE_BADFLAGS";
863 break;
864 case DISP_CHANGE_BADMODE:
865 reason = "DISP_CHANGE_BADMODE";
866 break;
867 case DISP_CHANGE_BADPARAM:
868 reason = "DISP_CHANGE_BADPARAM";
869 break;
870 case DISP_CHANGE_FAILED:
871 reason = "DISP_CHANGE_FAILED";
872 break;
873 }
874 return SDL_SetError("ChangeDisplaySettingsEx() failed: %s", reason);
875 }
876
877#ifdef DEBUG_MODES
878 SDL_Log("WIN_SetDisplayMode: monitor state after mode change:");
879 WIN_LogMonitor(_this, displaydata->MonitorHandle);
880#endif
881
882 EnumDisplaySettingsW(displaydata->DeviceName, ENUM_CURRENT_SETTINGS, &data->DeviceMode);
883 WIN_UpdateDisplayMode(_this, displaydata->DeviceName, ENUM_CURRENT_SETTINGS, mode);
884 return true;
885}
886
887void WIN_RefreshDisplays(SDL_VideoDevice *_this)
888{
889 int i;
890
891 // Mark all displays as potentially invalid to detect
892 // entries that have actually been removed
893 for (i = 0; i < _this->num_displays; ++i) {
894 SDL_DisplayData *internal = _this->displays[i]->internal;
895 internal->state = DisplayRemoved;
896 }
897
898 // Enumerate displays to add any new ones and mark still
899 // connected entries as valid
900 WIN_AddDisplays(_this);
901
902 // Delete any entries still marked as invalid, iterate
903 // in reverse as each delete takes effect immediately
904 for (i = _this->num_displays - 1; i >= 0; --i) {
905 SDL_VideoDisplay *display = _this->displays[i];
906 SDL_DisplayData *internal = display->internal;
907 if (internal->state == DisplayRemoved) {
908 SDL_DelVideoDisplay(display->id, true);
909 }
910 }
911
912 // Send events for any newly added displays
913 for (i = 0; i < _this->num_displays; ++i) {
914 SDL_VideoDisplay *display = _this->displays[i];
915 SDL_DisplayData *internal = display->internal;
916 if (internal->state == DisplayAdded) {
917 SDL_SendDisplayEvent(display, SDL_EVENT_DISPLAY_ADDED, 0, 0);
918 }
919 }
920}
921
922void WIN_QuitModes(SDL_VideoDevice *_this)
923{
924 // All fullscreen windows should have restored modes by now
925}
926
927#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 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#ifndef SDL_windowsmodes_h_
24#define SDL_windowsmodes_h_
25
26typedef enum
27{
28 DisplayUnchanged,
29 DisplayAdded,
30 DisplayRemoved,
31
32} WIN_DisplayState;
33
34struct SDL_DisplayData
35{
36 WCHAR DeviceName[32];
37 HMONITOR MonitorHandle;
38 WIN_DisplayState state;
39 SDL_Rect bounds;
40};
41
42struct SDL_DisplayModeData
43{
44 DEVMODE DeviceMode;
45};
46
47extern bool WIN_InitModes(SDL_VideoDevice *_this);
48extern bool WIN_GetDisplayBounds(SDL_VideoDevice *_this, SDL_VideoDisplay *display, SDL_Rect *rect);
49extern bool WIN_GetDisplayUsableBounds(SDL_VideoDevice *_this, SDL_VideoDisplay *display, SDL_Rect *rect);
50extern bool WIN_GetDisplayModes(SDL_VideoDevice *_this, SDL_VideoDisplay *display);
51extern bool WIN_SetDisplayMode(SDL_VideoDevice *_this, SDL_VideoDisplay *display, SDL_DisplayMode *mode);
52extern void WIN_RefreshDisplays(SDL_VideoDevice *_this);
53extern void WIN_QuitModes(SDL_VideoDevice *_this);
54
55#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 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#if defined(SDL_VIDEO_DRIVER_WINDOWS) && !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
24
25#include "SDL_windowsvideo.h"
26#include "SDL_windowsevents.h"
27#include "SDL_windowsrawinput.h"
28
29#include "../SDL_video_c.h"
30#include "../../events/SDL_mouse_c.h"
31#include "../../joystick/usb_ids.h"
32#include "../../core/windows/SDL_windows.h" // for checking windows version
33
34
35typedef struct CachedCursor
36{
37 float scale;
38 HCURSOR cursor;
39 struct CachedCursor *next;
40} CachedCursor;
41
42struct SDL_CursorData
43{
44 SDL_Surface *surface;
45 int hot_x;
46 int hot_y;
47 CachedCursor *cache;
48 HCURSOR cursor;
49};
50
51typedef struct
52{
53 Uint64 xs[5];
54 Uint64 ys[5];
55 Sint64 residual[2];
56 Uint32 dpiscale;
57 Uint32 dpidenom;
58 int last_node;
59 bool enhanced;
60 bool dpiaware;
61} WIN_MouseData;
62
63DWORD SDL_last_warp_time = 0;
64HCURSOR SDL_cursor = NULL;
65static SDL_Cursor *SDL_blank_cursor = NULL;
66static WIN_MouseData WIN_system_scale_data;
67
68static SDL_Cursor *WIN_CreateCursorAndData(HCURSOR hcursor)
69{
70 if (!hcursor) {
71 return NULL;
72 }
73
74 SDL_Cursor *cursor = (SDL_Cursor *)SDL_calloc(1, sizeof(*cursor));
75 if (!cursor) {
76 return NULL;
77 }
78
79 SDL_CursorData *data = (SDL_CursorData *)SDL_calloc(1, sizeof(*data));
80 if (!data) {
81 SDL_free(cursor);
82 return NULL;
83 }
84
85 data->cursor = hcursor;
86 cursor->internal = data;
87 return cursor;
88}
89
90
91static bool IsMonochromeSurface(SDL_Surface *surface)
92{
93 int x, y;
94 Uint8 r, g, b, a;
95
96 SDL_assert(surface->format == SDL_PIXELFORMAT_ARGB8888);
97
98 for (y = 0; y < surface->h; y++) {
99 for (x = 0; x < surface->w; x++) {
100 SDL_ReadSurfacePixel(surface, x, y, &r, &g, &b, &a);
101
102 // Black or white pixel.
103 if (!((r == 0x00 && g == 0x00 && b == 0x00) || (r == 0xff && g == 0xff && b == 0xff))) {
104 return false;
105 }
106
107 // Transparent or opaque pixel.
108 if (!(a == 0x00 || a == 0xff)) {
109 return false;
110 }
111 }
112 }
113
114 return true;
115}
116
117static HBITMAP CreateColorBitmap(SDL_Surface *surface)
118{
119 HBITMAP bitmap;
120 BITMAPINFO bi;
121 void *pixels;
122
123 SDL_assert(surface->format == SDL_PIXELFORMAT_ARGB8888);
124
125 SDL_zero(bi);
126 bi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
127 bi.bmiHeader.biWidth = surface->w;
128 bi.bmiHeader.biHeight = -surface->h; // Invert height to make the top-down DIB.
129 bi.bmiHeader.biPlanes = 1;
130 bi.bmiHeader.biBitCount = 32;
131 bi.bmiHeader.biCompression = BI_RGB;
132
133 bitmap = CreateDIBSection(NULL, &bi, DIB_RGB_COLORS, &pixels, NULL, 0);
134 if (!bitmap || !pixels) {
135 WIN_SetError("CreateDIBSection()");
136 if (bitmap) {
137 DeleteObject(bitmap);
138 }
139 return NULL;
140 }
141
142 SDL_memcpy(pixels, surface->pixels, surface->pitch * surface->h);
143
144 return bitmap;
145}
146
147/* Generate bitmap with a mask and optional monochrome image data.
148 *
149 * For info on the expected mask format see:
150 * https://devblogs.microsoft.com/oldnewthing/20101018-00/?p=12513
151 */
152static HBITMAP CreateMaskBitmap(SDL_Surface *surface, bool is_monochrome)
153{
154 HBITMAP bitmap;
155 bool isstack;
156 void *pixels;
157 int x, y;
158 Uint8 r, g, b, a;
159 Uint8 *dst;
160 const int pitch = ((surface->w + 15) & ~15) / 8;
161 const int size = pitch * surface->h;
162 static const unsigned char masks[] = { 0x80, 0x40, 0x20, 0x10, 0x8, 0x4, 0x2, 0x1 };
163
164 SDL_assert(surface->format == SDL_PIXELFORMAT_ARGB8888);
165
166 pixels = SDL_small_alloc(Uint8, size * (is_monochrome ? 2 : 1), &isstack);
167 if (!pixels) {
168 SDL_OutOfMemory();
169 return NULL;
170 }
171
172 dst = (Uint8 *)pixels;
173
174 // Make the mask completely transparent.
175 SDL_memset(dst, 0xff, size);
176 if (is_monochrome) {
177 SDL_memset(dst + size, 0x00, size);
178 }
179
180 for (y = 0; y < surface->h; y++, dst += pitch) {
181 for (x = 0; x < surface->w; x++) {
182 SDL_ReadSurfacePixel(surface, x, y, &r, &g, &b, &a);
183
184 if (a != 0) {
185 // Reset bit of an opaque pixel.
186 dst[x >> 3] &= ~masks[x & 7];
187 }
188
189 if (is_monochrome && !(r == 0x00 && g == 0x00 && b == 0x00)) {
190 // Set bit of white or inverted pixel.
191 dst[size + (x >> 3)] |= masks[x & 7];
192 }
193 }
194 }
195
196 bitmap = CreateBitmap(surface->w, surface->h * (is_monochrome ? 2 : 1), 1, 1, pixels);
197 SDL_small_free(pixels, isstack);
198 if (!bitmap) {
199 WIN_SetError("CreateBitmap()");
200 return NULL;
201 }
202
203 return bitmap;
204}
205
206static HCURSOR WIN_CreateHCursor(SDL_Surface *surface, int hot_x, int hot_y)
207{
208 HCURSOR hcursor;
209 ICONINFO ii;
210 bool is_monochrome = IsMonochromeSurface(surface);
211
212 SDL_zero(ii);
213 ii.fIcon = FALSE;
214 ii.xHotspot = (DWORD)hot_x;
215 ii.yHotspot = (DWORD)hot_y;
216 ii.hbmMask = CreateMaskBitmap(surface, is_monochrome);
217 ii.hbmColor = is_monochrome ? NULL : CreateColorBitmap(surface);
218
219 if (!ii.hbmMask || (!is_monochrome && !ii.hbmColor)) {
220 SDL_SetError("Couldn't create cursor bitmaps");
221 if (ii.hbmMask) {
222 DeleteObject(ii.hbmMask);
223 }
224 if (ii.hbmColor) {
225 DeleteObject(ii.hbmColor);
226 }
227 return NULL;
228 }
229
230 hcursor = CreateIconIndirect(&ii);
231 if (!hcursor) {
232 WIN_SetError("CreateIconIndirect()");
233 DeleteObject(ii.hbmMask);
234 if (ii.hbmColor) {
235 DeleteObject(ii.hbmColor);
236 }
237 return NULL;
238 }
239
240 DeleteObject(ii.hbmMask);
241 if (ii.hbmColor) {
242 DeleteObject(ii.hbmColor);
243 }
244
245 return hcursor;
246}
247
248static SDL_Cursor *WIN_CreateCursor(SDL_Surface *surface, int hot_x, int hot_y)
249{
250 if (!SDL_SurfaceHasAlternateImages(surface)) {
251 HCURSOR hcursor = WIN_CreateHCursor(surface, hot_x, hot_y);
252 if (!hcursor) {
253 return NULL;
254 }
255 return WIN_CreateCursorAndData(hcursor);
256 }
257
258 // Dynamically generate cursors at the appropriate DPI
259 SDL_Cursor *cursor = (SDL_Cursor *)SDL_calloc(1, sizeof(*cursor));
260 if (cursor) {
261 SDL_CursorData *data = (SDL_CursorData *)SDL_calloc(1, sizeof(*data));
262 if (!data) {
263 SDL_free(cursor);
264 return NULL;
265 }
266 data->hot_x = hot_x;
267 data->hot_y = hot_y;
268 data->surface = surface;
269 ++surface->refcount;
270 cursor->internal = data;
271 }
272 return cursor;
273}
274
275static SDL_Cursor *WIN_CreateBlankCursor(void)
276{
277 SDL_Cursor *cursor = NULL;
278 SDL_Surface *surface = SDL_CreateSurface(32, 32, SDL_PIXELFORMAT_ARGB8888);
279 if (surface) {
280 cursor = WIN_CreateCursor(surface, 0, 0);
281 SDL_DestroySurface(surface);
282 }
283 return cursor;
284}
285
286static SDL_Cursor *WIN_CreateSystemCursor(SDL_SystemCursor id)
287{
288 LPCTSTR name;
289
290 switch (id) {
291 default:
292 SDL_assert(!"Unknown system cursor ID");
293 return NULL;
294 case SDL_SYSTEM_CURSOR_DEFAULT:
295 name = IDC_ARROW;
296 break;
297 case SDL_SYSTEM_CURSOR_TEXT:
298 name = IDC_IBEAM;
299 break;
300 case SDL_SYSTEM_CURSOR_WAIT:
301 name = IDC_WAIT;
302 break;
303 case SDL_SYSTEM_CURSOR_CROSSHAIR:
304 name = IDC_CROSS;
305 break;
306 case SDL_SYSTEM_CURSOR_PROGRESS:
307 name = IDC_APPSTARTING;
308 break;
309 case SDL_SYSTEM_CURSOR_NWSE_RESIZE:
310 name = IDC_SIZENWSE;
311 break;
312 case SDL_SYSTEM_CURSOR_NESW_RESIZE:
313 name = IDC_SIZENESW;
314 break;
315 case SDL_SYSTEM_CURSOR_EW_RESIZE:
316 name = IDC_SIZEWE;
317 break;
318 case SDL_SYSTEM_CURSOR_NS_RESIZE:
319 name = IDC_SIZENS;
320 break;
321 case SDL_SYSTEM_CURSOR_MOVE:
322 name = IDC_SIZEALL;
323 break;
324 case SDL_SYSTEM_CURSOR_NOT_ALLOWED:
325 name = IDC_NO;
326 break;
327 case SDL_SYSTEM_CURSOR_POINTER:
328 name = IDC_HAND;
329 break;
330 case SDL_SYSTEM_CURSOR_NW_RESIZE:
331 name = IDC_SIZENWSE;
332 break;
333 case SDL_SYSTEM_CURSOR_N_RESIZE:
334 name = IDC_SIZENS;
335 break;
336 case SDL_SYSTEM_CURSOR_NE_RESIZE:
337 name = IDC_SIZENESW;
338 break;
339 case SDL_SYSTEM_CURSOR_E_RESIZE:
340 name = IDC_SIZEWE;
341 break;
342 case SDL_SYSTEM_CURSOR_SE_RESIZE:
343 name = IDC_SIZENWSE;
344 break;
345 case SDL_SYSTEM_CURSOR_S_RESIZE:
346 name = IDC_SIZENS;
347 break;
348 case SDL_SYSTEM_CURSOR_SW_RESIZE:
349 name = IDC_SIZENESW;
350 break;
351 case SDL_SYSTEM_CURSOR_W_RESIZE:
352 name = IDC_SIZEWE;
353 break;
354 }
355 return WIN_CreateCursorAndData(LoadCursor(NULL, name));
356}
357
358static SDL_Cursor *WIN_CreateDefaultCursor(void)
359{
360 SDL_SystemCursor id = SDL_GetDefaultSystemCursor();
361 return WIN_CreateSystemCursor(id);
362}
363
364static void WIN_FreeCursor(SDL_Cursor *cursor)
365{
366 SDL_CursorData *data = cursor->internal;
367
368 if (data->surface) {
369 SDL_DestroySurface(data->surface);
370 }
371 while (data->cache) {
372 CachedCursor *entry = data->cache;
373 data->cache = entry->next;
374 if (entry->cursor) {
375 DestroyCursor(entry->cursor);
376 }
377 SDL_free(entry);
378 }
379 if (data->cursor) {
380 DestroyCursor(data->cursor);
381 }
382 SDL_free(data);
383 SDL_free(cursor);
384}
385
386static HCURSOR GetCachedCursor(SDL_Cursor *cursor)
387{
388 SDL_CursorData *data = cursor->internal;
389
390 SDL_Window *focus = SDL_GetMouseFocus();
391 if (!focus) {
392 return NULL;
393 }
394
395 float scale = SDL_GetDisplayContentScale(SDL_GetDisplayForWindow(focus));
396 for (CachedCursor *entry = data->cache; entry; entry = entry->next) {
397 if (scale == entry->scale) {
398 return entry->cursor;
399 }
400 }
401
402 // Need to create a cursor for this content scale
403 SDL_Surface *surface = NULL;
404 HCURSOR hcursor = NULL;
405 CachedCursor *entry = NULL;
406
407 surface = SDL_GetSurfaceImage(data->surface, scale);
408 if (!surface) {
409 goto error;
410 }
411
412 int hot_x = (int)SDL_round(data->hot_x * scale);
413 int hot_y = (int)SDL_round(data->hot_y * scale);
414 hcursor = WIN_CreateHCursor(surface, hot_x, hot_y);
415 if (!hcursor) {
416 goto error;
417 }
418
419 entry = (CachedCursor *)SDL_malloc(sizeof(*entry));
420 if (!entry) {
421 goto error;
422 }
423 entry->cursor = hcursor;
424 entry->scale = scale;
425 entry->next = data->cache;
426 data->cache = entry;
427
428 SDL_DestroySurface(surface);
429
430 return hcursor;
431
432error:
433 if (surface) {
434 SDL_DestroySurface(surface);
435 }
436 if (hcursor) {
437 DestroyCursor(hcursor);
438 }
439 SDL_free(entry);
440 return NULL;
441}
442
443static bool WIN_ShowCursor(SDL_Cursor *cursor)
444{
445 if (!cursor) {
446 cursor = SDL_blank_cursor;
447 }
448 if (cursor) {
449 if (cursor->internal->surface) {
450 SDL_cursor = GetCachedCursor(cursor);
451 } else {
452 SDL_cursor = cursor->internal->cursor;
453 }
454 } else {
455 SDL_cursor = NULL;
456 }
457 if (SDL_GetMouseFocus() != NULL) {
458 SetCursor(SDL_cursor);
459 }
460 return true;
461}
462
463void WIN_SetCursorPos(int x, int y)
464{
465 // We need to jitter the value because otherwise Windows will occasionally inexplicably ignore the SetCursorPos() or SendInput()
466 SetCursorPos(x, y);
467 SetCursorPos(x + 1, y);
468 SetCursorPos(x, y);
469
470 // Flush any mouse motion prior to or associated with this warp
471#ifdef _MSC_VER // We explicitly want to use GetTickCount(), not GetTickCount64()
472#pragma warning(push)
473#pragma warning(disable : 28159)
474#endif
475 SDL_last_warp_time = GetTickCount();
476 if (!SDL_last_warp_time) {
477 SDL_last_warp_time = 1;
478 }
479#ifdef _MSC_VER
480#pragma warning(pop)
481#endif
482}
483
484static bool WIN_WarpMouse(SDL_Window *window, float x, float y)
485{
486 SDL_WindowData *data = window->internal;
487 HWND hwnd = data->hwnd;
488 POINT pt;
489
490 // Don't warp the mouse while we're doing a modal interaction
491 if (data->in_title_click || data->focus_click_pending) {
492 return true;
493 }
494
495 pt.x = (int)SDL_roundf(x);
496 pt.y = (int)SDL_roundf(y);
497 ClientToScreen(hwnd, &pt);
498 WIN_SetCursorPos(pt.x, pt.y);
499
500 // Send the exact mouse motion associated with this warp
501 SDL_SendMouseMotion(0, window, SDL_GLOBAL_MOUSE_ID, false, x, y);
502 return true;
503}
504
505static bool WIN_WarpMouseGlobal(float x, float y)
506{
507 POINT pt;
508
509 pt.x = (int)SDL_roundf(x);
510 pt.y = (int)SDL_roundf(y);
511 SetCursorPos(pt.x, pt.y);
512 return true;
513}
514
515static bool WIN_SetRelativeMouseMode(bool enabled)
516{
517 return WIN_SetRawMouseEnabled(SDL_GetVideoDevice(), enabled);
518}
519
520static bool WIN_CaptureMouse(SDL_Window *window)
521{
522 if (window) {
523 SDL_WindowData *data = window->internal;
524 SetCapture(data->hwnd);
525 } else {
526 SDL_Window *focus_window = SDL_GetMouseFocus();
527
528 if (focus_window) {
529 SDL_WindowData *data = focus_window->internal;
530 if (!data->mouse_tracked) {
531 SDL_SetMouseFocus(NULL);
532 }
533 }
534 ReleaseCapture();
535 }
536
537 return true;
538}
539
540static SDL_MouseButtonFlags WIN_GetGlobalMouseState(float *x, float *y)
541{
542 SDL_MouseButtonFlags result = 0;
543 POINT pt = { 0, 0 };
544 bool swapButtons = GetSystemMetrics(SM_SWAPBUTTON) != 0;
545
546 GetCursorPos(&pt);
547 *x = (float)pt.x;
548 *y = (float)pt.y;
549
550 result |= GetAsyncKeyState(!swapButtons ? VK_LBUTTON : VK_RBUTTON) & 0x8000 ? SDL_BUTTON_LMASK : 0;
551 result |= GetAsyncKeyState(!swapButtons ? VK_RBUTTON : VK_LBUTTON) & 0x8000 ? SDL_BUTTON_RMASK : 0;
552 result |= GetAsyncKeyState(VK_MBUTTON) & 0x8000 ? SDL_BUTTON_MMASK : 0;
553 result |= GetAsyncKeyState(VK_XBUTTON1) & 0x8000 ? SDL_BUTTON_X1MASK : 0;
554 result |= GetAsyncKeyState(VK_XBUTTON2) & 0x8000 ? SDL_BUTTON_X2MASK : 0;
555
556 return result;
557}
558
559static void WIN_ApplySystemScale(void *internal, Uint64 timestamp, SDL_Window *window, SDL_MouseID mouseID, float *x, float *y)
560{
561 if (!internal) {
562 return;
563 }
564 WIN_MouseData *data = (WIN_MouseData *)internal;
565
566 SDL_VideoDisplay *display = window ? SDL_GetVideoDisplayForWindow(window) : SDL_GetVideoDisplay(SDL_GetPrimaryDisplay());
567
568 Sint64 ix = (Sint64)*x * 65536;
569 Sint64 iy = (Sint64)*y * 65536;
570 Uint32 dpi = display ? (Uint32)(display->content_scale * USER_DEFAULT_SCREEN_DPI) : USER_DEFAULT_SCREEN_DPI;
571
572 if (!data->enhanced) { // early return if flat scale
573 dpi = data->dpiscale * (data->dpiaware ? dpi : USER_DEFAULT_SCREEN_DPI);
574 ix *= dpi;
575 iy *= dpi;
576 ix /= USER_DEFAULT_SCREEN_DPI;
577 iy /= USER_DEFAULT_SCREEN_DPI;
578 ix /= 32;
579 iy /= 32;
580 // data->residual[0] += ix;
581 // data->residual[1] += iy;
582 // ix = 65536 * (data->residual[0] / 65536);
583 // iy = 65536 * (data->residual[1] / 65536);
584 // data->residual[0] -= ix;
585 // data->residual[1] -= iy;
586 *x = (float)ix / 65536.0f;
587 *y = (float)iy / 65536.0f;
588 return;
589 }
590
591 Uint64 *xs = data->xs;
592 Uint64 *ys = data->ys;
593 Uint64 absx = SDL_abs(ix);
594 Uint64 absy = SDL_abs(iy);
595 Uint64 speed = SDL_min(absx, absy) + (SDL_max(absx, absy) << 1); // super cursed approximation used by Windows
596 if (speed == 0) {
597 return;
598 }
599
600 int i, j, k;
601 for (i = 1; i < 5; i++) {
602 j = i;
603 if (speed < xs[j]) {
604 break;
605 }
606 }
607 i -= 1;
608 j -= 1;
609 k = data->last_node;
610 data->last_node = j;
611
612 Uint32 denom = data->dpidenom;
613 Sint64 scale = 0;
614 Sint64 xdiff = xs[j+1] - xs[j];
615 Sint64 ydiff = ys[j+1] - ys[j];
616 if (xdiff != 0) {
617 Sint64 slope = ydiff / xdiff;
618 Sint64 inter = slope * xs[i] - ys[i];
619 scale += slope - inter / speed;
620 }
621
622 if (j > k) {
623 denom <<= 1;
624 xdiff = xs[k+1] - xs[k];
625 ydiff = ys[k+1] - ys[k];
626 if (xdiff != 0) {
627 Sint64 slope = ydiff / xdiff;
628 Sint64 inter = slope * xs[k] - ys[k];
629 scale += slope - inter / speed;
630 }
631 }
632
633 scale *= dpi;
634 ix *= scale;
635 iy *= scale;
636 ix /= denom;
637 iy /= denom;
638 // data->residual[0] += ix;
639 // data->residual[1] += iy;
640 // ix = 65536 * (data->residual[0] / 65536);
641 // iy = 65536 * (data->residual[1] / 65536);
642 // data->residual[0] -= ix;
643 // data->residual[1] -= iy;
644 *x = (float)ix / 65536.0f;
645 *y = (float)iy / 65536.0f;
646}
647
648void WIN_InitMouse(SDL_VideoDevice *_this)
649{
650 SDL_Mouse *mouse = SDL_GetMouse();
651
652 mouse->CreateCursor = WIN_CreateCursor;
653 mouse->CreateSystemCursor = WIN_CreateSystemCursor;
654 mouse->ShowCursor = WIN_ShowCursor;
655 mouse->FreeCursor = WIN_FreeCursor;
656 mouse->WarpMouse = WIN_WarpMouse;
657 mouse->WarpMouseGlobal = WIN_WarpMouseGlobal;
658 mouse->SetRelativeMouseMode = WIN_SetRelativeMouseMode;
659 mouse->CaptureMouse = WIN_CaptureMouse;
660 mouse->GetGlobalMouseState = WIN_GetGlobalMouseState;
661 mouse->ApplySystemScale = WIN_ApplySystemScale;
662 mouse->system_scale_data = &WIN_system_scale_data;
663
664 SDL_SetDefaultCursor(WIN_CreateDefaultCursor());
665
666 SDL_blank_cursor = WIN_CreateBlankCursor();
667
668 WIN_UpdateMouseSystemScale();
669}
670
671void WIN_QuitMouse(SDL_VideoDevice *_this)
672{
673 if (SDL_blank_cursor) {
674 WIN_FreeCursor(SDL_blank_cursor);
675 SDL_blank_cursor = NULL;
676 }
677}
678
679static void ReadMouseCurve(int v, Uint64 xs[5], Uint64 ys[5])
680{
681 bool win8 = WIN_IsWindows8OrGreater();
682 DWORD xbuff[10] = {
683 0x00000000, 0,
684 0x00006e15, 0,
685 0x00014000, 0,
686 0x0003dc29, 0,
687 0x00280000, 0
688 };
689 DWORD ybuff[10] = {
690 0x00000000, 0,
691 win8 ? 0x000111fd : 0x00015eb8, 0,
692 win8 ? 0x00042400 : 0x00054ccd, 0,
693 win8 ? 0x0012fc00 : 0x00184ccd, 0,
694 win8 ? 0x01bbc000 : 0x02380000, 0
695 };
696 DWORD xsize = sizeof(xbuff);
697 DWORD ysize = sizeof(ybuff);
698 HKEY open_handle;
699 if (RegOpenKeyExW(HKEY_CURRENT_USER, L"Control Panel\\Mouse", 0, KEY_READ, &open_handle) == ERROR_SUCCESS) {
700 RegQueryValueExW(open_handle, L"SmoothMouseXCurve", NULL, NULL, (BYTE*)xbuff, &xsize);
701 RegQueryValueExW(open_handle, L"SmoothMouseYCurve", NULL, NULL, (BYTE*)ybuff, &ysize);
702 RegCloseKey(open_handle);
703 }
704 xs[0] = 0; // first node must always be origin
705 ys[0] = 0; // first node must always be origin
706 int i;
707 for (i = 1; i < 5; i++) {
708 xs[i] = (7 * (Uint64)xbuff[i*2]);
709 ys[i] = (v * (Uint64)ybuff[i*2]) << 17;
710 }
711}
712
713void WIN_UpdateMouseSystemScale(void)
714{
715 SDL_Mouse *mouse = SDL_GetMouse();
716
717 if (mouse->ApplySystemScale == WIN_ApplySystemScale) {
718 mouse->system_scale_data = &WIN_system_scale_data;
719 }
720
721 // always reinitialize to valid defaults, whether fetch was successful or not.
722 WIN_MouseData *data = &WIN_system_scale_data;
723 data->residual[0] = 0;
724 data->residual[1] = 0;
725 data->dpiscale = 32;
726 data->dpidenom = (10 * (WIN_IsWindows8OrGreater() ? 120 : 150)) << 16;
727 data->dpiaware = WIN_IsPerMonitorV2DPIAware(SDL_GetVideoDevice());
728 data->enhanced = false;
729
730 int v = 10;
731 if (SystemParametersInfo(SPI_GETMOUSESPEED, 0, &v, 0)) {
732 v = SDL_max(1, SDL_min(v, 20));
733 data->dpiscale = SDL_max(SDL_max(v, (v - 2) * 4), (v - 6) * 8);
734 }
735
736 int params[3];
737 if (SystemParametersInfo(SPI_GETMOUSE, 0, &params, 0)) {
738 data->enhanced = params[2] ? true : false;
739 if (params[2]) {
740 ReadMouseCurve(v, data->xs, data->ys);
741 }
742 }
743}
744
745#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 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#ifndef SDL_windowsmouse_h_
24#define SDL_windowsmouse_h_
25
26extern DWORD SDL_last_warp_time;
27extern HCURSOR SDL_cursor;
28
29extern void WIN_InitMouse(SDL_VideoDevice *_this);
30extern void WIN_QuitMouse(SDL_VideoDevice *_this);
31extern void WIN_SetCursorPos(int x, int y);
32extern void WIN_UpdateMouseSystemScale(void);
33
34#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 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#ifdef SDL_VIDEO_DRIVER_WINDOWS
24
25#include "SDL_windowsvideo.h"
26#include "SDL_windowsopengles.h"
27
28// WGL implementation of SDL OpenGL support
29
30#ifdef SDL_VIDEO_OPENGL_WGL
31#include <SDL3/SDL_opengl.h>
32
33#define DEFAULT_OPENGL "OPENGL32.DLL"
34
35#ifndef WGL_ARB_create_context
36#define WGL_ARB_create_context
37#define WGL_CONTEXT_MAJOR_VERSION_ARB 0x2091
38#define WGL_CONTEXT_MINOR_VERSION_ARB 0x2092
39#define WGL_CONTEXT_LAYER_PLANE_ARB 0x2093
40#define WGL_CONTEXT_FLAGS_ARB 0x2094
41#define WGL_CONTEXT_DEBUG_BIT_ARB 0x0001
42#define WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB 0x0002
43
44#ifndef WGL_ARB_create_context_profile
45#define WGL_ARB_create_context_profile
46#define WGL_CONTEXT_PROFILE_MASK_ARB 0x9126
47#define WGL_CONTEXT_CORE_PROFILE_BIT_ARB 0x00000001
48#define WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB 0x00000002
49#endif
50
51#ifndef WGL_ARB_create_context_robustness
52#define WGL_ARB_create_context_robustness
53#define WGL_CONTEXT_ROBUST_ACCESS_BIT_ARB 0x00000004
54#define WGL_CONTEXT_RESET_NOTIFICATION_STRATEGY_ARB 0x8256
55#define WGL_NO_RESET_NOTIFICATION_ARB 0x8261
56#define WGL_LOSE_CONTEXT_ON_RESET_ARB 0x8252
57#endif
58#endif
59
60#ifndef WGL_EXT_create_context_es2_profile
61#define WGL_EXT_create_context_es2_profile
62#define WGL_CONTEXT_ES2_PROFILE_BIT_EXT 0x00000004
63#endif
64
65#ifndef WGL_EXT_create_context_es_profile
66#define WGL_EXT_create_context_es_profile
67#define WGL_CONTEXT_ES_PROFILE_BIT_EXT 0x00000004
68#endif
69
70#ifndef WGL_ARB_framebuffer_sRGB
71#define WGL_ARB_framebuffer_sRGB
72#define WGL_FRAMEBUFFER_SRGB_CAPABLE_ARB 0x20A9
73#endif
74
75#ifndef WGL_ARB_pixel_format_float
76#define WGL_ARB_pixel_format_float
77#define WGL_TYPE_RGBA_FLOAT_ARB 0x21A0
78#endif
79
80#ifndef WGL_ARB_context_flush_control
81#define WGL_ARB_context_flush_control
82#define WGL_CONTEXT_RELEASE_BEHAVIOR_ARB 0x2097
83#define WGL_CONTEXT_RELEASE_BEHAVIOR_NONE_ARB 0x0000
84#define WGL_CONTEXT_RELEASE_BEHAVIOR_FLUSH_ARB 0x2098
85#endif
86
87#ifndef WGL_ARB_create_context_no_error
88#define WGL_ARB_create_context_no_error
89#define WGL_CONTEXT_OPENGL_NO_ERROR_ARB 0x31B3
90#endif
91
92typedef HGLRC(APIENTRYP PFNWGLCREATECONTEXTATTRIBSARBPROC)(HDC hDC,
93 HGLRC
94 hShareContext,
95 const int
96 *attribList);
97
98#if defined(SDL_PLATFORM_XBOXONE) || defined(SDL_PLATFORM_XBOXSERIES)
99#define GetDC(hwnd) (HDC) hwnd
100#define ReleaseDC(hwnd, hdc) 1
101#define SwapBuffers _this->gl_data->wglSwapBuffers
102#define DescribePixelFormat _this->gl_data->wglDescribePixelFormat
103#define ChoosePixelFormat _this->gl_data->wglChoosePixelFormat
104#define GetPixelFormat _this->gl_data->wglGetPixelFormat
105#define SetPixelFormat _this->gl_data->wglSetPixelFormat
106#endif
107
108bool WIN_GL_LoadLibrary(SDL_VideoDevice *_this, const char *path)
109{
110 void *handle;
111
112 if (path == NULL) {
113 path = SDL_GetHint(SDL_HINT_OPENGL_LIBRARY);
114 }
115 if (path == NULL) {
116 path = DEFAULT_OPENGL;
117 }
118 _this->gl_config.dll_handle = SDL_LoadObject(path);
119 if (!_this->gl_config.dll_handle) {
120 return false;
121 }
122 SDL_strlcpy(_this->gl_config.driver_path, path,
123 SDL_arraysize(_this->gl_config.driver_path));
124
125 // Allocate OpenGL memory
126 _this->gl_data = (struct SDL_GLDriverData *)SDL_calloc(1, sizeof(struct SDL_GLDriverData));
127 if (!_this->gl_data) {
128 return false;
129 }
130
131 // Load function pointers
132 handle = _this->gl_config.dll_handle;
133 /* *INDENT-OFF* */ // clang-format off
134 _this->gl_data->wglGetProcAddress = (PROC (WINAPI *)(const char *))
135 SDL_LoadFunction(handle, "wglGetProcAddress");
136 _this->gl_data->wglCreateContext = (HGLRC (WINAPI *)(HDC))
137 SDL_LoadFunction(handle, "wglCreateContext");
138 _this->gl_data->wglDeleteContext = (BOOL (WINAPI *)(HGLRC))
139 SDL_LoadFunction(handle, "wglDeleteContext");
140 _this->gl_data->wglMakeCurrent = (BOOL (WINAPI *)(HDC, HGLRC))
141 SDL_LoadFunction(handle, "wglMakeCurrent");
142 _this->gl_data->wglShareLists = (BOOL (WINAPI *)(HGLRC, HGLRC))
143 SDL_LoadFunction(handle, "wglShareLists");
144 /* *INDENT-ON* */ // clang-format on
145
146#if defined(SDL_PLATFORM_XBOXONE) || defined(SDL_PLATFORM_XBOXSERIES)
147 _this->gl_data->wglSwapBuffers = (BOOL(WINAPI *)(HDC))
148 SDL_LoadFunction(handle, "wglSwapBuffers");
149 _this->gl_data->wglDescribePixelFormat = (int(WINAPI *)(HDC, int, UINT, LPPIXELFORMATDESCRIPTOR))
150 SDL_LoadFunction(handle, "wglDescribePixelFormat");
151 _this->gl_data->wglChoosePixelFormat = (int(WINAPI *)(HDC, const PIXELFORMATDESCRIPTOR *))
152 SDL_LoadFunction(handle, "wglChoosePixelFormat");
153 _this->gl_data->wglSetPixelFormat = (BOOL(WINAPI *)(HDC, int, const PIXELFORMATDESCRIPTOR *))
154 SDL_LoadFunction(handle, "wglSetPixelFormat");
155 _this->gl_data->wglGetPixelFormat = (int(WINAPI *)(HDC hdc))
156 SDL_LoadFunction(handle, "wglGetPixelFormat");
157#endif
158
159 if (!_this->gl_data->wglGetProcAddress ||
160 !_this->gl_data->wglCreateContext ||
161 !_this->gl_data->wglDeleteContext ||
162 !_this->gl_data->wglMakeCurrent
163#if defined(SDL_PLATFORM_XBOXONE) || defined(SDL_PLATFORM_XBOXSERIES)
164 || !_this->gl_data->wglSwapBuffers ||
165 !_this->gl_data->wglDescribePixelFormat ||
166 !_this->gl_data->wglChoosePixelFormat ||
167 !_this->gl_data->wglGetPixelFormat ||
168 !_this->gl_data->wglSetPixelFormat
169#endif
170 ) {
171 return SDL_SetError("Could not retrieve OpenGL functions");
172 }
173
174 /* XXX Too sleazy? WIN_GL_InitExtensions looks for certain OpenGL
175 extensions via SDL_GL_DeduceMaxSupportedESProfile. This uses
176 SDL_GL_ExtensionSupported which in turn calls SDL_GL_GetProcAddress.
177 However SDL_GL_GetProcAddress will fail if the library is not
178 loaded; it checks for gl_config.driver_loaded > 0. To avoid this
179 test failing, increment driver_loaded around the call to
180 WIN_GLInitExtensions.
181
182 Successful loading of the library is normally indicated by
183 SDL_GL_LoadLibrary incrementing driver_loaded immediately after
184 this function returns 0 to it.
185
186 Alternatives to this are:
187 - moving SDL_GL_DeduceMaxSupportedESProfile to both the WIN and
188 X11 platforms while adding a function equivalent to
189 SDL_GL_ExtensionSupported but which directly calls
190 glGetProcAddress(). Having 3 copies of the
191 SDL_GL_ExtensionSupported makes this alternative unattractive.
192 - moving SDL_GL_DeduceMaxSupportedESProfile to a new file shared
193 by the WIN and X11 platforms while adding a function equivalent
194 to SDL_GL_ExtensionSupported. This is unattractive due to the
195 number of project files that will need updating, plus there
196 will be 2 copies of the SDL_GL_ExtensionSupported code.
197 - Add a private equivalent of SDL_GL_ExtensionSupported to
198 SDL_video.c.
199 - Move the call to WIN_GL_InitExtensions back to WIN_CreateWindow
200 and add a flag to gl_data to avoid multiple calls to this
201 expensive function. This is probably the least objectionable
202 alternative if this increment/decrement trick is unacceptable.
203
204 Note that the driver_loaded > 0 check needs to remain in
205 SDL_GL_ExtensionSupported and SDL_GL_GetProcAddress as they are
206 public API functions.
207 */
208 ++_this->gl_config.driver_loaded;
209 WIN_GL_InitExtensions(_this);
210 --_this->gl_config.driver_loaded;
211
212 return true;
213}
214
215SDL_FunctionPointer WIN_GL_GetProcAddress(SDL_VideoDevice *_this, const char *proc)
216{
217 SDL_FunctionPointer func;
218
219 // This is to pick up extensions
220 func = (SDL_FunctionPointer)_this->gl_data->wglGetProcAddress(proc);
221 if (!func) {
222 // This is probably a normal GL function
223 func = (SDL_FunctionPointer)GetProcAddress((HMODULE)_this->gl_config.dll_handle, proc);
224 }
225 return func;
226}
227
228void WIN_GL_UnloadLibrary(SDL_VideoDevice *_this)
229{
230 SDL_UnloadObject(_this->gl_config.dll_handle);
231 _this->gl_config.dll_handle = NULL;
232
233 // Free OpenGL memory
234 SDL_free(_this->gl_data);
235 _this->gl_data = NULL;
236}
237
238static void WIN_GL_SetupPixelFormat(SDL_VideoDevice *_this, PIXELFORMATDESCRIPTOR *pfd)
239{
240 SDL_zerop(pfd);
241 pfd->nSize = sizeof(*pfd);
242 pfd->nVersion = 1;
243 pfd->dwFlags = (PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL);
244 if (_this->gl_config.double_buffer) {
245 pfd->dwFlags |= PFD_DOUBLEBUFFER;
246 }
247 if (_this->gl_config.stereo) {
248 pfd->dwFlags |= PFD_STEREO;
249 }
250 pfd->iLayerType = PFD_MAIN_PLANE;
251 pfd->iPixelType = PFD_TYPE_RGBA;
252 pfd->cRedBits = (BYTE)_this->gl_config.red_size;
253 pfd->cGreenBits = (BYTE)_this->gl_config.green_size;
254 pfd->cBlueBits = (BYTE)_this->gl_config.blue_size;
255 pfd->cAlphaBits = (BYTE)_this->gl_config.alpha_size;
256 if (_this->gl_config.buffer_size) {
257 pfd->cColorBits = (BYTE)(_this->gl_config.buffer_size - _this->gl_config.alpha_size);
258 } else {
259 pfd->cColorBits = (pfd->cRedBits + pfd->cGreenBits + pfd->cBlueBits);
260 }
261 pfd->cAccumRedBits = (BYTE)_this->gl_config.accum_red_size;
262 pfd->cAccumGreenBits = (BYTE)_this->gl_config.accum_green_size;
263 pfd->cAccumBlueBits = (BYTE)_this->gl_config.accum_blue_size;
264 pfd->cAccumAlphaBits = (BYTE)_this->gl_config.accum_alpha_size;
265 pfd->cAccumBits =
266 (pfd->cAccumRedBits + pfd->cAccumGreenBits + pfd->cAccumBlueBits +
267 pfd->cAccumAlphaBits);
268 pfd->cDepthBits = (BYTE)_this->gl_config.depth_size;
269 pfd->cStencilBits = (BYTE)_this->gl_config.stencil_size;
270}
271
272/* Choose the closest pixel format that meets or exceeds the target.
273 FIXME: Should we weight any particular attribute over any other?
274*/
275static bool WIN_GL_ChoosePixelFormat(SDL_VideoDevice *_this, HDC hdc, PIXELFORMATDESCRIPTOR *target)
276{
277 PIXELFORMATDESCRIPTOR pfd;
278 int count, index, best = 0;
279 unsigned int dist, best_dist = ~0U;
280
281 count = DescribePixelFormat(hdc, 1, sizeof(pfd), NULL);
282
283 for (index = 1; index <= count; index++) {
284
285 if (!DescribePixelFormat(hdc, index, sizeof(pfd), &pfd)) {
286 continue;
287 }
288
289 if ((pfd.dwFlags & target->dwFlags) != target->dwFlags) {
290 continue;
291 }
292
293 if (pfd.iLayerType != target->iLayerType) {
294 continue;
295 }
296 if (pfd.iPixelType != target->iPixelType) {
297 continue;
298 }
299
300 dist = 0;
301
302 if (pfd.cColorBits < target->cColorBits) {
303 continue;
304 } else {
305 dist += (pfd.cColorBits - target->cColorBits);
306 }
307 if (pfd.cRedBits < target->cRedBits) {
308 continue;
309 } else {
310 dist += (pfd.cRedBits - target->cRedBits);
311 }
312 if (pfd.cGreenBits < target->cGreenBits) {
313 continue;
314 } else {
315 dist += (pfd.cGreenBits - target->cGreenBits);
316 }
317 if (pfd.cBlueBits < target->cBlueBits) {
318 continue;
319 } else {
320 dist += (pfd.cBlueBits - target->cBlueBits);
321 }
322 if (pfd.cAlphaBits < target->cAlphaBits) {
323 continue;
324 } else {
325 dist += (pfd.cAlphaBits - target->cAlphaBits);
326 }
327 if (pfd.cAccumBits < target->cAccumBits) {
328 continue;
329 } else {
330 dist += (pfd.cAccumBits - target->cAccumBits);
331 }
332 if (pfd.cAccumRedBits < target->cAccumRedBits) {
333 continue;
334 } else {
335 dist += (pfd.cAccumRedBits - target->cAccumRedBits);
336 }
337 if (pfd.cAccumGreenBits < target->cAccumGreenBits) {
338 continue;
339 } else {
340 dist += (pfd.cAccumGreenBits - target->cAccumGreenBits);
341 }
342 if (pfd.cAccumBlueBits < target->cAccumBlueBits) {
343 continue;
344 } else {
345 dist += (pfd.cAccumBlueBits - target->cAccumBlueBits);
346 }
347 if (pfd.cAccumAlphaBits < target->cAccumAlphaBits) {
348 continue;
349 } else {
350 dist += (pfd.cAccumAlphaBits - target->cAccumAlphaBits);
351 }
352 if (pfd.cDepthBits < target->cDepthBits) {
353 continue;
354 } else {
355 dist += (pfd.cDepthBits - target->cDepthBits);
356 }
357 if (pfd.cStencilBits < target->cStencilBits) {
358 continue;
359 } else {
360 dist += (pfd.cStencilBits - target->cStencilBits);
361 }
362
363 if (dist < best_dist) {
364 best = index;
365 best_dist = dist;
366 }
367 }
368
369 return best;
370}
371
372static bool HasExtension(const char *extension, const char *extensions)
373{
374 const char *start;
375 const char *where, *terminator;
376
377 // Extension names should not have spaces.
378 where = SDL_strchr(extension, ' ');
379 if (where || *extension == '\0') {
380 return false;
381 }
382
383 if (!extensions) {
384 return false;
385 }
386
387 /* It takes a bit of care to be fool-proof about parsing the
388 * OpenGL extensions string. Don't be fooled by sub-strings,
389 * etc. */
390
391 start = extensions;
392
393 for (;;) {
394 where = SDL_strstr(start, extension);
395 if (!where) {
396 break;
397 }
398
399 terminator = where + SDL_strlen(extension);
400 if (where == start || *(where - 1) == ' ') {
401 if (*terminator == ' ' || *terminator == '\0') {
402 return true;
403 }
404 }
405
406 start = terminator;
407 }
408 return false;
409}
410
411void WIN_GL_InitExtensions(SDL_VideoDevice *_this)
412{
413 /* *INDENT-OFF* */ // clang-format off
414 const char *(WINAPI * wglGetExtensionsStringARB)(HDC) = 0;
415 /* *INDENT-ON* */ // clang-format on
416 const char *extensions;
417 HWND hwnd;
418 HDC hdc;
419 HGLRC hglrc;
420 PIXELFORMATDESCRIPTOR pfd;
421
422 if (!_this->gl_data) {
423 return;
424 }
425
426 hwnd =
427 CreateWindow(SDL_Appname, SDL_Appname, (WS_POPUP | WS_DISABLED), 0, 0,
428 10, 10, NULL, NULL, SDL_Instance, NULL);
429 if (!hwnd) {
430 return;
431 }
432 WIN_PumpEvents(_this);
433
434 hdc = GetDC(hwnd);
435
436 WIN_GL_SetupPixelFormat(_this, &pfd);
437
438 SetPixelFormat(hdc, ChoosePixelFormat(hdc, &pfd), &pfd);
439
440 hglrc = _this->gl_data->wglCreateContext(hdc);
441 if (!hglrc) {
442 return;
443 }
444 _this->gl_data->wglMakeCurrent(hdc, hglrc);
445
446 /* *INDENT-OFF* */ // clang-format off
447 wglGetExtensionsStringARB = (const char *(WINAPI *)(HDC))
448 _this->gl_data->wglGetProcAddress("wglGetExtensionsStringARB");
449 /* *INDENT-ON* */ // clang-format on
450 if (wglGetExtensionsStringARB) {
451 extensions = wglGetExtensionsStringARB(hdc);
452 } else {
453 extensions = NULL;
454 }
455
456 // Check for WGL_ARB_pixel_format
457 _this->gl_data->HAS_WGL_ARB_pixel_format = false;
458 if (HasExtension("WGL_ARB_pixel_format", extensions)) {
459 /* *INDENT-OFF* */ // clang-format off
460 _this->gl_data->wglChoosePixelFormatARB =
461 (BOOL (WINAPI *)(HDC, const int *, const FLOAT *, UINT, int *, UINT *))
462 WIN_GL_GetProcAddress(_this, "wglChoosePixelFormatARB");
463 _this->gl_data->wglGetPixelFormatAttribivARB =
464 (BOOL (WINAPI *)(HDC, int, int, UINT, const int *, int *))
465 WIN_GL_GetProcAddress(_this, "wglGetPixelFormatAttribivARB");
466 /* *INDENT-ON* */ // clang-format on
467
468 if ((_this->gl_data->wglChoosePixelFormatARB != NULL) &&
469 (_this->gl_data->wglGetPixelFormatAttribivARB != NULL)) {
470 _this->gl_data->HAS_WGL_ARB_pixel_format = true;
471 }
472 }
473
474 // Check for WGL_EXT_swap_control
475 _this->gl_data->HAS_WGL_EXT_swap_control_tear = false;
476 if (HasExtension("WGL_EXT_swap_control", extensions)) {
477 _this->gl_data->wglSwapIntervalEXT =
478 (BOOL (WINAPI *)(int))
479 WIN_GL_GetProcAddress(_this, "wglSwapIntervalEXT");
480 _this->gl_data->wglGetSwapIntervalEXT =
481 (int (WINAPI *)(void))
482 WIN_GL_GetProcAddress(_this, "wglGetSwapIntervalEXT");
483 if (HasExtension("WGL_EXT_swap_control_tear", extensions)) {
484 _this->gl_data->HAS_WGL_EXT_swap_control_tear = true;
485 }
486 } else {
487 _this->gl_data->wglSwapIntervalEXT = NULL;
488 _this->gl_data->wglGetSwapIntervalEXT = NULL;
489 }
490
491 // Check for WGL_EXT_create_context_es2_profile
492 if (HasExtension("WGL_EXT_create_context_es2_profile", extensions)) {
493 SDL_GL_DeduceMaxSupportedESProfile(
494 &_this->gl_data->es_profile_max_supported_version.major,
495 &_this->gl_data->es_profile_max_supported_version.minor);
496 }
497
498 // Check for WGL_ARB_context_flush_control
499 if (HasExtension("WGL_ARB_context_flush_control", extensions)) {
500 _this->gl_data->HAS_WGL_ARB_context_flush_control = true;
501 }
502
503 // Check for WGL_ARB_create_context_robustness
504 if (HasExtension("WGL_ARB_create_context_robustness", extensions)) {
505 _this->gl_data->HAS_WGL_ARB_create_context_robustness = true;
506 }
507
508 // Check for WGL_ARB_create_context_no_error
509 if (HasExtension("WGL_ARB_create_context_no_error", extensions)) {
510 _this->gl_data->HAS_WGL_ARB_create_context_no_error = true;
511 }
512
513 _this->gl_data->wglMakeCurrent(hdc, NULL);
514 _this->gl_data->wglDeleteContext(hglrc);
515 ReleaseDC(hwnd, hdc);
516 DestroyWindow(hwnd);
517 WIN_PumpEvents(_this);
518}
519
520static int WIN_GL_ChoosePixelFormatARB(SDL_VideoDevice *_this, int *iAttribs, float *fAttribs)
521{
522 HWND hwnd;
523 HDC hdc;
524 PIXELFORMATDESCRIPTOR pfd;
525 HGLRC hglrc;
526 int pixel_format = 0;
527 unsigned int matching;
528
529 int qAttrib = WGL_FRAMEBUFFER_SRGB_CAPABLE_ARB;
530 int srgb = 0;
531
532 hwnd =
533 CreateWindow(SDL_Appname, SDL_Appname, (WS_POPUP | WS_DISABLED), 0, 0,
534 10, 10, NULL, NULL, SDL_Instance, NULL);
535 WIN_PumpEvents(_this);
536
537 hdc = GetDC(hwnd);
538
539 WIN_GL_SetupPixelFormat(_this, &pfd);
540
541 SetPixelFormat(hdc, ChoosePixelFormat(hdc, &pfd), &pfd);
542
543 hglrc = _this->gl_data->wglCreateContext(hdc);
544 if (hglrc) {
545 _this->gl_data->wglMakeCurrent(hdc, hglrc);
546
547 if (_this->gl_data->HAS_WGL_ARB_pixel_format) {
548 _this->gl_data->wglChoosePixelFormatARB(hdc, iAttribs, fAttribs,
549 1, &pixel_format,
550 &matching);
551
552 // Check whether we actually got an SRGB capable buffer
553 _this->gl_data->wglGetPixelFormatAttribivARB(hdc, pixel_format, 0, 1, &qAttrib, &srgb);
554 _this->gl_config.framebuffer_srgb_capable = srgb;
555 }
556
557 _this->gl_data->wglMakeCurrent(hdc, NULL);
558 _this->gl_data->wglDeleteContext(hglrc);
559 }
560 ReleaseDC(hwnd, hdc);
561 DestroyWindow(hwnd);
562 WIN_PumpEvents(_this);
563
564 return pixel_format;
565}
566
567// actual work of WIN_GL_SetupWindow() happens here.
568static bool WIN_GL_SetupWindowInternal(SDL_VideoDevice *_this, SDL_Window *window)
569{
570 HDC hdc = window->internal->hdc;
571 PIXELFORMATDESCRIPTOR pfd;
572 int pixel_format = 0;
573 int iAttribs[64];
574 int *iAttr;
575 int *iAccelAttr;
576 float fAttribs[1] = { 0 };
577
578 WIN_GL_SetupPixelFormat(_this, &pfd);
579
580 // setup WGL_ARB_pixel_format attribs
581 iAttr = &iAttribs[0];
582
583 *iAttr++ = WGL_DRAW_TO_WINDOW_ARB;
584 *iAttr++ = GL_TRUE;
585 *iAttr++ = WGL_RED_BITS_ARB;
586 *iAttr++ = _this->gl_config.red_size;
587 *iAttr++ = WGL_GREEN_BITS_ARB;
588 *iAttr++ = _this->gl_config.green_size;
589 *iAttr++ = WGL_BLUE_BITS_ARB;
590 *iAttr++ = _this->gl_config.blue_size;
591
592 if (_this->gl_config.alpha_size) {
593 *iAttr++ = WGL_ALPHA_BITS_ARB;
594 *iAttr++ = _this->gl_config.alpha_size;
595 }
596
597 *iAttr++ = WGL_DOUBLE_BUFFER_ARB;
598 *iAttr++ = _this->gl_config.double_buffer;
599
600 *iAttr++ = WGL_DEPTH_BITS_ARB;
601 *iAttr++ = _this->gl_config.depth_size;
602
603 if (_this->gl_config.stencil_size) {
604 *iAttr++ = WGL_STENCIL_BITS_ARB;
605 *iAttr++ = _this->gl_config.stencil_size;
606 }
607
608 if (_this->gl_config.accum_red_size) {
609 *iAttr++ = WGL_ACCUM_RED_BITS_ARB;
610 *iAttr++ = _this->gl_config.accum_red_size;
611 }
612
613 if (_this->gl_config.accum_green_size) {
614 *iAttr++ = WGL_ACCUM_GREEN_BITS_ARB;
615 *iAttr++ = _this->gl_config.accum_green_size;
616 }
617
618 if (_this->gl_config.accum_blue_size) {
619 *iAttr++ = WGL_ACCUM_BLUE_BITS_ARB;
620 *iAttr++ = _this->gl_config.accum_blue_size;
621 }
622
623 if (_this->gl_config.accum_alpha_size) {
624 *iAttr++ = WGL_ACCUM_ALPHA_BITS_ARB;
625 *iAttr++ = _this->gl_config.accum_alpha_size;
626 }
627
628 if (_this->gl_config.stereo) {
629 *iAttr++ = WGL_STEREO_ARB;
630 *iAttr++ = GL_TRUE;
631 }
632
633 if (_this->gl_config.multisamplebuffers) {
634 *iAttr++ = WGL_SAMPLE_BUFFERS_ARB;
635 *iAttr++ = _this->gl_config.multisamplebuffers;
636 }
637
638 if (_this->gl_config.multisamplesamples) {
639 *iAttr++ = WGL_SAMPLES_ARB;
640 *iAttr++ = _this->gl_config.multisamplesamples;
641 }
642
643 if (_this->gl_config.floatbuffers) {
644 *iAttr++ = WGL_PIXEL_TYPE_ARB;
645 *iAttr++ = WGL_TYPE_RGBA_FLOAT_ARB;
646 }
647
648 if (_this->gl_config.framebuffer_srgb_capable) {
649 *iAttr++ = WGL_FRAMEBUFFER_SRGB_CAPABLE_ARB;
650 *iAttr++ = _this->gl_config.framebuffer_srgb_capable;
651 }
652
653 /* We always choose either FULL or NO accel on Windows, because of flaky
654 drivers. If the app didn't specify, we use FULL, because that's
655 probably what they wanted (and if you didn't care and got FULL, that's
656 a perfectly valid result in any case). */
657 *iAttr++ = WGL_ACCELERATION_ARB;
658 iAccelAttr = iAttr;
659 if (_this->gl_config.accelerated) {
660 *iAttr++ = WGL_FULL_ACCELERATION_ARB;
661 } else {
662 *iAttr++ = WGL_NO_ACCELERATION_ARB;
663 }
664
665 *iAttr = 0;
666
667 // Choose and set the closest available pixel format
668 pixel_format = WIN_GL_ChoosePixelFormatARB(_this, iAttribs, fAttribs);
669
670 // App said "don't care about accel" and FULL accel failed. Try NO.
671 if ((!pixel_format) && (_this->gl_config.accelerated < 0)) {
672 *iAccelAttr = WGL_NO_ACCELERATION_ARB;
673 pixel_format = WIN_GL_ChoosePixelFormatARB(_this, iAttribs, fAttribs);
674 *iAccelAttr = WGL_FULL_ACCELERATION_ARB; // if we try again.
675 }
676 if (!pixel_format) {
677 pixel_format = WIN_GL_ChoosePixelFormat(_this, hdc, &pfd);
678 }
679 if (!pixel_format) {
680 return SDL_SetError("No matching GL pixel format available");
681 }
682 if (!SetPixelFormat(hdc, pixel_format, &pfd)) {
683 return WIN_SetError("SetPixelFormat()");
684 }
685 return true;
686}
687
688bool WIN_GL_SetupWindow(SDL_VideoDevice *_this, SDL_Window *window)
689{
690 // The current context is lost in here; save it and reset it.
691 SDL_Window *current_win = SDL_GL_GetCurrentWindow();
692 SDL_GLContext current_ctx = SDL_GL_GetCurrentContext();
693 const int result = WIN_GL_SetupWindowInternal(_this, window);
694 WIN_GL_MakeCurrent(_this, current_win, current_ctx);
695 return result;
696}
697
698bool WIN_GL_UseEGL(SDL_VideoDevice *_this)
699{
700 SDL_assert(_this->gl_data != NULL);
701 SDL_assert(_this->gl_config.profile_mask == SDL_GL_CONTEXT_PROFILE_ES);
702
703 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.
704}
705
706SDL_GLContext WIN_GL_CreateContext(SDL_VideoDevice *_this, SDL_Window *window)
707{
708 HDC hdc = window->internal->hdc;
709 HGLRC context, share_context;
710
711 if (_this->gl_config.profile_mask == SDL_GL_CONTEXT_PROFILE_ES && WIN_GL_UseEGL(_this)) {
712#ifdef SDL_VIDEO_OPENGL_EGL
713 // Switch to EGL based functions
714 WIN_GL_UnloadLibrary(_this);
715 _this->GL_LoadLibrary = WIN_GLES_LoadLibrary;
716 _this->GL_GetProcAddress = WIN_GLES_GetProcAddress;
717 _this->GL_UnloadLibrary = WIN_GLES_UnloadLibrary;
718 _this->GL_CreateContext = WIN_GLES_CreateContext;
719 _this->GL_MakeCurrent = WIN_GLES_MakeCurrent;
720 _this->GL_SetSwapInterval = WIN_GLES_SetSwapInterval;
721 _this->GL_GetSwapInterval = WIN_GLES_GetSwapInterval;
722 _this->GL_SwapWindow = WIN_GLES_SwapWindow;
723 _this->GL_DestroyContext = WIN_GLES_DestroyContext;
724 _this->GL_GetEGLSurface = WIN_GLES_GetEGLSurface;
725
726 if (!WIN_GLES_LoadLibrary(_this, NULL)) {
727 return NULL;
728 }
729
730 return WIN_GLES_CreateContext(_this, window);
731#else
732 SDL_SetError("SDL not configured with EGL support");
733 return NULL;
734#endif
735 }
736
737 if (_this->gl_config.share_with_current_context) {
738 share_context = (HGLRC)SDL_GL_GetCurrentContext();
739 } else {
740 share_context = 0;
741 }
742
743 if (_this->gl_config.major_version < 3 &&
744 _this->gl_config.profile_mask == 0 &&
745 _this->gl_config.flags == 0) {
746 // Create legacy context
747 context = _this->gl_data->wglCreateContext(hdc);
748 if (share_context != 0) {
749 _this->gl_data->wglShareLists(share_context, context);
750 }
751 } else {
752 PFNWGLCREATECONTEXTATTRIBSARBPROC wglCreateContextAttribsARB;
753 HGLRC temp_context = _this->gl_data->wglCreateContext(hdc);
754 if (!temp_context) {
755 SDL_SetError("Could not create GL context");
756 return NULL;
757 }
758
759 // Make the context current
760 if (!WIN_GL_MakeCurrent(_this, window, (SDL_GLContext)temp_context)) {
761 WIN_GL_DestroyContext(_this, (SDL_GLContext)temp_context);
762 return NULL;
763 }
764
765 wglCreateContextAttribsARB =
766 (PFNWGLCREATECONTEXTATTRIBSARBPROC)_this->gl_data->wglGetProcAddress("wglCreateContextAttribsARB");
767 if (!wglCreateContextAttribsARB) {
768 SDL_SetError("GL 3.x is not supported");
769 context = temp_context;
770 } else {
771 int attribs[15]; // max 14 attributes plus terminator
772 int iattr = 0;
773
774 attribs[iattr++] = WGL_CONTEXT_MAJOR_VERSION_ARB;
775 attribs[iattr++] = _this->gl_config.major_version;
776 attribs[iattr++] = WGL_CONTEXT_MINOR_VERSION_ARB;
777 attribs[iattr++] = _this->gl_config.minor_version;
778
779 // SDL profile bits match WGL profile bits
780 if (_this->gl_config.profile_mask != 0) {
781 attribs[iattr++] = WGL_CONTEXT_PROFILE_MASK_ARB;
782 attribs[iattr++] = _this->gl_config.profile_mask;
783 }
784
785 // SDL flags match WGL flags
786 if (_this->gl_config.flags != 0) {
787 attribs[iattr++] = WGL_CONTEXT_FLAGS_ARB;
788 attribs[iattr++] = _this->gl_config.flags;
789 }
790
791 // only set if wgl extension is available and not the default setting
792 if ((_this->gl_data->HAS_WGL_ARB_context_flush_control) && (_this->gl_config.release_behavior == 0)) {
793 attribs[iattr++] = WGL_CONTEXT_RELEASE_BEHAVIOR_ARB;
794 attribs[iattr++] = _this->gl_config.release_behavior ? WGL_CONTEXT_RELEASE_BEHAVIOR_FLUSH_ARB : WGL_CONTEXT_RELEASE_BEHAVIOR_NONE_ARB;
795 }
796
797 // only set if wgl extension is available and not the default setting
798 if ((_this->gl_data->HAS_WGL_ARB_create_context_robustness) && (_this->gl_config.reset_notification != 0)) {
799 attribs[iattr++] = WGL_CONTEXT_RESET_NOTIFICATION_STRATEGY_ARB;
800 attribs[iattr++] = _this->gl_config.reset_notification ? WGL_LOSE_CONTEXT_ON_RESET_ARB : WGL_NO_RESET_NOTIFICATION_ARB;
801 }
802
803 // only set if wgl extension is available and not the default setting
804 if ((_this->gl_data->HAS_WGL_ARB_create_context_no_error) && (_this->gl_config.no_error != 0)) {
805 attribs[iattr++] = WGL_CONTEXT_OPENGL_NO_ERROR_ARB;
806 attribs[iattr++] = _this->gl_config.no_error;
807 }
808
809 attribs[iattr++] = 0;
810
811 // Create the GL 3.x context
812 context = wglCreateContextAttribsARB(hdc, share_context, attribs);
813 // Delete the GL 2.x context
814 _this->gl_data->wglDeleteContext(temp_context);
815 }
816 }
817
818 if (!context) {
819 WIN_SetError("Could not create GL context");
820 return NULL;
821 }
822
823 if (!WIN_GL_MakeCurrent(_this, window, (SDL_GLContext)context)) {
824 WIN_GL_DestroyContext(_this, (SDL_GLContext)context);
825 return NULL;
826 }
827
828 return (SDL_GLContext)context;
829}
830
831bool WIN_GL_MakeCurrent(SDL_VideoDevice *_this, SDL_Window *window, SDL_GLContext context)
832{
833 HDC hdc;
834
835 if (!_this->gl_data) {
836 return SDL_SetError("OpenGL not initialized");
837 }
838
839 // sanity check that higher level handled this.
840 SDL_assert(window || (window == NULL && !context));
841
842 /* Some Windows drivers freak out if hdc is NULL, even when context is
843 NULL, against spec. Since hdc is _supposed_ to be ignored if context
844 is NULL, we either use the current GL window, or do nothing if we
845 already have no current context. */
846 if (!window) {
847 window = SDL_GL_GetCurrentWindow();
848 if (!window) {
849 SDL_assert(SDL_GL_GetCurrentContext() == NULL);
850 return true; // already done.
851 }
852 }
853
854 hdc = window->internal->hdc;
855 if (!_this->gl_data->wglMakeCurrent(hdc, (HGLRC)context)) {
856 return WIN_SetError("wglMakeCurrent()");
857 }
858 return true;
859}
860
861bool WIN_GL_SetSwapInterval(SDL_VideoDevice *_this, int interval)
862{
863 if ((interval < 0) && (!_this->gl_data->HAS_WGL_EXT_swap_control_tear)) {
864 return SDL_SetError("Negative swap interval unsupported in this GL");
865 } else if (_this->gl_data->wglSwapIntervalEXT) {
866 if (!_this->gl_data->wglSwapIntervalEXT(interval)) {
867 return WIN_SetError("wglSwapIntervalEXT()");
868 }
869 } else {
870 return SDL_Unsupported();
871 }
872 return true;
873}
874
875bool WIN_GL_GetSwapInterval(SDL_VideoDevice *_this, int *interval)
876{
877 if (_this->gl_data->wglGetSwapIntervalEXT) {
878 *interval = _this->gl_data->wglGetSwapIntervalEXT();
879 return true;
880 } else {
881 return false;
882 }
883}
884
885bool WIN_GL_SwapWindow(SDL_VideoDevice *_this, SDL_Window *window)
886{
887 HDC hdc = window->internal->hdc;
888
889 if (!SwapBuffers(hdc)) {
890 return WIN_SetError("SwapBuffers()");
891 }
892 return true;
893}
894
895bool WIN_GL_DestroyContext(SDL_VideoDevice *_this, SDL_GLContext context)
896{
897 if (!_this->gl_data) {
898 return true;
899 }
900 _this->gl_data->wglDeleteContext((HGLRC)context);
901 return true;
902}
903
904#endif // SDL_VIDEO_OPENGL_WGL
905
906#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 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#ifndef SDL_windowsopengl_h_
24#define SDL_windowsopengl_h_
25
26#ifdef SDL_VIDEO_OPENGL_WGL
27
28#if defined(SDL_PLATFORM_XBOXONE) || defined(SDL_PLATFORM_XBOXSERIES)
29typedef struct tagPIXELFORMATDESCRIPTOR
30{
31 WORD nSize;
32 WORD nVersion;
33 DWORD dwFlags;
34 BYTE iPixelType;
35 BYTE cColorBits;
36 BYTE cRedBits;
37 BYTE cRedShift;
38 BYTE cGreenBits;
39 BYTE cGreenShift;
40 BYTE cBlueBits;
41 BYTE cBlueShift;
42 BYTE cAlphaBits;
43 BYTE cAlphaShift;
44 BYTE cAccumBits;
45 BYTE cAccumRedBits;
46 BYTE cAccumGreenBits;
47 BYTE cAccumBlueBits;
48 BYTE cAccumAlphaBits;
49 BYTE cDepthBits;
50 BYTE cStencilBits;
51 BYTE cAuxBuffers;
52 BYTE iLayerType;
53 BYTE bReserved;
54 DWORD dwLayerMask;
55 DWORD dwVisibleMask;
56 DWORD dwDamageMask;
57} PIXELFORMATDESCRIPTOR, *PPIXELFORMATDESCRIPTOR, *LPPIXELFORMATDESCRIPTOR;
58#endif
59
60struct SDL_GLDriverData
61{
62 bool HAS_WGL_ARB_pixel_format;
63 bool HAS_WGL_EXT_swap_control_tear;
64 bool HAS_WGL_ARB_context_flush_control;
65 bool HAS_WGL_ARB_create_context_robustness;
66 bool HAS_WGL_ARB_create_context_no_error;
67
68 /* Max version of OpenGL ES context that can be created if the
69 implementation supports WGL_EXT_create_context_es2_profile.
70 major = minor = 0 when unsupported.
71 */
72 struct
73 {
74 int major;
75 int minor;
76 } es_profile_max_supported_version;
77
78 /* *INDENT-OFF* */ // clang-format off
79 PROC (WINAPI *wglGetProcAddress)(const char *proc);
80 HGLRC (WINAPI *wglCreateContext)(HDC hdc);
81 BOOL (WINAPI *wglDeleteContext)(HGLRC hglrc);
82 BOOL (WINAPI *wglMakeCurrent)(HDC hdc, HGLRC hglrc);
83 BOOL (WINAPI *wglShareLists)(HGLRC hglrc1, HGLRC hglrc2);
84 BOOL (WINAPI *wglChoosePixelFormatARB)(HDC hdc, const int *piAttribIList, const FLOAT * pfAttribFList, UINT nMaxFormats, int *piFormats, UINT * nNumFormats);
85 BOOL (WINAPI *wglGetPixelFormatAttribivARB)(HDC hdc, int iPixelFormat, int iLayerPlane, UINT nAttributes, const int *piAttributes, int *piValues);
86 BOOL (WINAPI *wglSwapIntervalEXT)(int interval);
87 int (WINAPI *wglGetSwapIntervalEXT)(void);
88#if defined(SDL_PLATFORM_XBOXONE) || defined(SDL_PLATFORM_XBOXSERIES)
89 BOOL (WINAPI *wglSwapBuffers)(HDC hdc);
90 int (WINAPI *wglDescribePixelFormat)(HDC hdc,
91 int iPixelFormat,
92 UINT nBytes,
93 LPPIXELFORMATDESCRIPTOR ppfd);
94 int (WINAPI *wglChoosePixelFormat)(HDC hdc,
95 const PIXELFORMATDESCRIPTOR *ppfd);
96 BOOL (WINAPI *wglSetPixelFormat)(HDC hdc,
97 int format,
98 const PIXELFORMATDESCRIPTOR *ppfd);
99 int (WINAPI *wglGetPixelFormat)(HDC hdc);
100#endif
101 /* *INDENT-ON* */ // clang-format on
102};
103
104// OpenGL functions
105extern bool WIN_GL_LoadLibrary(SDL_VideoDevice *_this, const char *path);
106extern SDL_FunctionPointer WIN_GL_GetProcAddress(SDL_VideoDevice *_this, const char *proc);
107extern void WIN_GL_UnloadLibrary(SDL_VideoDevice *_this);
108extern bool WIN_GL_UseEGL(SDL_VideoDevice *_this);
109extern bool WIN_GL_SetupWindow(SDL_VideoDevice *_this, SDL_Window *window);
110extern SDL_GLContext WIN_GL_CreateContext(SDL_VideoDevice *_this, SDL_Window *window);
111extern bool WIN_GL_MakeCurrent(SDL_VideoDevice *_this, SDL_Window *window,
112 SDL_GLContext context);
113extern bool WIN_GL_SetSwapInterval(SDL_VideoDevice *_this, int interval);
114extern bool WIN_GL_GetSwapInterval(SDL_VideoDevice *_this, int *interval);
115extern bool WIN_GL_SwapWindow(SDL_VideoDevice *_this, SDL_Window *window);
116extern bool WIN_GL_DestroyContext(SDL_VideoDevice *_this, SDL_GLContext context);
117extern void WIN_GL_InitExtensions(SDL_VideoDevice *_this);
118
119#ifndef WGL_ARB_pixel_format
120#define WGL_NUMBER_PIXEL_FORMATS_ARB 0x2000
121#define WGL_DRAW_TO_WINDOW_ARB 0x2001
122#define WGL_DRAW_TO_BITMAP_ARB 0x2002
123#define WGL_ACCELERATION_ARB 0x2003
124#define WGL_NEED_PALETTE_ARB 0x2004
125#define WGL_NEED_SYSTEM_PALETTE_ARB 0x2005
126#define WGL_SWAP_LAYER_BUFFERS_ARB 0x2006
127#define WGL_SWAP_METHOD_ARB 0x2007
128#define WGL_NUMBER_OVERLAYS_ARB 0x2008
129#define WGL_NUMBER_UNDERLAYS_ARB 0x2009
130#define WGL_TRANSPARENT_ARB 0x200A
131#define WGL_TRANSPARENT_RED_VALUE_ARB 0x2037
132#define WGL_TRANSPARENT_GREEN_VALUE_ARB 0x2038
133#define WGL_TRANSPARENT_BLUE_VALUE_ARB 0x2039
134#define WGL_TRANSPARENT_ALPHA_VALUE_ARB 0x203A
135#define WGL_TRANSPARENT_INDEX_VALUE_ARB 0x203B
136#define WGL_SHARE_DEPTH_ARB 0x200C
137#define WGL_SHARE_STENCIL_ARB 0x200D
138#define WGL_SHARE_ACCUM_ARB 0x200E
139#define WGL_SUPPORT_GDI_ARB 0x200F
140#define WGL_SUPPORT_OPENGL_ARB 0x2010
141#define WGL_DOUBLE_BUFFER_ARB 0x2011
142#define WGL_STEREO_ARB 0x2012
143#define WGL_PIXEL_TYPE_ARB 0x2013
144#define WGL_COLOR_BITS_ARB 0x2014
145#define WGL_RED_BITS_ARB 0x2015
146#define WGL_RED_SHIFT_ARB 0x2016
147#define WGL_GREEN_BITS_ARB 0x2017
148#define WGL_GREEN_SHIFT_ARB 0x2018
149#define WGL_BLUE_BITS_ARB 0x2019
150#define WGL_BLUE_SHIFT_ARB 0x201A
151#define WGL_ALPHA_BITS_ARB 0x201B
152#define WGL_ALPHA_SHIFT_ARB 0x201C
153#define WGL_ACCUM_BITS_ARB 0x201D
154#define WGL_ACCUM_RED_BITS_ARB 0x201E
155#define WGL_ACCUM_GREEN_BITS_ARB 0x201F
156#define WGL_ACCUM_BLUE_BITS_ARB 0x2020
157#define WGL_ACCUM_ALPHA_BITS_ARB 0x2021
158#define WGL_DEPTH_BITS_ARB 0x2022
159#define WGL_STENCIL_BITS_ARB 0x2023
160#define WGL_AUX_BUFFERS_ARB 0x2024
161#define WGL_NO_ACCELERATION_ARB 0x2025
162#define WGL_GENERIC_ACCELERATION_ARB 0x2026
163#define WGL_FULL_ACCELERATION_ARB 0x2027
164#define WGL_SWAP_EXCHANGE_ARB 0x2028
165#define WGL_SWAP_COPY_ARB 0x2029
166#define WGL_SWAP_UNDEFINED_ARB 0x202A
167#define WGL_TYPE_RGBA_ARB 0x202B
168#define WGL_TYPE_COLORINDEX_ARB 0x202C
169#endif
170
171#ifndef WGL_ARB_multisample
172#define WGL_SAMPLE_BUFFERS_ARB 0x2041
173#define WGL_SAMPLES_ARB 0x2042
174#endif
175
176#endif // SDL_VIDEO_OPENGL_WGL
177
178#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 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#if defined(SDL_VIDEO_DRIVER_WINDOWS) && defined(SDL_VIDEO_OPENGL_EGL) && !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
24
25#include "SDL_windowsvideo.h"
26#include "SDL_windowsopengles.h"
27#include "SDL_windowsopengl.h"
28#include "SDL_windowswindow.h"
29
30// EGL implementation of SDL OpenGL support
31
32bool WIN_GLES_LoadLibrary(SDL_VideoDevice *_this, const char *path)
33{
34
35 // If the profile requested is not GL ES, switch over to WIN_GL functions
36 if (_this->gl_config.profile_mask != SDL_GL_CONTEXT_PROFILE_ES &&
37 !SDL_GetHintBoolean(SDL_HINT_VIDEO_FORCE_EGL, false)) {
38#ifdef SDL_VIDEO_OPENGL_WGL
39 WIN_GLES_UnloadLibrary(_this);
40 _this->GL_LoadLibrary = WIN_GL_LoadLibrary;
41 _this->GL_GetProcAddress = WIN_GL_GetProcAddress;
42 _this->GL_UnloadLibrary = WIN_GL_UnloadLibrary;
43 _this->GL_CreateContext = WIN_GL_CreateContext;
44 _this->GL_MakeCurrent = WIN_GL_MakeCurrent;
45 _this->GL_SetSwapInterval = WIN_GL_SetSwapInterval;
46 _this->GL_GetSwapInterval = WIN_GL_GetSwapInterval;
47 _this->GL_SwapWindow = WIN_GL_SwapWindow;
48 _this->GL_DestroyContext = WIN_GL_DestroyContext;
49 _this->GL_GetEGLSurface = NULL;
50 return WIN_GL_LoadLibrary(_this, path);
51#else
52 return SDL_SetError("SDL not configured with OpenGL/WGL support");
53#endif
54 }
55
56 if (!_this->egl_data) {
57 return SDL_EGL_LoadLibrary(_this, NULL, EGL_DEFAULT_DISPLAY, _this->gl_config.egl_platform);
58 }
59
60 return true;
61}
62
63SDL_GLContext WIN_GLES_CreateContext(SDL_VideoDevice *_this, SDL_Window *window)
64{
65 SDL_GLContext context;
66 SDL_WindowData *data = window->internal;
67
68#ifdef SDL_VIDEO_OPENGL_WGL
69 if (_this->gl_config.profile_mask != SDL_GL_CONTEXT_PROFILE_ES &&
70 !SDL_GetHintBoolean(SDL_HINT_VIDEO_FORCE_EGL, false)) {
71 // Switch to WGL based functions
72 WIN_GLES_UnloadLibrary(_this);
73 _this->GL_LoadLibrary = WIN_GL_LoadLibrary;
74 _this->GL_GetProcAddress = WIN_GL_GetProcAddress;
75 _this->GL_UnloadLibrary = WIN_GL_UnloadLibrary;
76 _this->GL_CreateContext = WIN_GL_CreateContext;
77 _this->GL_MakeCurrent = WIN_GL_MakeCurrent;
78 _this->GL_SetSwapInterval = WIN_GL_SetSwapInterval;
79 _this->GL_GetSwapInterval = WIN_GL_GetSwapInterval;
80 _this->GL_SwapWindow = WIN_GL_SwapWindow;
81 _this->GL_DestroyContext = WIN_GL_DestroyContext;
82 _this->GL_GetEGLSurface = NULL;
83
84 if (!WIN_GL_LoadLibrary(_this, NULL)) {
85 return NULL;
86 }
87
88 return WIN_GL_CreateContext(_this, window);
89 }
90#endif
91
92 context = SDL_EGL_CreateContext(_this, data->egl_surface);
93 return context;
94}
95
96bool WIN_GLES_DestroyContext(SDL_VideoDevice *_this, SDL_GLContext context)
97{
98 return SDL_EGL_DestroyContext(_this, context);
99}
100
101/* *INDENT-OFF* */ // clang-format off
102SDL_EGL_SwapWindow_impl(WIN)
103SDL_EGL_MakeCurrent_impl(WIN)
104/* *INDENT-ON* */ // clang-format on
105
106bool WIN_GLES_SetupWindow(SDL_VideoDevice *_this, SDL_Window *window)
107{
108 // The current context is lost in here; save it and reset it.
109 SDL_WindowData *windowdata = window->internal;
110 SDL_Window *current_win = SDL_GL_GetCurrentWindow();
111 SDL_GLContext current_ctx = SDL_GL_GetCurrentContext();
112
113 if (!_this->egl_data) {
114// !!! FIXME: commenting out this assertion is (I think) incorrect; figure out why driver_loaded is wrong for ANGLE instead. --ryan.
115#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.
116 SDL_assert(!_this->gl_config.driver_loaded);
117#endif
118 if (!SDL_EGL_LoadLibrary(_this, NULL, EGL_DEFAULT_DISPLAY, _this->gl_config.egl_platform)) {
119 SDL_EGL_UnloadLibrary(_this);
120 return false;
121 }
122 _this->gl_config.driver_loaded = 1;
123 }
124
125 // Create the GLES window surface
126 windowdata->egl_surface = SDL_EGL_CreateSurface(_this, window, (NativeWindowType)windowdata->hwnd);
127
128 if (windowdata->egl_surface == EGL_NO_SURFACE) {
129 return SDL_SetError("Could not create GLES window surface");
130 }
131
132 return WIN_GLES_MakeCurrent(_this, current_win, current_ctx);
133}
134
135EGLSurface WIN_GLES_GetEGLSurface(SDL_VideoDevice *_this, SDL_Window *window)
136{
137 SDL_WindowData *windowdata = window->internal;
138
139 return windowdata->egl_surface;
140}
141
142#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 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#ifndef SDL_winopengles_h_
24#define SDL_winopengles_h_
25
26#ifdef SDL_VIDEO_OPENGL_EGL
27
28#include "../SDL_sysvideo.h"
29#include "../SDL_egl_c.h"
30
31// OpenGLES functions
32#define WIN_GLES_GetAttribute SDL_EGL_GetAttribute
33#define WIN_GLES_GetProcAddress SDL_EGL_GetProcAddressInternal
34#define WIN_GLES_UnloadLibrary SDL_EGL_UnloadLibrary
35#define WIN_GLES_GetSwapInterval SDL_EGL_GetSwapInterval
36#define WIN_GLES_SetSwapInterval SDL_EGL_SetSwapInterval
37
38extern bool WIN_GLES_LoadLibrary(SDL_VideoDevice *_this, const char *path);
39extern SDL_GLContext WIN_GLES_CreateContext(SDL_VideoDevice *_this, SDL_Window *window);
40extern bool WIN_GLES_SwapWindow(SDL_VideoDevice *_this, SDL_Window *window);
41extern bool WIN_GLES_MakeCurrent(SDL_VideoDevice *_this, SDL_Window *window, SDL_GLContext context);
42extern bool WIN_GLES_DestroyContext(SDL_VideoDevice *_this, SDL_GLContext context);
43extern bool WIN_GLES_SetupWindow(SDL_VideoDevice *_this, SDL_Window *window);
44extern SDL_EGLSurface WIN_GLES_GetEGLSurface(SDL_VideoDevice *_this, SDL_Window *window);
45
46#endif // SDL_VIDEO_OPENGL_EGL
47
48#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 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#if defined(SDL_VIDEO_DRIVER_WINDOWS)
24
25#include "SDL_windowsvideo.h"
26
27#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
28
29#include "SDL_windowsevents.h"
30
31#include "../../joystick/usb_ids.h"
32#include "../../events/SDL_events_c.h"
33
34#define ENABLE_RAW_MOUSE_INPUT 0x01
35#define ENABLE_RAW_KEYBOARD_INPUT 0x02
36
37typedef struct
38{
39 bool done;
40 Uint32 flags;
41 HANDLE ready_event;
42 HANDLE done_event;
43 HANDLE thread;
44} RawInputThreadData;
45
46static RawInputThreadData thread_data = {
47 false,
48 0,
49 INVALID_HANDLE_VALUE,
50 INVALID_HANDLE_VALUE,
51 INVALID_HANDLE_VALUE
52};
53
54static DWORD WINAPI WIN_RawInputThread(LPVOID param)
55{
56 SDL_VideoDevice *_this = SDL_GetVideoDevice();
57 RawInputThreadData *data = (RawInputThreadData *)param;
58 RAWINPUTDEVICE devices[2];
59 HWND window;
60 UINT count = 0;
61
62 window = CreateWindowEx(0, TEXT("Message"), NULL, 0, 0, 0, 0, 0, HWND_MESSAGE, NULL, NULL, NULL);
63 if (!window) {
64 return 0;
65 }
66
67 SDL_zeroa(devices);
68
69 if (data->flags & ENABLE_RAW_MOUSE_INPUT) {
70 devices[count].usUsagePage = USB_USAGEPAGE_GENERIC_DESKTOP;
71 devices[count].usUsage = USB_USAGE_GENERIC_MOUSE;
72 devices[count].dwFlags = 0;
73 devices[count].hwndTarget = window;
74 ++count;
75 }
76
77 if (data->flags & ENABLE_RAW_KEYBOARD_INPUT) {
78 devices[count].usUsagePage = USB_USAGEPAGE_GENERIC_DESKTOP;
79 devices[count].usUsage = USB_USAGE_GENERIC_KEYBOARD;
80 devices[count].dwFlags = 0;
81 devices[count].hwndTarget = window;
82 ++count;
83 }
84
85 if (!RegisterRawInputDevices(devices, count, sizeof(devices[0]))) {
86 DestroyWindow(window);
87 return 0;
88 }
89
90 // Make sure we get events as soon as possible
91 SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL);
92
93 // Tell the parent we're ready to go!
94 SetEvent(data->ready_event);
95
96 while (!data->done) {
97 Uint64 idle_begin = SDL_GetTicksNS();
98 DWORD result = MsgWaitForMultipleObjects(1, &data->done_event, FALSE, INFINITE, QS_RAWINPUT);
99 Uint64 idle_end = SDL_GetTicksNS();
100 if (result != (WAIT_OBJECT_0 + 1)) {
101 break;
102 }
103
104 // Clear the queue status so MsgWaitForMultipleObjects() will wait again
105 (void)GetQueueStatus(QS_RAWINPUT);
106
107 Uint64 idle_time = idle_end - idle_begin;
108 Uint64 usb_8khz_interval = SDL_US_TO_NS(125);
109 Uint64 poll_start = idle_time < usb_8khz_interval ? _this->internal->last_rawinput_poll : idle_end;
110
111 WIN_PollRawInput(_this, poll_start);
112 }
113
114 devices[0].dwFlags |= RIDEV_REMOVE;
115 devices[1].dwFlags |= RIDEV_REMOVE;
116 RegisterRawInputDevices(devices, count, sizeof(devices[0]));
117
118 DestroyWindow(window);
119
120 return 0;
121}
122
123static void CleanupRawInputThreadData(RawInputThreadData *data)
124{
125 if (data->thread != INVALID_HANDLE_VALUE) {
126 data->done = true;
127 SetEvent(data->done_event);
128 WaitForSingleObject(data->thread, 3000);
129 CloseHandle(data->thread);
130 data->thread = INVALID_HANDLE_VALUE;
131 }
132
133 if (data->ready_event != INVALID_HANDLE_VALUE) {
134 CloseHandle(data->ready_event);
135 data->ready_event = INVALID_HANDLE_VALUE;
136 }
137
138 if (data->done_event != INVALID_HANDLE_VALUE) {
139 CloseHandle(data->done_event);
140 data->done_event = INVALID_HANDLE_VALUE;
141 }
142}
143
144static bool WIN_SetRawInputEnabled(SDL_VideoDevice *_this, Uint32 flags)
145{
146 bool result = false;
147
148 CleanupRawInputThreadData(&thread_data);
149
150 if (flags) {
151 HANDLE handles[2];
152
153 thread_data.flags = flags;
154 thread_data.ready_event = CreateEvent(NULL, FALSE, FALSE, NULL);
155 if (thread_data.ready_event == INVALID_HANDLE_VALUE) {
156 WIN_SetError("CreateEvent");
157 goto done;
158 }
159
160 thread_data.done = false;
161 thread_data.done_event = CreateEvent(NULL, FALSE, FALSE, NULL);
162 if (thread_data.done_event == INVALID_HANDLE_VALUE) {
163 WIN_SetError("CreateEvent");
164 goto done;
165 }
166
167 thread_data.thread = CreateThread(NULL, 0, WIN_RawInputThread, &thread_data, 0, NULL);
168 if (thread_data.thread == INVALID_HANDLE_VALUE) {
169 WIN_SetError("CreateThread");
170 goto done;
171 }
172
173 // Wait for the thread to signal ready or exit
174 handles[0] = thread_data.ready_event;
175 handles[1] = thread_data.thread;
176 if (WaitForMultipleObjects(2, handles, FALSE, INFINITE) != WAIT_OBJECT_0) {
177 SDL_SetError("Couldn't set up raw input handling");
178 goto done;
179 }
180 result = true;
181 } else {
182 result = true;
183 }
184
185done:
186 if (!result) {
187 CleanupRawInputThreadData(&thread_data);
188 }
189 return result;
190}
191
192static bool WIN_UpdateRawInputEnabled(SDL_VideoDevice *_this)
193{
194 SDL_VideoData *data = _this->internal;
195 Uint32 flags = 0;
196 if (data->raw_mouse_enabled) {
197 flags |= ENABLE_RAW_MOUSE_INPUT;
198 }
199 if (data->raw_keyboard_enabled) {
200 flags |= ENABLE_RAW_KEYBOARD_INPUT;
201 }
202 if (flags != data->raw_input_enabled) {
203 if (WIN_SetRawInputEnabled(_this, flags)) {
204 data->raw_input_enabled = flags;
205 } else {
206 return false;
207 }
208 }
209 return true;
210}
211
212bool WIN_SetRawMouseEnabled(SDL_VideoDevice *_this, bool enabled)
213{
214 SDL_VideoData *data = _this->internal;
215 data->raw_mouse_enabled = enabled;
216 if (data->gameinput_context) {
217 if (!WIN_UpdateGameInputEnabled(_this)) {
218 data->raw_mouse_enabled = !enabled;
219 return false;
220 }
221 } else {
222 if (!WIN_UpdateRawInputEnabled(_this)) {
223 data->raw_mouse_enabled = !enabled;
224 return false;
225 }
226 }
227 return true;
228}
229
230bool WIN_SetRawKeyboardEnabled(SDL_VideoDevice *_this, bool enabled)
231{
232 SDL_VideoData *data = _this->internal;
233 data->raw_keyboard_enabled = enabled;
234 if (data->gameinput_context) {
235 if (!WIN_UpdateGameInputEnabled(_this)) {
236 data->raw_keyboard_enabled = !enabled;
237 return false;
238 }
239 } else {
240 if (!WIN_UpdateRawInputEnabled(_this)) {
241 data->raw_keyboard_enabled = !enabled;
242 return false;
243 }
244 }
245 return true;
246}
247
248#else
249
250bool WIN_SetRawMouseEnabled(SDL_VideoDevice *_this, bool enabled)
251{
252 return SDL_Unsupported();
253}
254
255bool WIN_SetRawKeyboardEnabled(SDL_VideoDevice *_this, bool enabled)
256{
257 return SDL_Unsupported();
258}
259
260#endif // !SDL_PLATFORM_XBOXONE && !SDL_PLATFORM_XBOXSERIES
261
262#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 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#ifndef SDL_windowsrawinput_h_
24#define SDL_windowsrawinput_h_
25
26extern bool WIN_SetRawMouseEnabled(SDL_VideoDevice *_this, bool enabled);
27extern bool WIN_SetRawKeyboardEnabled(SDL_VideoDevice *_this, bool enabled);
28extern bool WIN_RefreshRawInputEnabled(SDL_VideoDevice *_this);
29
30#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 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#if defined(SDL_VIDEO_DRIVER_WINDOWS) && !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
24
25#include "SDL_windowsvideo.h"
26#include "SDL_windowsshape.h"
27
28
29static void AddRegion(HRGN *mask, int x1, int y1, int x2, int y2)
30{
31 HRGN region = CreateRectRgn(x1, y1, x2, y2);
32 if (*mask) {
33 CombineRgn(*mask, *mask, region, RGN_OR);
34 DeleteObject(region);
35 } else {
36 *mask = region;
37 }
38}
39
40static HRGN GenerateSpanListRegion(SDL_Surface *shape, int offset_x, int offset_y)
41{
42 HRGN mask = NULL;
43 int x, y;
44 int span_start = -1;
45
46 for (y = 0; y < shape->h; ++y) {
47 const Uint8 *a = (const Uint8 *)shape->pixels + y * shape->pitch;
48 for (x = 0; x < shape->w; ++x) {
49 if (*a == SDL_ALPHA_TRANSPARENT) {
50 if (span_start != -1) {
51 AddRegion(&mask, offset_x + span_start, offset_y + y, offset_x + x, offset_y + y + 1);
52 span_start = -1;
53 }
54 } else {
55 if (span_start == -1) {
56 span_start = x;
57 }
58 }
59 a += 4;
60 }
61 if (span_start != -1) {
62 // Add the final span
63 AddRegion(&mask, offset_x + span_start, offset_y + y, offset_x + x, offset_y + y + 1);
64 span_start = -1;
65 }
66 }
67 return mask;
68}
69
70bool WIN_UpdateWindowShape(SDL_VideoDevice *_this, SDL_Window *window, SDL_Surface *shape)
71{
72 SDL_WindowData *data = window->internal;
73 HRGN mask = NULL;
74
75 // Generate a set of spans for the region
76 if (shape) {
77 SDL_Surface *stretched = NULL;
78 RECT rect;
79
80 if (shape->w != window->w || shape->h != window->h) {
81 stretched = SDL_CreateSurface(window->w, window->h, SDL_PIXELFORMAT_ARGB32);
82 if (!stretched) {
83 return false;
84 }
85 if (!SDL_StretchSurface(shape, NULL, stretched, NULL, SDL_SCALEMODE_LINEAR)) {
86 SDL_DestroySurface(stretched);
87 return false;
88 }
89 shape = stretched;
90 }
91
92 rect.top = 0;
93 rect.left = 0;
94 rect.bottom = 0;
95 rect.right = 0;
96 if (!(SDL_GetWindowFlags(data->window) & SDL_WINDOW_BORDERLESS)) {
97 WIN_AdjustWindowRectForHWND(data->hwnd, &rect, 0);
98 }
99
100 mask = GenerateSpanListRegion(shape, -rect.left, -rect.top);
101
102 if (!(SDL_GetWindowFlags(data->window) & SDL_WINDOW_BORDERLESS)) {
103 // Add the window borders
104 // top
105 AddRegion(&mask, 0, 0, -rect.left + shape->w + rect.right + 1, -rect.top + 1);
106 // left
107 AddRegion(&mask, 0, -rect.top, -rect.left + 1, -rect.top + shape->h + 1);
108 // right
109 AddRegion(&mask, -rect.left + shape->w, -rect.top, -rect.left + shape->w + rect.right + 1, -rect.top + shape->h + 1);
110 // bottom
111 AddRegion(&mask, 0, -rect.top + shape->h, -rect.left + shape->w + rect.right + 1, -rect.top + shape->h + rect.bottom + 1);
112 }
113
114 if (stretched) {
115 SDL_DestroySurface(stretched);
116 }
117 }
118 if (!SetWindowRgn(data->hwnd, mask, TRUE)) {
119 DeleteObject(mask);
120 return WIN_SetError("SetWindowRgn failed");
121 }
122 return true;
123}
124
125#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 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#ifndef SDL_windowsshape_h_
24#define SDL_windowsshape_h_
25
26extern bool WIN_UpdateWindowShape(SDL_VideoDevice *_this, SDL_Window *window, SDL_Surface *shape);
27
28#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 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#ifdef SDL_VIDEO_DRIVER_WINDOWS
24
25#ifdef SDL_VIDEO_VULKAN
26#include "../SDL_vulkan_internal.h"
27#endif
28#include "../SDL_sysvideo.h"
29#include "../SDL_pixels_c.h"
30#include "../../SDL_hints_c.h"
31#include "../../core/windows/SDL_hid.h"
32
33#include "SDL_windowsvideo.h"
34#include "SDL_windowsframebuffer.h"
35#include "SDL_windowsmessagebox.h"
36#include "SDL_windowsrawinput.h"
37#include "SDL_windowsvulkan.h"
38
39#ifdef SDL_GDK_TEXTINPUT
40#include "../gdk/SDL_gdktextinput.h"
41#endif
42
43// #define HIGHDPI_DEBUG
44
45// Initialization/Query functions
46static bool WIN_VideoInit(SDL_VideoDevice *_this);
47static void WIN_VideoQuit(SDL_VideoDevice *_this);
48
49// Hints
50bool g_WindowsEnableMessageLoop = true;
51bool g_WindowsEnableMenuMnemonics = false;
52bool g_WindowFrameUsableWhileCursorHidden = true;
53
54static void SDLCALL UpdateWindowsRawKeyboard(void *userdata, const char *name, const char *oldValue, const char *newValue)
55{
56 SDL_VideoDevice *_this = (SDL_VideoDevice *)userdata;
57 bool enabled = SDL_GetStringBoolean(newValue, false);
58 WIN_SetRawKeyboardEnabled(_this, enabled);
59}
60
61static void SDLCALL UpdateWindowsEnableMessageLoop(void *userdata, const char *name, const char *oldValue, const char *newValue)
62{
63 g_WindowsEnableMessageLoop = SDL_GetStringBoolean(newValue, true);
64}
65
66static void SDLCALL UpdateWindowsEnableMenuMnemonics(void *userdata, const char *name, const char *oldValue, const char *newValue)
67{
68 g_WindowsEnableMenuMnemonics = SDL_GetStringBoolean(newValue, false);
69}
70
71static void SDLCALL UpdateWindowFrameUsableWhileCursorHidden(void *userdata, const char *name, const char *oldValue, const char *newValue)
72{
73 g_WindowFrameUsableWhileCursorHidden = SDL_GetStringBoolean(newValue, true);
74}
75
76#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
77static bool WIN_SuspendScreenSaver(SDL_VideoDevice *_this)
78{
79 DWORD result;
80 if (_this->suspend_screensaver) {
81 result = SetThreadExecutionState(ES_CONTINUOUS | ES_DISPLAY_REQUIRED);
82 } else {
83 result = SetThreadExecutionState(ES_CONTINUOUS);
84 }
85 if (result == 0) {
86 SDL_SetError("SetThreadExecutionState() failed");
87 return false;
88 }
89 return true;
90}
91#endif
92
93#if defined(SDL_PLATFORM_XBOXONE) || defined(SDL_PLATFORM_XBOXSERIES)
94extern void D3D12_XBOX_GetResolution(Uint32 *width, Uint32 *height);
95#endif
96
97// Windows driver bootstrap functions
98
99static void WIN_DeleteDevice(SDL_VideoDevice *device)
100{
101 SDL_VideoData *data = device->internal;
102
103 SDL_UnregisterApp();
104#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
105 if (data->userDLL) {
106 SDL_UnloadObject(data->userDLL);
107 }
108 if (data->shcoreDLL) {
109 SDL_UnloadObject(data->shcoreDLL);
110 }
111#endif
112#ifdef HAVE_DXGI_H
113 if (data->pDXGIFactory) {
114 IDXGIFactory_Release(data->pDXGIFactory);
115 }
116 if (data->dxgiDLL) {
117 SDL_UnloadObject(data->dxgiDLL);
118 }
119#endif
120 if (device->wakeup_lock) {
121 SDL_DestroyMutex(device->wakeup_lock);
122 }
123 SDL_free(device->internal->rawinput);
124 SDL_free(device->internal);
125 SDL_free(device);
126}
127
128static SDL_VideoDevice *WIN_CreateDevice(void)
129{
130 SDL_VideoDevice *device;
131 SDL_VideoData *data;
132
133 SDL_RegisterApp(NULL, 0, NULL);
134
135 // Initialize all variables that we clean on shutdown
136 device = (SDL_VideoDevice *)SDL_calloc(1, sizeof(SDL_VideoDevice));
137 if (device) {
138 data = (SDL_VideoData *)SDL_calloc(1, sizeof(SDL_VideoData));
139 } else {
140 data = NULL;
141 }
142 if (!data) {
143 SDL_UnregisterApp();
144 SDL_free(device);
145 return NULL;
146 }
147 device->internal = data;
148 device->wakeup_lock = SDL_CreateMutex();
149 device->system_theme = WIN_GetSystemTheme();
150
151#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
152 data->userDLL = SDL_LoadObject("USER32.DLL");
153 if (data->userDLL) {
154 /* *INDENT-OFF* */ // clang-format off
155 data->CloseTouchInputHandle = (BOOL (WINAPI *)(HTOUCHINPUT))SDL_LoadFunction(data->userDLL, "CloseTouchInputHandle");
156 data->GetTouchInputInfo = (BOOL (WINAPI *)(HTOUCHINPUT, UINT, PTOUCHINPUT, int)) SDL_LoadFunction(data->userDLL, "GetTouchInputInfo");
157 data->RegisterTouchWindow = (BOOL (WINAPI *)(HWND, ULONG))SDL_LoadFunction(data->userDLL, "RegisterTouchWindow");
158 data->SetProcessDPIAware = (BOOL (WINAPI *)(void))SDL_LoadFunction(data->userDLL, "SetProcessDPIAware");
159 data->SetProcessDpiAwarenessContext = (BOOL (WINAPI *)(DPI_AWARENESS_CONTEXT))SDL_LoadFunction(data->userDLL, "SetProcessDpiAwarenessContext");
160 data->SetThreadDpiAwarenessContext = (DPI_AWARENESS_CONTEXT (WINAPI *)(DPI_AWARENESS_CONTEXT))SDL_LoadFunction(data->userDLL, "SetThreadDpiAwarenessContext");
161 data->GetThreadDpiAwarenessContext = (DPI_AWARENESS_CONTEXT (WINAPI *)(void))SDL_LoadFunction(data->userDLL, "GetThreadDpiAwarenessContext");
162 data->GetAwarenessFromDpiAwarenessContext = (DPI_AWARENESS (WINAPI *)(DPI_AWARENESS_CONTEXT))SDL_LoadFunction(data->userDLL, "GetAwarenessFromDpiAwarenessContext");
163 data->EnableNonClientDpiScaling = (BOOL (WINAPI *)(HWND))SDL_LoadFunction(data->userDLL, "EnableNonClientDpiScaling");
164 data->AdjustWindowRectExForDpi = (BOOL (WINAPI *)(LPRECT, DWORD, BOOL, DWORD, UINT))SDL_LoadFunction(data->userDLL, "AdjustWindowRectExForDpi");
165 data->GetDpiForWindow = (UINT (WINAPI *)(HWND))SDL_LoadFunction(data->userDLL, "GetDpiForWindow");
166 data->AreDpiAwarenessContextsEqual = (BOOL (WINAPI *)(DPI_AWARENESS_CONTEXT, DPI_AWARENESS_CONTEXT))SDL_LoadFunction(data->userDLL, "AreDpiAwarenessContextsEqual");
167 data->IsValidDpiAwarenessContext = (BOOL (WINAPI *)(DPI_AWARENESS_CONTEXT))SDL_LoadFunction(data->userDLL, "IsValidDpiAwarenessContext");
168 data->GetDisplayConfigBufferSizes = (LONG (WINAPI *)(UINT32,UINT32*,UINT32* ))SDL_LoadFunction(data->userDLL, "GetDisplayConfigBufferSizes");
169 data->QueryDisplayConfig = (LONG (WINAPI *)(UINT32,UINT32*,DISPLAYCONFIG_PATH_INFO*,UINT32*,DISPLAYCONFIG_MODE_INFO*,DISPLAYCONFIG_TOPOLOGY_ID*))SDL_LoadFunction(data->userDLL, "QueryDisplayConfig");
170 data->DisplayConfigGetDeviceInfo = (LONG (WINAPI *)(DISPLAYCONFIG_DEVICE_INFO_HEADER*))SDL_LoadFunction(data->userDLL, "DisplayConfigGetDeviceInfo");
171 data->GetPointerType = (BOOL (WINAPI *)(UINT32, POINTER_INPUT_TYPE *))SDL_LoadFunction(data->userDLL, "GetPointerType");
172 data->GetPointerPenInfo = (BOOL (WINAPI *)(UINT32, POINTER_PEN_INFO *))SDL_LoadFunction(data->userDLL, "GetPointerPenInfo");
173 /* *INDENT-ON* */ // clang-format on
174 } else {
175 SDL_ClearError();
176 }
177
178 data->shcoreDLL = SDL_LoadObject("SHCORE.DLL");
179 if (data->shcoreDLL) {
180 /* *INDENT-OFF* */ // clang-format off
181 data->GetDpiForMonitor = (HRESULT (WINAPI *)(HMONITOR, MONITOR_DPI_TYPE, UINT *, UINT *))SDL_LoadFunction(data->shcoreDLL, "GetDpiForMonitor");
182 data->SetProcessDpiAwareness = (HRESULT (WINAPI *)(PROCESS_DPI_AWARENESS))SDL_LoadFunction(data->shcoreDLL, "SetProcessDpiAwareness");
183 /* *INDENT-ON* */ // clang-format on
184 } else {
185 SDL_ClearError();
186 }
187#endif // #if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
188
189#ifdef HAVE_DXGI_H
190 data->dxgiDLL = SDL_LoadObject("DXGI.DLL");
191 if (data->dxgiDLL) {
192 /* *INDENT-OFF* */ // clang-format off
193 typedef HRESULT (WINAPI *CreateDXGI_t)(REFIID riid, void **ppFactory);
194 /* *INDENT-ON* */ // clang-format on
195 CreateDXGI_t CreateDXGI;
196
197 CreateDXGI = (CreateDXGI_t)SDL_LoadFunction(data->dxgiDLL, "CreateDXGIFactory");
198 if (CreateDXGI) {
199 GUID dxgiGUID = { 0x7b7166ec, 0x21c7, 0x44ae, { 0xb2, 0x1a, 0xc9, 0xae, 0x32, 0x1a, 0xe3, 0x69 } };
200 CreateDXGI(&dxgiGUID, (void **)&data->pDXGIFactory);
201 }
202 }
203#endif
204
205 // Set the function pointers
206 device->VideoInit = WIN_VideoInit;
207 device->VideoQuit = WIN_VideoQuit;
208#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
209 device->RefreshDisplays = WIN_RefreshDisplays;
210 device->GetDisplayBounds = WIN_GetDisplayBounds;
211 device->GetDisplayUsableBounds = WIN_GetDisplayUsableBounds;
212 device->GetDisplayModes = WIN_GetDisplayModes;
213 device->SetDisplayMode = WIN_SetDisplayMode;
214#endif
215 device->PumpEvents = WIN_PumpEvents;
216 device->WaitEventTimeout = WIN_WaitEventTimeout;
217#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
218 device->SendWakeupEvent = WIN_SendWakeupEvent;
219 device->SuspendScreenSaver = WIN_SuspendScreenSaver;
220#endif
221
222 device->CreateSDLWindow = WIN_CreateWindow;
223 device->SetWindowTitle = WIN_SetWindowTitle;
224 device->SetWindowIcon = WIN_SetWindowIcon;
225 device->SetWindowPosition = WIN_SetWindowPosition;
226 device->SetWindowSize = WIN_SetWindowSize;
227 device->GetWindowBordersSize = WIN_GetWindowBordersSize;
228 device->GetWindowSizeInPixels = WIN_GetWindowSizeInPixels;
229 device->SetWindowOpacity = WIN_SetWindowOpacity;
230 device->ShowWindow = WIN_ShowWindow;
231 device->HideWindow = WIN_HideWindow;
232 device->RaiseWindow = WIN_RaiseWindow;
233 device->MaximizeWindow = WIN_MaximizeWindow;
234 device->MinimizeWindow = WIN_MinimizeWindow;
235 device->RestoreWindow = WIN_RestoreWindow;
236 device->SetWindowBordered = WIN_SetWindowBordered;
237 device->SetWindowResizable = WIN_SetWindowResizable;
238 device->SetWindowAlwaysOnTop = WIN_SetWindowAlwaysOnTop;
239 device->SetWindowFullscreen = WIN_SetWindowFullscreen;
240 device->SetWindowParent = WIN_SetWindowParent;
241 device->SetWindowModal = WIN_SetWindowModal;
242#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
243 device->GetWindowICCProfile = WIN_GetWindowICCProfile;
244 device->SetWindowMouseRect = WIN_SetWindowMouseRect;
245 device->SetWindowMouseGrab = WIN_SetWindowMouseGrab;
246 device->SetWindowKeyboardGrab = WIN_SetWindowKeyboardGrab;
247#endif
248 device->DestroyWindow = WIN_DestroyWindow;
249#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
250 device->CreateWindowFramebuffer = WIN_CreateWindowFramebuffer;
251 device->UpdateWindowFramebuffer = WIN_UpdateWindowFramebuffer;
252 device->DestroyWindowFramebuffer = WIN_DestroyWindowFramebuffer;
253 device->OnWindowEnter = WIN_OnWindowEnter;
254 device->SetWindowHitTest = WIN_SetWindowHitTest;
255 device->AcceptDragAndDrop = WIN_AcceptDragAndDrop;
256 device->FlashWindow = WIN_FlashWindow;
257 device->ShowWindowSystemMenu = WIN_ShowWindowSystemMenu;
258 device->SetWindowFocusable = WIN_SetWindowFocusable;
259 device->UpdateWindowShape = WIN_UpdateWindowShape;
260#endif
261
262#ifdef SDL_VIDEO_OPENGL_WGL
263 device->GL_LoadLibrary = WIN_GL_LoadLibrary;
264 device->GL_GetProcAddress = WIN_GL_GetProcAddress;
265 device->GL_UnloadLibrary = WIN_GL_UnloadLibrary;
266 device->GL_CreateContext = WIN_GL_CreateContext;
267 device->GL_MakeCurrent = WIN_GL_MakeCurrent;
268 device->GL_SetSwapInterval = WIN_GL_SetSwapInterval;
269 device->GL_GetSwapInterval = WIN_GL_GetSwapInterval;
270 device->GL_SwapWindow = WIN_GL_SwapWindow;
271 device->GL_DestroyContext = WIN_GL_DestroyContext;
272 device->GL_GetEGLSurface = NULL;
273#endif
274#ifdef SDL_VIDEO_OPENGL_EGL
275#ifdef SDL_VIDEO_OPENGL_WGL
276 if (SDL_GetHintBoolean(SDL_HINT_VIDEO_FORCE_EGL, false)) {
277#endif
278 // Use EGL based functions
279 device->GL_LoadLibrary = WIN_GLES_LoadLibrary;
280 device->GL_GetProcAddress = WIN_GLES_GetProcAddress;
281 device->GL_UnloadLibrary = WIN_GLES_UnloadLibrary;
282 device->GL_CreateContext = WIN_GLES_CreateContext;
283 device->GL_MakeCurrent = WIN_GLES_MakeCurrent;
284 device->GL_SetSwapInterval = WIN_GLES_SetSwapInterval;
285 device->GL_GetSwapInterval = WIN_GLES_GetSwapInterval;
286 device->GL_SwapWindow = WIN_GLES_SwapWindow;
287 device->GL_DestroyContext = WIN_GLES_DestroyContext;
288 device->GL_GetEGLSurface = WIN_GLES_GetEGLSurface;
289#ifdef SDL_VIDEO_OPENGL_WGL
290 }
291#endif
292#endif
293#ifdef SDL_VIDEO_VULKAN
294 device->Vulkan_LoadLibrary = WIN_Vulkan_LoadLibrary;
295 device->Vulkan_UnloadLibrary = WIN_Vulkan_UnloadLibrary;
296 device->Vulkan_GetInstanceExtensions = WIN_Vulkan_GetInstanceExtensions;
297 device->Vulkan_CreateSurface = WIN_Vulkan_CreateSurface;
298 device->Vulkan_DestroySurface = WIN_Vulkan_DestroySurface;
299 device->Vulkan_GetPresentationSupport = WIN_Vulkan_GetPresentationSupport;
300#endif
301
302#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
303 device->StartTextInput = WIN_StartTextInput;
304 device->StopTextInput = WIN_StopTextInput;
305 device->UpdateTextInputArea = WIN_UpdateTextInputArea;
306 device->ClearComposition = WIN_ClearComposition;
307
308 device->SetClipboardData = WIN_SetClipboardData;
309 device->GetClipboardData = WIN_GetClipboardData;
310 device->HasClipboardData = WIN_HasClipboardData;
311#endif
312
313#ifdef SDL_GDK_TEXTINPUT
314 GDK_EnsureHints();
315
316 device->StartTextInput = GDK_StartTextInput;
317 device->StopTextInput = GDK_StopTextInput;
318 device->UpdateTextInputArea = GDK_UpdateTextInputArea;
319 device->ClearComposition = GDK_ClearComposition;
320
321 device->HasScreenKeyboardSupport = GDK_HasScreenKeyboardSupport;
322 device->ShowScreenKeyboard = GDK_ShowScreenKeyboard;
323 device->HideScreenKeyboard = GDK_HideScreenKeyboard;
324 device->IsScreenKeyboardShown = GDK_IsScreenKeyboardShown;
325#endif
326
327 device->free = WIN_DeleteDevice;
328
329 device->device_caps = VIDEO_DEVICE_CAPS_HAS_POPUP_WINDOW_SUPPORT |
330 VIDEO_DEVICE_CAPS_SENDS_FULLSCREEN_DIMENSIONS;
331
332 return device;
333}
334
335VideoBootStrap WINDOWS_bootstrap = {
336 "windows", "SDL Windows video driver", WIN_CreateDevice,
337 #if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
338 WIN_ShowMessageBox,
339 #else
340 NULL,
341 #endif
342 false
343};
344
345static BOOL WIN_DeclareDPIAwareUnaware(SDL_VideoDevice *_this)
346{
347#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
348 SDL_VideoData *data = _this->internal;
349
350 if (data->SetProcessDpiAwarenessContext) {
351 return data->SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_UNAWARE);
352 } else if (data->SetProcessDpiAwareness) {
353 // Windows 8.1
354 return SUCCEEDED(data->SetProcessDpiAwareness(PROCESS_DPI_UNAWARE));
355 }
356#endif
357 return FALSE;
358}
359
360static BOOL WIN_DeclareDPIAwareSystem(SDL_VideoDevice *_this)
361{
362#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
363 SDL_VideoData *data = _this->internal;
364
365 if (data->SetProcessDpiAwarenessContext) {
366 // Windows 10, version 1607
367 return data->SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_SYSTEM_AWARE);
368 } else if (data->SetProcessDpiAwareness) {
369 // Windows 8.1
370 return SUCCEEDED(data->SetProcessDpiAwareness(PROCESS_SYSTEM_DPI_AWARE));
371 } else if (data->SetProcessDPIAware) {
372 // Windows Vista
373 return data->SetProcessDPIAware();
374 }
375#endif
376 return FALSE;
377}
378
379static BOOL WIN_DeclareDPIAwarePerMonitor(SDL_VideoDevice *_this)
380{
381#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
382 SDL_VideoData *data = _this->internal;
383
384 if (data->SetProcessDpiAwarenessContext) {
385 // Windows 10, version 1607
386 return data->SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE);
387 } else if (data->SetProcessDpiAwareness) {
388 // Windows 8.1
389 return SUCCEEDED(data->SetProcessDpiAwareness(PROCESS_PER_MONITOR_DPI_AWARE));
390 } else {
391 // Older OS: fall back to system DPI aware
392 return WIN_DeclareDPIAwareSystem(_this);
393 }
394#else
395 return FALSE;
396#endif
397}
398
399static BOOL WIN_DeclareDPIAwarePerMonitorV2(SDL_VideoDevice *_this)
400{
401#if defined(SDL_PLATFORM_XBOXONE) || defined(SDL_PLATFORM_XBOXSERIES)
402 return FALSE;
403#else
404 SDL_VideoData *data = _this->internal;
405
406 // Declare DPI aware (may have been done in external code or a manifest, as well)
407 if (data->SetProcessDpiAwarenessContext) {
408 // Windows 10, version 1607
409
410 /* NOTE: SetThreadDpiAwarenessContext doesn't work here with OpenGL - the OpenGL contents
411 end up still getting OS scaled. (tested on Windows 10 21H1 19043.1348, NVIDIA 496.49)
412
413 NOTE: Enabling DPI awareness through Windows Explorer
414 (right click .exe -> Properties -> Compatibility -> High DPI Settings ->
415 check "Override high DPI Scaling behaviour", select Application) gives
416 a DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE context (at least on Windows 10 21H1), and
417 setting DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 will fail.
418
419 NOTE: Entering exclusive fullscreen in a DPI_AWARENESS_CONTEXT_UNAWARE process
420 appears to cause Windows to change the .exe manifest to DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE
421 on future launches. This means attempting to use DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2
422 will fail in the future until you manually clear the "Override high DPI Scaling behaviour"
423 setting in Windows Explorer (tested on Windows 10 21H2).
424 */
425 if (data->SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2)) {
426 return TRUE;
427 } else {
428 return WIN_DeclareDPIAwarePerMonitor(_this);
429 }
430 } else {
431 // Older OS: fall back to per-monitor (or system)
432 return WIN_DeclareDPIAwarePerMonitor(_this);
433 }
434#endif
435}
436
437#ifdef HIGHDPI_DEBUG
438static const char *WIN_GetDPIAwareness(SDL_VideoDevice *_this)
439{
440 SDL_VideoData *data = _this->internal;
441
442 if (data->GetThreadDpiAwarenessContext && data->AreDpiAwarenessContextsEqual) {
443 DPI_AWARENESS_CONTEXT context = data->GetThreadDpiAwarenessContext();
444
445 if (data->AreDpiAwarenessContextsEqual(context, DPI_AWARENESS_CONTEXT_UNAWARE)) {
446 return "unaware";
447 } else if (data->AreDpiAwarenessContextsEqual(context, DPI_AWARENESS_CONTEXT_SYSTEM_AWARE)) {
448 return "system";
449 } else if (data->AreDpiAwarenessContextsEqual(context, DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE)) {
450 return "permonitor";
451 } else if (data->AreDpiAwarenessContextsEqual(context, DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2)) {
452 return "permonitorv2";
453 } else if (data->AreDpiAwarenessContextsEqual(context, DPI_AWARENESS_CONTEXT_UNAWARE_GDISCALED)) {
454 return "unaware_gdiscaled";
455 }
456 }
457
458 return "";
459}
460#endif
461
462static void WIN_InitDPIAwareness(SDL_VideoDevice *_this)
463{
464 const char *hint = SDL_GetHint("SDL_WINDOWS_DPI_AWARENESS");
465
466 if (!hint || SDL_strcmp(hint, "permonitorv2") == 0) {
467 WIN_DeclareDPIAwarePerMonitorV2(_this);
468 } else if (SDL_strcmp(hint, "permonitor") == 0) {
469 WIN_DeclareDPIAwarePerMonitor(_this);
470 } else if (SDL_strcmp(hint, "system") == 0) {
471 WIN_DeclareDPIAwareSystem(_this);
472 } else if (SDL_strcmp(hint, "unaware") == 0) {
473 WIN_DeclareDPIAwareUnaware(_this);
474 }
475}
476
477static bool WIN_VideoInit(SDL_VideoDevice *_this)
478{
479 SDL_VideoData *data = _this->internal;
480 HRESULT hr;
481
482 hr = WIN_CoInitialize();
483 if (SUCCEEDED(hr)) {
484 data->coinitialized = true;
485
486#if !(defined(SDL_PLATFORM_XBOXONE) || defined(SDL_PLATFORM_XBOXSERIES))
487 hr = OleInitialize(NULL);
488 if (SUCCEEDED(hr)) {
489 data->oleinitialized = true;
490 } else {
491 SDL_LogInfo(SDL_LOG_CATEGORY_VIDEO, "OleInitialize() failed: 0x%.8x, using fallback drag-n-drop functionality", (unsigned int)hr);
492 }
493#endif // !(defined(SDL_PLATFORM_XBOXONE) || defined(SDL_PLATFORM_XBOXSERIES))
494 } else {
495 SDL_LogInfo(SDL_LOG_CATEGORY_VIDEO, "CoInitialize() failed: 0x%.8x, using fallback drag-n-drop functionality", (unsigned int)hr);
496 }
497
498 WIN_InitDPIAwareness(_this);
499
500#ifdef HIGHDPI_DEBUG
501 SDL_Log("DPI awareness: %s", WIN_GetDPIAwareness(_this));
502#endif
503
504 if (SDL_GetHintBoolean(SDL_HINT_WINDOWS_GAMEINPUT, true)) {
505 WIN_InitGameInput(_this);
506 }
507
508#if defined(SDL_PLATFORM_XBOXONE) || defined(SDL_PLATFORM_XBOXSERIES)
509 // For Xbox, we just need to create the single display
510 {
511 SDL_DisplayMode mode;
512
513 SDL_zero(mode);
514 D3D12_XBOX_GetResolution(&mode.w, &mode.h);
515 mode.refresh_rate = 60.0f;
516 mode.format = SDL_PIXELFORMAT_ARGB8888;
517
518 SDL_AddBasicVideoDisplay(&mode);
519 }
520#else // !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
521 if (!WIN_InitModes(_this)) {
522 return false;
523 }
524
525 WIN_InitKeyboard(_this);
526 WIN_InitMouse(_this);
527 WIN_InitDeviceNotification();
528 if (!_this->internal->gameinput_context) {
529 WIN_CheckKeyboardAndMouseHotplug(_this, true);
530 }
531#endif
532
533 SDL_AddHintCallback(SDL_HINT_WINDOWS_RAW_KEYBOARD, UpdateWindowsRawKeyboard, _this);
534 SDL_AddHintCallback(SDL_HINT_WINDOWS_ENABLE_MESSAGELOOP, UpdateWindowsEnableMessageLoop, NULL);
535 SDL_AddHintCallback(SDL_HINT_WINDOWS_ENABLE_MENU_MNEMONICS, UpdateWindowsEnableMenuMnemonics, NULL);
536 SDL_AddHintCallback(SDL_HINT_WINDOW_FRAME_USABLE_WHILE_CURSOR_HIDDEN, UpdateWindowFrameUsableWhileCursorHidden, NULL);
537
538#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
539 data->_SDL_WAKEUP = RegisterWindowMessageA("_SDL_WAKEUP");
540#endif
541
542 return true;
543}
544
545void WIN_VideoQuit(SDL_VideoDevice *_this)
546{
547 SDL_VideoData *data = _this->internal;
548
549 SDL_RemoveHintCallback(SDL_HINT_WINDOWS_RAW_KEYBOARD, UpdateWindowsRawKeyboard, _this);
550 SDL_RemoveHintCallback(SDL_HINT_WINDOWS_ENABLE_MESSAGELOOP, UpdateWindowsEnableMessageLoop, NULL);
551 SDL_RemoveHintCallback(SDL_HINT_WINDOWS_ENABLE_MENU_MNEMONICS, UpdateWindowsEnableMenuMnemonics, NULL);
552 SDL_RemoveHintCallback(SDL_HINT_WINDOW_FRAME_USABLE_WHILE_CURSOR_HIDDEN, UpdateWindowFrameUsableWhileCursorHidden, NULL);
553
554 WIN_SetRawMouseEnabled(_this, false);
555 WIN_SetRawKeyboardEnabled(_this, false);
556 WIN_QuitGameInput(_this);
557
558#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
559 WIN_QuitModes(_this);
560 WIN_QuitDeviceNotification();
561 WIN_QuitKeyboard(_this);
562 WIN_QuitMouse(_this);
563
564 if (data->oleinitialized) {
565 OleUninitialize();
566 data->oleinitialized = false;
567 }
568#endif // !(defined(SDL_PLATFORM_XBOXONE) || defined(SDL_PLATFORM_XBOXSERIES))
569
570 if (data->coinitialized) {
571 WIN_CoUninitialize();
572 data->coinitialized = false;
573 }
574}
575
576#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
577#define D3D_DEBUG_INFO
578#include <d3d9.h>
579
580#ifdef D3D_DEBUG_INFO
581#ifndef D3D_SDK_VERSION
582#define D3D_SDK_VERSION (32 | 0x80000000)
583#endif
584#ifndef D3D9b_SDK_VERSION
585#define D3D9b_SDK_VERSION (31 | 0x80000000)
586#endif
587#else //
588#ifndef D3D_SDK_VERSION
589#define D3D_SDK_VERSION 32
590#endif
591#ifndef D3D9b_SDK_VERSION
592#define D3D9b_SDK_VERSION 31
593#endif
594#endif
595
596bool D3D_LoadDLL(void **pD3DDLL, IDirect3D9 **pDirect3D9Interface)
597{
598 *pD3DDLL = SDL_LoadObject("D3D9.DLL");
599 if (*pD3DDLL) {
600 /* *INDENT-OFF* */ // clang-format off
601 typedef IDirect3D9 *(WINAPI *Direct3DCreate9_t)(UINT SDKVersion);
602 typedef HRESULT (WINAPI* Direct3DCreate9Ex_t)(UINT SDKVersion, IDirect3D9Ex** ppD3D);
603 /* *INDENT-ON* */ // clang-format on
604 Direct3DCreate9_t Direct3DCreate9Func;
605
606 if (SDL_GetHintBoolean(SDL_HINT_WINDOWS_USE_D3D9EX, false)) {
607 Direct3DCreate9Ex_t Direct3DCreate9ExFunc;
608
609 Direct3DCreate9ExFunc = (Direct3DCreate9Ex_t)SDL_LoadFunction(*pD3DDLL, "Direct3DCreate9Ex");
610 if (Direct3DCreate9ExFunc) {
611 IDirect3D9Ex *pDirect3D9ExInterface;
612 HRESULT hr = Direct3DCreate9ExFunc(D3D_SDK_VERSION, &pDirect3D9ExInterface);
613 if (SUCCEEDED(hr)) {
614 const GUID IDirect3D9_GUID = { 0x81bdcbca, 0x64d4, 0x426d, { 0xae, 0x8d, 0xad, 0x1, 0x47, 0xf4, 0x27, 0x5c } };
615 hr = IDirect3D9Ex_QueryInterface(pDirect3D9ExInterface, &IDirect3D9_GUID, (void **)pDirect3D9Interface);
616 IDirect3D9Ex_Release(pDirect3D9ExInterface);
617 if (SUCCEEDED(hr)) {
618 return true;
619 }
620 }
621 }
622 }
623
624 Direct3DCreate9Func = (Direct3DCreate9_t)SDL_LoadFunction(*pD3DDLL, "Direct3DCreate9");
625 if (Direct3DCreate9Func) {
626 *pDirect3D9Interface = Direct3DCreate9Func(D3D_SDK_VERSION);
627 if (*pDirect3D9Interface) {
628 return true;
629 }
630 }
631
632 SDL_UnloadObject(*pD3DDLL);
633 *pD3DDLL = NULL;
634 }
635 *pDirect3D9Interface = NULL;
636 return false;
637}
638
639int SDL_GetDirect3D9AdapterIndex(SDL_DisplayID displayID)
640{
641 void *pD3DDLL;
642 IDirect3D9 *pD3D;
643 if (!D3D_LoadDLL(&pD3DDLL, &pD3D)) {
644 SDL_SetError("Unable to create Direct3D interface");
645 return -1;
646 } else {
647 SDL_DisplayData *pData = SDL_GetDisplayDriverData(displayID);
648 int adapterIndex = D3DADAPTER_DEFAULT;
649
650 if (!pData) {
651 SDL_SetError("Invalid display index");
652 adapterIndex = -1; // make sure we return something invalid
653 } else {
654 char *displayName = WIN_StringToUTF8W(pData->DeviceName);
655 unsigned int count = IDirect3D9_GetAdapterCount(pD3D);
656 unsigned int i;
657 for (i = 0; i < count; i++) {
658 D3DADAPTER_IDENTIFIER9 id;
659 IDirect3D9_GetAdapterIdentifier(pD3D, i, 0, &id);
660
661 if (SDL_strcmp(id.DeviceName, displayName) == 0) {
662 adapterIndex = i;
663 break;
664 }
665 }
666 SDL_free(displayName);
667 }
668
669 // free up the D3D stuff we inited
670 IDirect3D9_Release(pD3D);
671 SDL_UnloadObject(pD3DDLL);
672
673 return adapterIndex;
674 }
675}
676#endif // !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
677
678bool SDL_GetDXGIOutputInfo(SDL_DisplayID displayID, int *adapterIndex, int *outputIndex)
679{
680#ifndef HAVE_DXGI_H
681 if (adapterIndex) {
682 *adapterIndex = -1;
683 }
684 if (outputIndex) {
685 *outputIndex = -1;
686 }
687 return SDL_SetError("SDL was compiled without DXGI support due to missing dxgi.h header");
688#else
689 const SDL_VideoDevice *videodevice = SDL_GetVideoDevice();
690 const SDL_VideoData *videodata = videodevice ? videodevice->internal : NULL;
691 SDL_DisplayData *pData = SDL_GetDisplayDriverData(displayID);
692 int nAdapter, nOutput;
693 IDXGIAdapter *pDXGIAdapter;
694 IDXGIOutput *pDXGIOutput;
695
696 if (!adapterIndex) {
697 return SDL_InvalidParamError("adapterIndex");
698 }
699
700 if (!outputIndex) {
701 return SDL_InvalidParamError("outputIndex");
702 }
703
704 *adapterIndex = -1;
705 *outputIndex = -1;
706
707 if (!pData) {
708 return SDL_SetError("Invalid display index");
709 }
710
711 if (!videodata || !videodata->pDXGIFactory) {
712 return SDL_SetError("Unable to create DXGI interface");
713 }
714
715 nAdapter = 0;
716 while (*adapterIndex == -1 && SUCCEEDED(IDXGIFactory_EnumAdapters(videodata->pDXGIFactory, nAdapter, &pDXGIAdapter))) {
717 nOutput = 0;
718 while (*adapterIndex == -1 && SUCCEEDED(IDXGIAdapter_EnumOutputs(pDXGIAdapter, nOutput, &pDXGIOutput))) {
719 DXGI_OUTPUT_DESC outputDesc;
720 if (SUCCEEDED(IDXGIOutput_GetDesc(pDXGIOutput, &outputDesc))) {
721 if (SDL_wcscmp(outputDesc.DeviceName, pData->DeviceName) == 0) {
722 *adapterIndex = nAdapter;
723 *outputIndex = nOutput;
724 }
725 }
726 IDXGIOutput_Release(pDXGIOutput);
727 nOutput++;
728 }
729 IDXGIAdapter_Release(pDXGIAdapter);
730 nAdapter++;
731 }
732
733 if (*adapterIndex == -1) {
734 return SDL_SetError("Couldn't find matching adapter");
735 }
736 return true;
737#endif
738}
739
740SDL_SystemTheme WIN_GetSystemTheme(void)
741{
742 SDL_SystemTheme theme = SDL_SYSTEM_THEME_LIGHT;
743 HKEY hKey;
744 DWORD dwType = REG_DWORD;
745 DWORD value = ~0U;
746 DWORD length = sizeof(value);
747
748 // Technically this isn't the system theme, but it's the preference for applications
749 if (RegOpenKeyExW(HKEY_CURRENT_USER, L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize", 0, KEY_READ, &hKey) == ERROR_SUCCESS) {
750 if (RegQueryValueExW(hKey, L"AppsUseLightTheme", 0, &dwType, (LPBYTE)&value, &length) == ERROR_SUCCESS) {
751 if (value == 0) {
752 theme = SDL_SYSTEM_THEME_DARK;
753 }
754 }
755 RegCloseKey(hKey);
756 }
757 return theme;
758}
759
760bool WIN_IsPerMonitorV2DPIAware(SDL_VideoDevice *_this)
761{
762#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
763 SDL_VideoData *data = _this->internal;
764
765 if (data->AreDpiAwarenessContextsEqual && data->GetThreadDpiAwarenessContext) {
766 // Windows 10, version 1607
767 return data->AreDpiAwarenessContextsEqual(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2, data->GetThreadDpiAwarenessContext());
768 }
769#endif
770 return false;
771}
772
773#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 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#ifndef SDL_windowsvideo_h_
24#define SDL_windowsvideo_h_
25
26#include "../../core/windows/SDL_windows.h"
27
28#include "../SDL_sysvideo.h"
29
30#ifdef HAVE_DXGI_H
31#define CINTERFACE
32#define COBJMACROS
33#include <dxgi.h>
34#endif
35
36#if defined(_MSC_VER) && (_MSC_VER >= 1500) && !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
37#include <msctf.h>
38#else
39#include "SDL_msctf.h"
40#endif
41
42#include <imm.h>
43
44#define MAX_CANDLIST 10
45#define MAX_CANDLENGTH 256
46#define MAX_CANDSIZE (sizeof(WCHAR) * MAX_CANDLIST * MAX_CANDLENGTH)
47
48#include "SDL_windowsclipboard.h"
49#include "SDL_windowsevents.h"
50#include "SDL_windowsgameinput.h"
51#include "SDL_windowsopengl.h"
52
53#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
54#include "SDL_windowsshape.h"
55#include "SDL_windowskeyboard.h"
56#include "SDL_windowsmodes.h"
57#include "SDL_windowsmouse.h"
58#include "SDL_windowsopengles.h"
59#endif
60
61#include "SDL_windowswindow.h"
62
63#ifndef USER_DEFAULT_SCREEN_DPI
64#define USER_DEFAULT_SCREEN_DPI 96
65#endif
66
67#if WINVER < 0x0601
68// Touch input definitions
69#define TWF_FINETOUCH 1
70#define TWF_WANTPALM 2
71
72#define TOUCHEVENTF_MOVE 0x0001
73#define TOUCHEVENTF_DOWN 0x0002
74#define TOUCHEVENTF_UP 0x0004
75
76DECLARE_HANDLE(HTOUCHINPUT);
77
78typedef struct _TOUCHINPUT
79{
80 LONG x;
81 LONG y;
82 HANDLE hSource;
83 DWORD dwID;
84 DWORD dwFlags;
85 DWORD dwMask;
86 DWORD dwTime;
87 ULONG_PTR dwExtraInfo;
88 DWORD cxContact;
89 DWORD cyContact;
90} TOUCHINPUT, *PTOUCHINPUT;
91
92// More-robust display information in Vista...
93// This is a huge amount of data to be stuffing into three API calls. :(
94typedef struct DISPLAYCONFIG_PATH_SOURCE_INFO
95{
96 LUID adapterId;
97 UINT32 id;
98 union
99 {
100 UINT32 modeInfoIdx;
101 struct
102 {
103 UINT32 cloneGroupId : 16;
104 UINT32 sourceModeInfoIdx : 16;
105 } DUMMYSTRUCTNAME;
106 } DUMMYUNIONNAME;
107
108 UINT32 statusFlags;
109} DISPLAYCONFIG_PATH_SOURCE_INFO;
110
111typedef struct DISPLAYCONFIG_RATIONAL
112{
113 UINT32 Numerator;
114 UINT32 Denominator;
115} DISPLAYCONFIG_RATIONAL;
116
117typedef struct DISPLAYCONFIG_PATH_TARGET_INFO
118{
119 LUID adapterId;
120 UINT32 id;
121 union
122 {
123 UINT32 modeInfoIdx;
124 struct
125 {
126 UINT32 desktopModeInfoIdx : 16;
127 UINT32 targetModeInfoIdx : 16;
128 } DUMMYSTRUCTNAME;
129 } DUMMYUNIONNAME;
130 UINT32 /*DISPLAYCONFIG_VIDEO_OUTPUT_TECHNOLOGY*/ outputTechnology;
131 UINT32 /*DISPLAYCONFIG_ROTATION*/ rotation;
132 UINT32 /*DISPLAYCONFIG_SCALING*/ scaling;
133 DISPLAYCONFIG_RATIONAL refreshRate;
134 UINT32 /*DISPLAYCONFIG_SCANLINE_ORDERING*/ scanLineOrdering;
135 BOOL targetAvailable;
136 UINT32 statusFlags;
137} DISPLAYCONFIG_PATH_TARGET_INFO;
138
139typedef struct DISPLAYCONFIG_PATH_INFO
140{
141 DISPLAYCONFIG_PATH_SOURCE_INFO sourceInfo;
142 DISPLAYCONFIG_PATH_TARGET_INFO targetInfo;
143 UINT32 flags;
144} DISPLAYCONFIG_PATH_INFO;
145
146typedef enum
147{
148 DISPLAYCONFIG_MODE_INFO_TYPE_SOURCE = 1,
149 DISPLAYCONFIG_MODE_INFO_TYPE_TARGET = 2,
150 DISPLAYCONFIG_MODE_INFO_TYPE_DESKTOP_IMAGE = 3,
151 DISPLAYCONFIG_MODE_INFO_TYPE_FORCE_UINT32 = 0xFFFFFFFF
152} DISPLAYCONFIG_MODE_INFO_TYPE;
153
154typedef struct DISPLAYCONFIG_2DREGION
155{
156 UINT32 cx;
157 UINT32 cy;
158} DISPLAYCONFIG_2DREGION;
159
160typedef struct DISPLAYCONFIG_VIDEO_SIGNAL_INFO
161{
162 UINT64 pixelRate;
163 DISPLAYCONFIG_RATIONAL hSyncFreq;
164 DISPLAYCONFIG_RATIONAL vSyncFreq;
165 DISPLAYCONFIG_2DREGION activeSize;
166 DISPLAYCONFIG_2DREGION totalSize;
167
168 union
169 {
170 struct
171 {
172 UINT32 videoStandard : 16;
173
174 // Vertical refresh frequency divider
175 UINT32 vSyncFreqDivider : 6;
176
177 UINT32 reserved : 10;
178 } AdditionalSignalInfo;
179
180 UINT32 videoStandard;
181 } DUMMYUNIONNAME;
182
183 // Scan line ordering (e.g. progressive, interlaced).
184 UINT32 /*DISPLAYCONFIG_SCANLINE_ORDERING*/ scanLineOrdering;
185} DISPLAYCONFIG_VIDEO_SIGNAL_INFO;
186
187typedef struct DISPLAYCONFIG_SOURCE_MODE
188{
189 UINT32 width;
190 UINT32 height;
191 UINT32 /*DISPLAYCONFIG_PIXELFORMAT*/ pixelFormat;
192 POINTL position;
193} DISPLAYCONFIG_SOURCE_MODE;
194
195typedef struct DISPLAYCONFIG_TARGET_MODE
196{
197 DISPLAYCONFIG_VIDEO_SIGNAL_INFO targetVideoSignalInfo;
198} DISPLAYCONFIG_TARGET_MODE;
199
200typedef struct DISPLAYCONFIG_DESKTOP_IMAGE_INFO
201{
202 POINTL PathSourceSize;
203 RECTL DesktopImageRegion;
204 RECTL DesktopImageClip;
205} DISPLAYCONFIG_DESKTOP_IMAGE_INFO;
206
207typedef struct DISPLAYCONFIG_MODE_INFO
208{
209 DISPLAYCONFIG_MODE_INFO_TYPE infoType;
210 UINT32 id;
211 LUID adapterId;
212 union
213 {
214 DISPLAYCONFIG_TARGET_MODE targetMode;
215 DISPLAYCONFIG_SOURCE_MODE sourceMode;
216 DISPLAYCONFIG_DESKTOP_IMAGE_INFO desktopImageInfo;
217 } DUMMYUNIONNAME;
218} DISPLAYCONFIG_MODE_INFO;
219
220typedef enum DISPLAYCONFIG_TOPOLOGY_ID
221{
222 DISPLAYCONFIG_TOPOLOGY_INTERNAL = 0x00000001,
223 DISPLAYCONFIG_TOPOLOGY_CLONE = 0x00000002,
224 DISPLAYCONFIG_TOPOLOGY_EXTEND = 0x00000004,
225 DISPLAYCONFIG_TOPOLOGY_EXTERNAL = 0x00000008,
226 DISPLAYCONFIG_TOPOLOGY_FORCE_UINT32 = 0xFFFFFFFF
227} DISPLAYCONFIG_TOPOLOGY_ID;
228
229typedef enum
230{
231 DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_NAME = 1,
232 DISPLAYCONFIG_DEVICE_INFO_GET_TARGET_NAME = 2,
233 DISPLAYCONFIG_DEVICE_INFO_GET_TARGET_PREFERRED_MODE = 3,
234 DISPLAYCONFIG_DEVICE_INFO_GET_ADAPTER_NAME = 4,
235 DISPLAYCONFIG_DEVICE_INFO_SET_TARGET_PERSISTENCE = 5,
236 DISPLAYCONFIG_DEVICE_INFO_GET_TARGET_BASE_TYPE = 6,
237 DISPLAYCONFIG_DEVICE_INFO_GET_SUPPORT_VIRTUAL_RESOLUTION = 7,
238 DISPLAYCONFIG_DEVICE_INFO_SET_SUPPORT_VIRTUAL_RESOLUTION = 8,
239 DISPLAYCONFIG_DEVICE_INFO_GET_ADVANCED_COLOR_INFO = 9,
240 DISPLAYCONFIG_DEVICE_INFO_SET_ADVANCED_COLOR_STATE = 10,
241 DISPLAYCONFIG_DEVICE_INFO_GET_SDR_WHITE_LEVEL = 11,
242 DISPLAYCONFIG_DEVICE_INFO_FORCE_UINT32 = 0xFFFFFFFF
243} DISPLAYCONFIG_DEVICE_INFO_TYPE;
244
245typedef struct DISPLAYCONFIG_DEVICE_INFO_HEADER
246{
247 DISPLAYCONFIG_DEVICE_INFO_TYPE type;
248 UINT32 size;
249 LUID adapterId;
250 UINT32 id;
251} DISPLAYCONFIG_DEVICE_INFO_HEADER;
252
253typedef struct DISPLAYCONFIG_SOURCE_DEVICE_NAME
254{
255 DISPLAYCONFIG_DEVICE_INFO_HEADER header;
256 WCHAR viewGdiDeviceName[CCHDEVICENAME];
257} DISPLAYCONFIG_SOURCE_DEVICE_NAME;
258
259typedef struct DISPLAYCONFIG_TARGET_DEVICE_NAME_FLAGS
260{
261 union
262 {
263 struct
264 {
265 UINT32 friendlyNameFromEdid : 1;
266 UINT32 friendlyNameForced : 1;
267 UINT32 edidIdsValid : 1;
268 UINT32 reserved : 29;
269 } DUMMYSTRUCTNAME;
270 UINT32 value;
271 } DUMMYUNIONNAME;
272} DISPLAYCONFIG_TARGET_DEVICE_NAME_FLAGS;
273
274typedef struct DISPLAYCONFIG_TARGET_DEVICE_NAME
275{
276 DISPLAYCONFIG_DEVICE_INFO_HEADER header;
277 DISPLAYCONFIG_TARGET_DEVICE_NAME_FLAGS flags;
278 UINT32 /*DISPLAYCONFIG_VIDEO_OUTPUT_TECHNOLOGY*/ outputTechnology;
279 UINT16 edidManufactureId;
280 UINT16 edidProductCodeId;
281 UINT32 connectorInstance;
282 WCHAR monitorFriendlyDeviceName[64];
283 WCHAR monitorDevicePath[128];
284} DISPLAYCONFIG_TARGET_DEVICE_NAME;
285
286#define QDC_ONLY_ACTIVE_PATHS 0x00000002
287
288#endif // WINVER < 0x0601
289
290#ifndef HAVE_SHELLSCALINGAPI_H
291
292typedef enum MONITOR_DPI_TYPE
293{
294 MDT_EFFECTIVE_DPI = 0,
295 MDT_ANGULAR_DPI = 1,
296 MDT_RAW_DPI = 2,
297 MDT_DEFAULT = MDT_EFFECTIVE_DPI
298} MONITOR_DPI_TYPE;
299
300typedef enum PROCESS_DPI_AWARENESS
301{
302 PROCESS_DPI_UNAWARE = 0,
303 PROCESS_SYSTEM_DPI_AWARE = 1,
304 PROCESS_PER_MONITOR_DPI_AWARE = 2
305} PROCESS_DPI_AWARENESS;
306
307#else
308#include <shellscalingapi.h>
309#endif
310
311#ifndef _DPI_AWARENESS_CONTEXTS_
312
313typedef enum DPI_AWARENESS
314{
315 DPI_AWARENESS_INVALID = -1,
316 DPI_AWARENESS_UNAWARE = 0,
317 DPI_AWARENESS_SYSTEM_AWARE = 1,
318 DPI_AWARENESS_PER_MONITOR_AWARE = 2
319} DPI_AWARENESS;
320
321DECLARE_HANDLE(DPI_AWARENESS_CONTEXT);
322
323#define DPI_AWARENESS_CONTEXT_UNAWARE ((DPI_AWARENESS_CONTEXT)-1)
324#define DPI_AWARENESS_CONTEXT_SYSTEM_AWARE ((DPI_AWARENESS_CONTEXT)-2)
325#define DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE ((DPI_AWARENESS_CONTEXT)-3)
326
327#endif // _DPI_AWARENESS_CONTEXTS_
328
329// Windows 10 Creators Update
330#if NTDDI_VERSION < 0x0A000003
331#define DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 ((DPI_AWARENESS_CONTEXT)-4)
332#endif // NTDDI_VERSION < 0x0A000003
333
334// Windows 10 version 1809
335#if NTDDI_VERSION < 0x0A000006
336#define DPI_AWARENESS_CONTEXT_UNAWARE_GDISCALED ((DPI_AWARENESS_CONTEXT)-5)
337#endif // NTDDI_VERSION < 0x0A000006
338
339typedef BOOL (*PFNSHFullScreen)(HWND, DWORD);
340typedef void (*PFCoordTransform)(SDL_Window *, POINT *);
341
342typedef struct
343{
344 void **lpVtbl;
345 int refcount;
346 void *data;
347} TSFSink;
348
349#ifndef SDL_DISABLE_WINDOWS_IME
350// Definition from Win98DDK version of IMM.H
351typedef struct tagINPUTCONTEXT2
352{
353 HWND hWnd;
354 BOOL fOpen;
355 POINT ptStatusWndPos;
356 POINT ptSoftKbdPos;
357 DWORD fdwConversion;
358 DWORD fdwSentence;
359 union
360 {
361 LOGFONTA A;
362 LOGFONTW W;
363 } lfFont;
364 COMPOSITIONFORM cfCompForm;
365 CANDIDATEFORM cfCandForm[4];
366 HIMCC hCompStr;
367 HIMCC hCandInfo;
368 HIMCC hGuideLine;
369 HIMCC hPrivate;
370 DWORD dwNumMsgBuf;
371 HIMCC hMsgBuf;
372 DWORD fdwInit;
373 DWORD dwReserve[3];
374} INPUTCONTEXT2, *PINPUTCONTEXT2, NEAR *NPINPUTCONTEXT2, FAR *LPINPUTCONTEXT2;
375#endif
376
377// Private display data
378
379struct SDL_VideoData
380{
381 int render;
382
383 bool coinitialized;
384#if !(defined(SDL_PLATFORM_XBOXONE) || defined(SDL_PLATFORM_XBOXSERIES))
385 bool oleinitialized;
386#endif // !(defined(SDL_PLATFORM_XBOXONE) || defined(SDL_PLATFORM_XBOXSERIES))
387
388 DWORD clipboard_count;
389
390#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) // Xbox doesn't support user32/shcore
391 // Touch input functions
392 SDL_SharedObject *userDLL;
393 /* *INDENT-OFF* */ // clang-format off
394 BOOL (WINAPI *CloseTouchInputHandle)( HTOUCHINPUT );
395 BOOL (WINAPI *GetTouchInputInfo)( HTOUCHINPUT, UINT, PTOUCHINPUT, int );
396 BOOL (WINAPI *RegisterTouchWindow)( HWND, ULONG );
397 BOOL (WINAPI *SetProcessDPIAware)( void );
398 BOOL (WINAPI *SetProcessDpiAwarenessContext)( DPI_AWARENESS_CONTEXT );
399 DPI_AWARENESS_CONTEXT (WINAPI *SetThreadDpiAwarenessContext)( DPI_AWARENESS_CONTEXT );
400 DPI_AWARENESS_CONTEXT (WINAPI *GetThreadDpiAwarenessContext)( void );
401 DPI_AWARENESS (WINAPI *GetAwarenessFromDpiAwarenessContext)( DPI_AWARENESS_CONTEXT );
402 BOOL (WINAPI *EnableNonClientDpiScaling)( HWND );
403 BOOL (WINAPI *AdjustWindowRectExForDpi)( LPRECT, DWORD, BOOL, DWORD, UINT );
404 UINT (WINAPI *GetDpiForWindow)( HWND );
405 BOOL (WINAPI *AreDpiAwarenessContextsEqual)(DPI_AWARENESS_CONTEXT, DPI_AWARENESS_CONTEXT);
406 BOOL (WINAPI *IsValidDpiAwarenessContext)(DPI_AWARENESS_CONTEXT);
407 // DisplayConfig functions
408 LONG (WINAPI *GetDisplayConfigBufferSizes)( UINT32, UINT32*, UINT32* );
409 LONG (WINAPI *QueryDisplayConfig)( UINT32, UINT32*, DISPLAYCONFIG_PATH_INFO*, UINT32*, DISPLAYCONFIG_MODE_INFO*, DISPLAYCONFIG_TOPOLOGY_ID*);
410 LONG (WINAPI *DisplayConfigGetDeviceInfo)( DISPLAYCONFIG_DEVICE_INFO_HEADER*);
411 /* *INDENT-ON* */ // clang-format on
412
413 SDL_SharedObject *shcoreDLL;
414 /* *INDENT-OFF* */ // clang-format off
415 HRESULT (WINAPI *GetDpiForMonitor)( HMONITOR hmonitor,
416 MONITOR_DPI_TYPE dpiType,
417 UINT *dpiX,
418 UINT *dpiY );
419 HRESULT (WINAPI *SetProcessDpiAwareness)(PROCESS_DPI_AWARENESS dpiAwareness);
420 BOOL (WINAPI *GetPointerType)(UINT32 pointerId, POINTER_INPUT_TYPE *pointerType);
421 BOOL (WINAPI *GetPointerPenInfo)(UINT32 pointerId, POINTER_PEN_INFO *penInfo);
422
423 /* *INDENT-ON* */ // clang-format on
424#endif // !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
425
426#ifdef HAVE_DXGI_H
427 SDL_SharedObject *dxgiDLL;
428 IDXGIFactory *pDXGIFactory;
429#endif
430
431 bool cleared;
432
433 BYTE *rawinput;
434 UINT rawinput_offset;
435 UINT rawinput_size;
436 UINT rawinput_count;
437 Uint64 last_rawinput_poll;
438 SDL_Point last_raw_mouse_position;
439 bool raw_mouse_enabled;
440 bool raw_keyboard_enabled;
441 bool pending_E1_key_sequence;
442 Uint32 raw_input_enabled;
443
444 WIN_GameInputData *gameinput_context;
445
446#ifndef SDL_DISABLE_WINDOWS_IME
447 bool ime_initialized;
448 bool ime_enabled;
449 bool ime_available;
450 bool ime_internal_composition;
451 bool ime_internal_candidates;
452 HWND ime_hwnd_main;
453 HWND ime_hwnd_current;
454 bool ime_needs_clear_composition;
455 HIMC ime_himc;
456
457 WCHAR *ime_composition;
458 int ime_composition_length;
459 WCHAR ime_readingstring[16];
460 int ime_cursor;
461 int ime_selected_start;
462 int ime_selected_length;
463
464 bool ime_candidates_open;
465 bool ime_update_candidates;
466 char *ime_candidates[MAX_CANDLIST];
467 int ime_candcount;
468 DWORD ime_candref;
469 DWORD ime_candsel;
470 int ime_candlistindexbase;
471 bool ime_horizontal_candidates;
472#endif
473
474#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
475 COMPOSITIONFORM ime_composition_area;
476 CANDIDATEFORM ime_candidate_area;
477#endif // !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
478
479#ifndef SDL_DISABLE_WINDOWS_IME
480 HKL ime_hkl;
481 SDL_SharedObject *ime_himm32;
482 /* *INDENT-OFF* */ // clang-format off
483 UINT (WINAPI *GetReadingString)(HIMC himc, UINT uReadingBufLen, LPWSTR lpwReadingBuf, PINT pnErrorIndex, BOOL *pfIsVertical, PUINT puMaxReadingLen);
484 BOOL (WINAPI *ShowReadingWindow)(HIMC himc, BOOL bShow);
485 LPINPUTCONTEXT2 (WINAPI *ImmLockIMC)(HIMC himc);
486 BOOL (WINAPI *ImmUnlockIMC)(HIMC himc);
487 LPVOID (WINAPI *ImmLockIMCC)(HIMCC himcc);
488 BOOL (WINAPI *ImmUnlockIMCC)(HIMCC himcc);
489 /* *INDENT-ON* */ // clang-format on
490
491#endif // !SDL_DISABLE_WINDOWS_IME
492
493 BYTE pre_hook_key_state[256];
494 UINT _SDL_WAKEUP;
495};
496
497extern bool g_WindowsEnableMessageLoop;
498extern bool g_WindowsEnableMenuMnemonics;
499extern bool g_WindowFrameUsableWhileCursorHidden;
500
501typedef struct IDirect3D9 IDirect3D9;
502extern bool D3D_LoadDLL(void **pD3DDLL, IDirect3D9 **pDirect3D9Interface);
503
504extern SDL_SystemTheme WIN_GetSystemTheme(void);
505extern bool WIN_IsPerMonitorV2DPIAware(SDL_VideoDevice *_this);
506
507#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 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21
22/*
23 * @author Mark Callow, www.edgewise-consulting.com. Based on Jacob Lifshay's
24 * SDL_x11vulkan.c.
25 */
26
27#include "SDL_internal.h"
28
29#if defined(SDL_VIDEO_VULKAN) && defined(SDL_VIDEO_DRIVER_WINDOWS)
30
31#include "../SDL_vulkan_internal.h"
32
33#include "SDL_windowsvideo.h"
34#include "SDL_windowswindow.h"
35
36#include "SDL_windowsvulkan.h"
37
38bool WIN_Vulkan_LoadLibrary(SDL_VideoDevice *_this, const char *path)
39{
40 VkExtensionProperties *extensions = NULL;
41 Uint32 extensionCount = 0;
42 Uint32 i;
43 bool hasSurfaceExtension = false;
44 bool hasWin32SurfaceExtension = false;
45 PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr = NULL;
46 if (_this->vulkan_config.loader_handle) {
47 return SDL_SetError("Vulkan already loaded");
48 }
49
50 // Load the Vulkan loader library
51 if (!path) {
52 path = SDL_GetHint(SDL_HINT_VULKAN_LIBRARY);
53 }
54 if (!path) {
55 path = "vulkan-1.dll";
56 }
57 _this->vulkan_config.loader_handle = SDL_LoadObject(path);
58 if (!_this->vulkan_config.loader_handle) {
59 return false;
60 }
61 SDL_strlcpy(_this->vulkan_config.loader_path, path,
62 SDL_arraysize(_this->vulkan_config.loader_path));
63 vkGetInstanceProcAddr = (PFN_vkGetInstanceProcAddr)SDL_LoadFunction(
64 _this->vulkan_config.loader_handle, "vkGetInstanceProcAddr");
65 if (!vkGetInstanceProcAddr) {
66 goto fail;
67 }
68 _this->vulkan_config.vkGetInstanceProcAddr = (SDL_FunctionPointer)vkGetInstanceProcAddr;
69 _this->vulkan_config.vkEnumerateInstanceExtensionProperties =
70 (SDL_FunctionPointer)vkGetInstanceProcAddr(
71 VK_NULL_HANDLE, "vkEnumerateInstanceExtensionProperties");
72 if (!_this->vulkan_config.vkEnumerateInstanceExtensionProperties) {
73 goto fail;
74 }
75 extensions = SDL_Vulkan_CreateInstanceExtensionsList(
76 (PFN_vkEnumerateInstanceExtensionProperties)
77 _this->vulkan_config.vkEnumerateInstanceExtensionProperties,
78 &extensionCount);
79 if (!extensions) {
80 goto fail;
81 }
82 for (i = 0; i < extensionCount; i++) {
83 if (SDL_strcmp(VK_KHR_SURFACE_EXTENSION_NAME, extensions[i].extensionName) == 0) {
84 hasSurfaceExtension = true;
85 } else if (SDL_strcmp(VK_KHR_WIN32_SURFACE_EXTENSION_NAME, extensions[i].extensionName) == 0) {
86 hasWin32SurfaceExtension = true;
87 }
88 }
89 SDL_free(extensions);
90 if (!hasSurfaceExtension) {
91 SDL_SetError("Installed Vulkan doesn't implement the " VK_KHR_SURFACE_EXTENSION_NAME " extension");
92 goto fail;
93 } else if (!hasWin32SurfaceExtension) {
94 SDL_SetError("Installed Vulkan doesn't implement the " VK_KHR_WIN32_SURFACE_EXTENSION_NAME "extension");
95 goto fail;
96 }
97 return true;
98
99fail:
100 SDL_UnloadObject(_this->vulkan_config.loader_handle);
101 _this->vulkan_config.loader_handle = NULL;
102 return false;
103}
104
105void WIN_Vulkan_UnloadLibrary(SDL_VideoDevice *_this)
106{
107 if (_this->vulkan_config.loader_handle) {
108 SDL_UnloadObject(_this->vulkan_config.loader_handle);
109 _this->vulkan_config.loader_handle = NULL;
110 }
111}
112
113char const* const* WIN_Vulkan_GetInstanceExtensions(SDL_VideoDevice *_this,
114 Uint32 *count)
115{
116 static const char *const extensionsForWin32[] = {
117 VK_KHR_SURFACE_EXTENSION_NAME, VK_KHR_WIN32_SURFACE_EXTENSION_NAME
118 };
119 if (count) {
120 *count = SDL_arraysize(extensionsForWin32);
121 }
122 return extensionsForWin32;
123}
124
125bool WIN_Vulkan_CreateSurface(SDL_VideoDevice *_this,
126 SDL_Window *window,
127 VkInstance instance,
128 const struct VkAllocationCallbacks *allocator,
129 VkSurfaceKHR *surface)
130{
131 SDL_WindowData *windowData = window->internal;
132 PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr =
133 (PFN_vkGetInstanceProcAddr)_this->vulkan_config.vkGetInstanceProcAddr;
134 PFN_vkCreateWin32SurfaceKHR vkCreateWin32SurfaceKHR =
135 (PFN_vkCreateWin32SurfaceKHR)vkGetInstanceProcAddr(
136 instance,
137 "vkCreateWin32SurfaceKHR");
138 VkWin32SurfaceCreateInfoKHR createInfo;
139 VkResult result;
140
141 if (!_this->vulkan_config.loader_handle) {
142 return SDL_SetError("Vulkan is not loaded");
143 }
144
145 if (!vkCreateWin32SurfaceKHR) {
146 return SDL_SetError(VK_KHR_WIN32_SURFACE_EXTENSION_NAME
147 " extension is not enabled in the Vulkan instance.");
148 }
149 createInfo.sType = VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR;
150 createInfo.pNext = NULL;
151 createInfo.flags = 0;
152 createInfo.hinstance = windowData->hinstance;
153 createInfo.hwnd = windowData->hwnd;
154 result = vkCreateWin32SurfaceKHR(instance, &createInfo, allocator, surface);
155 if (result != VK_SUCCESS) {
156 return SDL_SetError("vkCreateWin32SurfaceKHR failed: %s", SDL_Vulkan_GetResultString(result));
157 }
158 return true;
159}
160
161void WIN_Vulkan_DestroySurface(SDL_VideoDevice *_this,
162 VkInstance instance,
163 VkSurfaceKHR surface,
164 const struct VkAllocationCallbacks *allocator)
165{
166 if (_this->vulkan_config.loader_handle) {
167 SDL_Vulkan_DestroySurface_Internal(_this->vulkan_config.vkGetInstanceProcAddr, instance, surface, allocator);
168 }
169}
170
171bool WIN_Vulkan_GetPresentationSupport(SDL_VideoDevice *_this,
172 VkInstance instance,
173 VkPhysicalDevice physicalDevice,
174 Uint32 queueFamilyIndex)
175{
176 PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr =
177 (PFN_vkGetInstanceProcAddr)_this->vulkan_config.vkGetInstanceProcAddr;
178 PFN_vkGetPhysicalDeviceWin32PresentationSupportKHR vkGetPhysicalDeviceWin32PresentationSupportKHR =
179 (PFN_vkGetPhysicalDeviceWin32PresentationSupportKHR)vkGetInstanceProcAddr(
180 instance,
181 "vkGetPhysicalDeviceWin32PresentationSupportKHR");
182
183 if (!_this->vulkan_config.loader_handle) {
184 return SDL_SetError("Vulkan is not loaded");
185 }
186
187 if (!vkGetPhysicalDeviceWin32PresentationSupportKHR) {
188 return SDL_SetError(VK_KHR_WIN32_SURFACE_EXTENSION_NAME " extension is not enabled in the Vulkan instance.");
189 }
190
191 return vkGetPhysicalDeviceWin32PresentationSupportKHR(physicalDevice,
192 queueFamilyIndex);
193}
194
195#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 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21
22/*
23 * @author Mark Callow, www.edgewise-consulting.com. Based on Jacob Lifshay's
24 * SDL_x11vulkan.h.
25 */
26
27#include "SDL_internal.h"
28
29#ifndef SDL_windowsvulkan_h_
30#define SDL_windowsvulkan_h_
31
32#include <SDL3/SDL_vulkan.h>
33
34#if defined(SDL_VIDEO_VULKAN) && defined(SDL_VIDEO_DRIVER_WINDOWS)
35
36extern bool WIN_Vulkan_LoadLibrary(SDL_VideoDevice *_this, const char *path);
37extern void WIN_Vulkan_UnloadLibrary(SDL_VideoDevice *_this);
38extern char const* const* WIN_Vulkan_GetInstanceExtensions(SDL_VideoDevice *_this, Uint32 *count);
39extern bool WIN_Vulkan_CreateSurface(SDL_VideoDevice *_this,
40 SDL_Window *window,
41 VkInstance instance,
42 const struct VkAllocationCallbacks *allocator,
43 VkSurfaceKHR *surface);
44extern void WIN_Vulkan_DestroySurface(SDL_VideoDevice *_this,
45 VkInstance instance,
46 VkSurfaceKHR surface,
47 const struct VkAllocationCallbacks *allocator);
48bool WIN_Vulkan_GetPresentationSupport(SDL_VideoDevice *_this,
49 VkInstance instance,
50 VkPhysicalDevice physicalDevice,
51 Uint32 queueFamilyIndex);
52
53#endif
54
55#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 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#ifdef SDL_VIDEO_DRIVER_WINDOWS
24
25#include "../../core/windows/SDL_windows.h"
26
27#include "../../SDL_hints_c.h"
28#include "../../events/SDL_dropevents_c.h"
29#include "../../events/SDL_keyboard_c.h"
30#include "../../events/SDL_mouse_c.h"
31#include "../../events/SDL_windowevents_c.h"
32#include "../SDL_pixels_c.h"
33#include "../SDL_sysvideo.h"
34
35#include "SDL_windowsvideo.h"
36#include "SDL_windowswindow.h"
37
38// Dropfile support
39#include <shellapi.h>
40
41// DWM setting support
42typedef HRESULT (WINAPI *DwmSetWindowAttribute_t)(HWND hwnd, DWORD dwAttribute, LPCVOID pvAttribute, DWORD cbAttribute);
43typedef HRESULT (WINAPI *DwmGetWindowAttribute_t)(HWND hwnd, DWORD dwAttribute, PVOID pvAttribute, DWORD cbAttribute);
44
45// Dark mode support
46typedef enum {
47 UXTHEME_APPMODE_DEFAULT,
48 UXTHEME_APPMODE_ALLOW_DARK,
49 UXTHEME_APPMODE_FORCE_DARK,
50 UXTHEME_APPMODE_FORCE_LIGHT,
51 UXTHEME_APPMODE_MAX
52} UxthemePreferredAppMode;
53
54typedef enum {
55 WCA_UNDEFINED = 0,
56 WCA_USEDARKMODECOLORS = 26,
57 WCA_LAST = 27
58} WINDOWCOMPOSITIONATTRIB;
59
60typedef struct {
61 WINDOWCOMPOSITIONATTRIB Attrib;
62 PVOID pvData;
63 SIZE_T cbData;
64} WINDOWCOMPOSITIONATTRIBDATA;
65
66typedef struct {
67 ULONG dwOSVersionInfoSize;
68 ULONG dwMajorVersion;
69 ULONG dwMinorVersion;
70 ULONG dwBuildNumber;
71 ULONG dwPlatformId;
72 WCHAR szCSDVersion[128];
73} NT_OSVERSIONINFOW;
74
75typedef bool (WINAPI *ShouldAppsUseDarkMode_t)(void);
76typedef void (WINAPI *AllowDarkModeForWindow_t)(HWND, bool);
77typedef void (WINAPI *AllowDarkModeForApp_t)(bool);
78typedef void (WINAPI *RefreshImmersiveColorPolicyState_t)(void);
79typedef UxthemePreferredAppMode (WINAPI *SetPreferredAppMode_t)(UxthemePreferredAppMode);
80typedef BOOL (WINAPI *SetWindowCompositionAttribute_t)(HWND, const WINDOWCOMPOSITIONATTRIBDATA *);
81typedef void (NTAPI *RtlGetVersion_t)(NT_OSVERSIONINFOW *);
82
83// Corner rounding support (Win 11+)
84#ifndef DWMWA_WINDOW_CORNER_PREFERENCE
85#define DWMWA_WINDOW_CORNER_PREFERENCE 33
86#endif
87typedef enum {
88 DWMWCP_DEFAULT = 0,
89 DWMWCP_DONOTROUND = 1,
90 DWMWCP_ROUND = 2,
91 DWMWCP_ROUNDSMALL = 3
92} DWM_WINDOW_CORNER_PREFERENCE;
93
94// Border Color support (Win 11+)
95#ifndef DWMWA_BORDER_COLOR
96#define DWMWA_BORDER_COLOR 34
97#endif
98
99#ifndef DWMWA_COLOR_DEFAULT
100#define DWMWA_COLOR_DEFAULT 0xFFFFFFFF
101#endif
102
103#ifndef DWMWA_COLOR_NONE
104#define DWMWA_COLOR_NONE 0xFFFFFFFE
105#endif
106
107// Transparent window support
108#ifndef DWM_BB_ENABLE
109#define DWM_BB_ENABLE 0x00000001
110#endif
111#ifndef DWM_BB_BLURREGION
112#define DWM_BB_BLURREGION 0x00000002
113#endif
114typedef struct
115{
116 DWORD flags;
117 BOOL enable;
118 HRGN blur_region;
119 BOOL transition_on_maxed;
120} DWM_BLURBEHIND;
121typedef HRESULT(WINAPI *DwmEnableBlurBehindWindow_t)(HWND hwnd, const DWM_BLURBEHIND *pBlurBehind);
122
123// Windows CE compatibility
124#ifndef SWP_NOCOPYBITS
125#define SWP_NOCOPYBITS 0
126#endif
127
128/* An undocumented message to create a popup system menu
129 * - wParam is always 0
130 * - lParam = MAKELONG(x, y) where x and y are the screen coordinates where the menu should be displayed
131 */
132#ifndef WM_POPUPSYSTEMMENU
133#define WM_POPUPSYSTEMMENU 0x313
134#endif
135
136// #define HIGHDPI_DEBUG
137
138// Fake window to help with DirectInput events.
139HWND SDL_HelperWindow = NULL;
140static const TCHAR *SDL_HelperWindowClassName = TEXT("SDLHelperWindowInputCatcher");
141static const TCHAR *SDL_HelperWindowName = TEXT("SDLHelperWindowInputMsgWindow");
142static ATOM SDL_HelperWindowClass = 0;
143
144/* For borderless Windows, still want the following flag:
145 - WS_MINIMIZEBOX: window will respond to Windows minimize commands sent to all windows, such as windows key + m, shaking title bar, etc.
146 Additionally, non-fullscreen windows can add:
147 - WS_CAPTION: this seems to enable the Windows minimize animation
148 - WS_SYSMENU: enables system context menu on task bar
149 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
150 - WS_THICKFRAME: allows hit-testing to resize window (doesn't actually add a frame to a borderless window).
151 - 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
152 */
153
154#define STYLE_BASIC (WS_CLIPSIBLINGS | WS_CLIPCHILDREN)
155#define STYLE_FULLSCREEN (WS_POPUP | WS_MINIMIZEBOX)
156#define STYLE_BORDERLESS (WS_POPUP | WS_MINIMIZEBOX)
157#define STYLE_BORDERLESS_WINDOWED (WS_POPUP | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX)
158#define STYLE_NORMAL (WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX)
159#define STYLE_RESIZABLE (WS_THICKFRAME | WS_MAXIMIZEBOX)
160#define STYLE_MASK (STYLE_FULLSCREEN | STYLE_BORDERLESS | STYLE_NORMAL | STYLE_RESIZABLE)
161
162static DWORD GetWindowStyle(SDL_Window *window)
163{
164 DWORD style = 0;
165
166 if (SDL_WINDOW_IS_POPUP(window)) {
167 style |= WS_POPUP;
168 } else if (window->flags & SDL_WINDOW_FULLSCREEN) {
169 style |= STYLE_FULLSCREEN;
170 } else {
171 if (window->flags & SDL_WINDOW_BORDERLESS) {
172 /* This behavior more closely matches other platform where the window is borderless
173 but still interacts with the window manager (e.g. task bar shows above it, it can
174 be resized to fit within usable desktop area, etc.)
175 */
176 if (SDL_GetHintBoolean("SDL_BORDERLESS_WINDOWED_STYLE", true)) {
177 style |= STYLE_BORDERLESS_WINDOWED;
178 } else {
179 style |= STYLE_BORDERLESS;
180 }
181 } else {
182 style |= STYLE_NORMAL;
183 }
184
185 /* The WS_MAXIMIZEBOX style flag needs to be retained for as long as the window is maximized,
186 * or restoration from minimized can fail, and leaving maximized can result in an odd size.
187 */
188 if (window->flags & SDL_WINDOW_RESIZABLE) {
189 /* You can have a borderless resizable window, but Windows doesn't always draw it correctly,
190 see https://bugzilla.libsdl.org/show_bug.cgi?id=4466
191 */
192 if (!(window->flags & SDL_WINDOW_BORDERLESS) ||
193 SDL_GetHintBoolean("SDL_BORDERLESS_RESIZABLE_STYLE", true)) {
194 style |= STYLE_RESIZABLE;
195 }
196 }
197
198 if (window->internal && window->internal->force_ws_maximizebox) {
199 /* Even if the resizable flag is cleared, WS_MAXIMIZEBOX is still needed as long
200 * as the window is maximized, or de-maximizing or minimizing and restoring the
201 * maximized window can result in the window disappearing or being the wrong size.
202 */
203 style |= WS_MAXIMIZEBOX;
204 }
205
206 // Need to set initialize minimize style, or when we call ShowWindow with WS_MINIMIZE it will activate a random window
207 if (window->flags & SDL_WINDOW_MINIMIZED) {
208 style |= WS_MINIMIZE;
209 }
210 }
211 return style;
212}
213
214static DWORD GetWindowStyleEx(SDL_Window *window)
215{
216 DWORD style = 0;
217
218 if (SDL_WINDOW_IS_POPUP(window) || (window->flags & SDL_WINDOW_UTILITY)) {
219 style |= WS_EX_TOOLWINDOW;
220 }
221 if (SDL_WINDOW_IS_POPUP(window) || (window->flags & SDL_WINDOW_NOT_FOCUSABLE)) {
222 style |= WS_EX_NOACTIVATE;
223 }
224 return style;
225}
226
227/**
228 * Returns arguments to pass to SetWindowPos - the window rect, including frame, in Windows coordinates.
229 * Can be called before we have a HWND.
230 */
231static bool WIN_AdjustWindowRectWithStyle(SDL_Window *window, DWORD style, DWORD styleEx, BOOL menu, int *x, int *y, int *width, int *height, SDL_WindowRect rect_type)
232{
233 SDL_VideoData *videodata = SDL_GetVideoDevice() ? SDL_GetVideoDevice()->internal : NULL;
234 RECT rect;
235
236 // Client rect, in points
237 switch (rect_type) {
238 case SDL_WINDOWRECT_CURRENT:
239 SDL_RelativeToGlobalForWindow(window, window->x, window->y, x, y);
240 *width = window->w;
241 *height = window->h;
242 break;
243 case SDL_WINDOWRECT_WINDOWED:
244 SDL_RelativeToGlobalForWindow(window, window->windowed.x, window->windowed.y, x, y);
245 *width = window->windowed.w;
246 *height = window->windowed.h;
247 break;
248 case SDL_WINDOWRECT_FLOATING:
249 SDL_RelativeToGlobalForWindow(window, window->floating.x, window->floating.y, x, y);
250 *width = window->floating.w;
251 *height = window->floating.h;
252 break;
253 case SDL_WINDOWRECT_PENDING:
254 SDL_RelativeToGlobalForWindow(window, window->pending.x, window->pending.y, x, y);
255 *width = window->pending.w;
256 *height = window->pending.h;
257 break;
258 default:
259 // Should never be here
260 SDL_assert_release(false);
261 *width = 0;
262 *height = 0;
263 break;
264 }
265
266 /* Copy the client size in pixels into this rect structure,
267 which we'll then adjust with AdjustWindowRectEx */
268 rect.left = 0;
269 rect.top = 0;
270 rect.right = *width;
271 rect.bottom = *height;
272
273 /* 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
274 expanding the window client area to the previous window + chrome size, so shouldn't need to adjust the window size for the set styles.
275 */
276 if (!(window->flags & SDL_WINDOW_BORDERLESS) && !SDL_WINDOW_IS_POPUP(window)) {
277#if defined(SDL_PLATFORM_XBOXONE) || defined(SDL_PLATFORM_XBOXSERIES)
278 AdjustWindowRectEx(&rect, style, menu, 0);
279#else
280 if (WIN_IsPerMonitorV2DPIAware(SDL_GetVideoDevice())) {
281 /* With per-monitor v2, the window border/titlebar size depend on the DPI, so we need to call AdjustWindowRectExForDpi instead of
282 AdjustWindowRectEx. */
283 if (videodata) {
284 UINT frame_dpi;
285 SDL_WindowData *data = window->internal;
286 frame_dpi = (data && videodata->GetDpiForWindow) ? videodata->GetDpiForWindow(data->hwnd) : USER_DEFAULT_SCREEN_DPI;
287 if (videodata->AdjustWindowRectExForDpi(&rect, style, menu, styleEx, frame_dpi) == 0) {
288 return WIN_SetError("AdjustWindowRectExForDpi()");
289 }
290 }
291 } else {
292 if (AdjustWindowRectEx(&rect, style, menu, styleEx) == 0) {
293 return WIN_SetError("AdjustWindowRectEx()");
294 }
295 }
296#endif
297 }
298
299 // Final rect in Windows screen space, including the frame
300 *x += rect.left;
301 *y += rect.top;
302 *width = (rect.right - rect.left);
303 *height = (rect.bottom - rect.top);
304
305#ifdef HIGHDPI_DEBUG
306 SDL_Log("WIN_AdjustWindowRectWithStyle: in: %d, %d, %dx%d, returning: %d, %d, %dx%d, used dpi %d for frame calculation",
307 (rect_type == SDL_WINDOWRECT_FLOATING ? window->floating.x : rect_type == SDL_WINDOWRECT_WINDOWED ? window->windowed.x : window->x),
308 (rect_type == SDL_WINDOWRECT_FLOATING ? window->floating.y : rect_type == SDL_WINDOWRECT_WINDOWED ? window->windowed.y : window->y),
309 (rect_type == SDL_WINDOWRECT_FLOATING ? window->floating.w : rect_type == SDL_WINDOWRECT_WINDOWED ? window->windowed.w : window->w),
310 (rect_type == SDL_WINDOWRECT_FLOATING ? window->floating.h : rect_type == SDL_WINDOWRECT_WINDOWED ? window->windowed.h : window->h),
311 *x, *y, *width, *height, frame_dpi);
312#endif
313 return true;
314}
315
316bool WIN_AdjustWindowRect(SDL_Window *window, int *x, int *y, int *width, int *height, SDL_WindowRect rect_type)
317{
318 SDL_WindowData *data = window->internal;
319 HWND hwnd = data->hwnd;
320 DWORD style, styleEx;
321 BOOL menu;
322
323 style = GetWindowLong(hwnd, GWL_STYLE);
324 styleEx = GetWindowLong(hwnd, GWL_EXSTYLE);
325#if defined(SDL_PLATFORM_XBOXONE) || defined(SDL_PLATFORM_XBOXSERIES)
326 menu = FALSE;
327#else
328 menu = (style & WS_CHILDWINDOW) ? FALSE : (GetMenu(hwnd) != NULL);
329#endif
330 return WIN_AdjustWindowRectWithStyle(window, style, styleEx, menu, x, y, width, height, rect_type);
331}
332
333bool WIN_AdjustWindowRectForHWND(HWND hwnd, LPRECT lpRect, UINT frame_dpi)
334{
335 SDL_VideoDevice *videodevice = SDL_GetVideoDevice();
336 SDL_VideoData *videodata = videodevice ? videodevice->internal : NULL;
337 DWORD style, styleEx;
338 BOOL menu;
339
340 style = GetWindowLong(hwnd, GWL_STYLE);
341 styleEx = GetWindowLong(hwnd, GWL_EXSTYLE);
342#if defined(SDL_PLATFORM_XBOXONE) || defined(SDL_PLATFORM_XBOXSERIES)
343 menu = FALSE;
344#else
345 menu = (style & WS_CHILDWINDOW) ? FALSE : (GetMenu(hwnd) != NULL);
346#endif
347
348#if defined(SDL_PLATFORM_XBOXONE) || defined(SDL_PLATFORM_XBOXSERIES)
349 AdjustWindowRectEx(lpRect, style, menu, styleEx);
350#else
351 if (WIN_IsPerMonitorV2DPIAware(videodevice)) {
352 // With per-monitor v2, the window border/titlebar size depend on the DPI, so we need to call AdjustWindowRectExForDpi instead of AdjustWindowRectEx.
353 if (!frame_dpi) {
354 frame_dpi = videodata->GetDpiForWindow ? videodata->GetDpiForWindow(hwnd) : USER_DEFAULT_SCREEN_DPI;
355 }
356 if (!videodata->AdjustWindowRectExForDpi(lpRect, style, menu, styleEx, frame_dpi)) {
357 return WIN_SetError("AdjustWindowRectExForDpi()");
358 }
359 } else {
360 if (!AdjustWindowRectEx(lpRect, style, menu, styleEx)) {
361 return WIN_SetError("AdjustWindowRectEx()");
362 }
363 }
364#endif
365 return true;
366}
367
368bool WIN_SetWindowPositionInternal(SDL_Window *window, UINT flags, SDL_WindowRect rect_type)
369{
370 SDL_Window *child_window;
371 SDL_WindowData *data = window->internal;
372 HWND hwnd = data->hwnd;
373 HWND top;
374 int x, y;
375 int w, h;
376 bool result = true;
377
378 // Figure out what the window area will be
379 if (SDL_ShouldAllowTopmost() && (window->flags & SDL_WINDOW_ALWAYS_ON_TOP)) {
380 top = HWND_TOPMOST;
381 } else {
382 top = HWND_NOTOPMOST;
383 }
384
385 WIN_AdjustWindowRect(window, &x, &y, &w, &h, rect_type);
386
387 data->expected_resize = true;
388 if (SetWindowPos(hwnd, top, x, y, w, h, flags) == 0) {
389 result = WIN_SetError("SetWindowPos()");
390 }
391 data->expected_resize = false;
392
393 // Update any child windows
394 for (child_window = window->first_child; child_window; child_window = child_window->next_sibling) {
395 if (!WIN_SetWindowPositionInternal(child_window, flags, SDL_WINDOWRECT_CURRENT)) {
396 result = false;
397 }
398 }
399 return result;
400}
401
402static SDL_WindowEraseBackgroundMode GetEraseBackgroundModeHint(void)
403{
404 const char *hint = SDL_GetHint(SDL_HINT_WINDOWS_ERASE_BACKGROUND_MODE);
405 if (!hint)
406 return SDL_ERASEBACKGROUNDMODE_INITIAL;
407
408 if (SDL_strstr(hint, "never"))
409 return SDL_ERASEBACKGROUNDMODE_NEVER;
410
411 if (SDL_strstr(hint, "initial"))
412 return SDL_ERASEBACKGROUNDMODE_INITIAL;
413
414 if (SDL_strstr(hint, "always"))
415 return SDL_ERASEBACKGROUNDMODE_ALWAYS;
416
417 int mode = SDL_GetStringInteger(hint, 1);
418 if (mode < 0 || mode > 2) {
419 SDL_Log("GetEraseBackgroundModeHint: invalid value for SDL_HINT_WINDOWS_ERASE_BACKGROUND_MODE. Fallback to default");
420 return SDL_ERASEBACKGROUNDMODE_INITIAL;
421 }
422
423 return (SDL_WindowEraseBackgroundMode)mode;
424}
425
426static bool SetupWindowData(SDL_VideoDevice *_this, SDL_Window *window, HWND hwnd, HWND parent)
427{
428 SDL_VideoData *videodata = _this->internal;
429 SDL_WindowData *data;
430
431 // Allocate the window data
432 data = (SDL_WindowData *)SDL_calloc(1, sizeof(*data));
433 if (!data) {
434 return false;
435 }
436 data->window = window;
437 data->hwnd = hwnd;
438 data->parent = parent;
439#if defined(SDL_PLATFORM_XBOXONE) || defined(SDL_PLATFORM_XBOXSERIES)
440 data->hdc = (HDC)data->hwnd;
441#else
442 data->hdc = GetDC(hwnd);
443#endif
444 data->hinstance = (HINSTANCE)GetWindowLongPtr(hwnd, GWLP_HINSTANCE);
445 data->mouse_button_flags = (WPARAM)-1;
446 data->last_pointer_update = (LPARAM)-1;
447 data->videodata = videodata;
448 data->initializing = true;
449 data->last_displayID = window->last_displayID;
450 data->dwma_border_color = DWMWA_COLOR_DEFAULT;
451 data->hint_erase_background_mode = GetEraseBackgroundModeHint();
452
453
454 // WIN_WarpCursor() jitters by +1, and remote desktop warp wobble is +/- 1
455#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
456 LONG remote_desktop_adjustment = GetSystemMetrics(SM_REMOTESESSION) ? 2 : 0;
457 data->cursor_ctrlock_rect.left = 0 - remote_desktop_adjustment;
458 data->cursor_ctrlock_rect.top = 0;
459 data->cursor_ctrlock_rect.right = 1 + remote_desktop_adjustment;
460 data->cursor_ctrlock_rect.bottom = 1;
461#endif
462
463 if (SDL_GetHintBoolean("SDL_WINDOW_RETAIN_CONTENT", false)) {
464 data->copybits_flag = 0;
465 } else {
466 data->copybits_flag = SWP_NOCOPYBITS;
467 }
468
469#ifdef HIGHDPI_DEBUG
470 SDL_Log("SetupWindowData: initialized data->scaling_dpi to %d", data->scaling_dpi);
471#endif
472
473#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
474 // Associate the data with the window
475 if (!SetProp(hwnd, TEXT("SDL_WindowData"), data)) {
476 ReleaseDC(hwnd, data->hdc);
477 SDL_free(data);
478 return WIN_SetError("SetProp() failed");
479 }
480#endif
481
482 window->internal = data;
483
484 // Set up the window proc function
485#ifdef GWLP_WNDPROC
486 data->wndproc = (WNDPROC)GetWindowLongPtr(hwnd, GWLP_WNDPROC);
487 if (data->wndproc == WIN_WindowProc) {
488 data->wndproc = NULL;
489 } else {
490 SetWindowLongPtr(hwnd, GWLP_WNDPROC, (LONG_PTR)WIN_WindowProc);
491 }
492#else
493 data->wndproc = (WNDPROC)GetWindowLong(hwnd, GWL_WNDPROC);
494 if (data->wndproc == WIN_WindowProc) {
495 data->wndproc = NULL;
496 } else {
497 SetWindowLong(hwnd, GWL_WNDPROC, (LONG_PTR)WIN_WindowProc);
498 }
499#endif
500
501 // Fill in the SDL window with the window state
502 {
503 DWORD style = GetWindowLong(hwnd, GWL_STYLE);
504 if (style & WS_VISIBLE) {
505 window->flags &= ~SDL_WINDOW_HIDDEN;
506 } else {
507 window->flags |= SDL_WINDOW_HIDDEN;
508 }
509 if (style & WS_POPUP) {
510 window->flags |= SDL_WINDOW_BORDERLESS;
511 } else {
512 window->flags &= ~SDL_WINDOW_BORDERLESS;
513 }
514 if (style & WS_THICKFRAME) {
515 window->flags |= SDL_WINDOW_RESIZABLE;
516 } else if (!(style & WS_POPUP)) {
517 window->flags &= ~SDL_WINDOW_RESIZABLE;
518 }
519#ifdef WS_MAXIMIZE
520 if (style & WS_MAXIMIZE) {
521 window->flags |= SDL_WINDOW_MAXIMIZED;
522 } else
523#endif
524 {
525 window->flags &= ~SDL_WINDOW_MAXIMIZED;
526 }
527#ifdef WS_MINIMIZE
528 if (style & WS_MINIMIZE) {
529 window->flags |= SDL_WINDOW_MINIMIZED;
530 } else
531#endif
532 {
533 window->flags &= ~SDL_WINDOW_MINIMIZED;
534 }
535 }
536 if (!(window->flags & SDL_WINDOW_MINIMIZED)) {
537 RECT rect;
538 if (GetClientRect(hwnd, &rect) && !WIN_IsRectEmpty(&rect)) {
539 int w = rect.right;
540 int h = rect.bottom;
541
542 if (window->flags & SDL_WINDOW_EXTERNAL) {
543 window->floating.w = window->windowed.w = window->w = w;
544 window->floating.h = window->windowed.h = window->h = h;
545 } else if ((window->windowed.w && window->windowed.w != w) || (window->windowed.h && window->windowed.h != h)) {
546 // We tried to create a window larger than the desktop and Windows didn't allow it. Override!
547 int x, y;
548 // Figure out what the window area will be
549 WIN_AdjustWindowRect(window, &x, &y, &w, &h, SDL_WINDOWRECT_FLOATING);
550 data->expected_resize = true;
551 SetWindowPos(hwnd, NULL, x, y, w, h, data->copybits_flag | SWP_NOZORDER | SWP_NOOWNERZORDER | SWP_NOACTIVATE);
552 data->expected_resize = false;
553 } else {
554 window->w = w;
555 window->h = h;
556 }
557 }
558 }
559#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
560 if (!(window->flags & SDL_WINDOW_MINIMIZED)) {
561 POINT point;
562 point.x = 0;
563 point.y = 0;
564 if (ClientToScreen(hwnd, &point)) {
565 if (window->flags & SDL_WINDOW_EXTERNAL) {
566 window->floating.x = window->windowed.x = point.x;
567 window->floating.y = window->windowed.y = point.y;
568 }
569 window->x = point.x;
570 window->y = point.y;
571 }
572 }
573
574 WIN_UpdateWindowICCProfile(window, false);
575#endif
576
577#if defined(SDL_PLATFORM_XBOXONE) || defined(SDL_PLATFORM_XBOXSERIES)
578 window->flags |= SDL_WINDOW_INPUT_FOCUS;
579 SDL_SetKeyboardFocus(window);
580#else
581 if (GetFocus() == hwnd) {
582 window->flags |= SDL_WINDOW_INPUT_FOCUS;
583 SDL_SetKeyboardFocus(window);
584 WIN_UpdateClipCursor(window);
585 }
586#endif
587
588 if (window->flags & SDL_WINDOW_ALWAYS_ON_TOP) {
589 WIN_SetWindowAlwaysOnTop(_this, window, true);
590 } else {
591 WIN_SetWindowAlwaysOnTop(_this, window, false);
592 }
593
594#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
595 // Enable multi-touch
596 if (videodata->RegisterTouchWindow) {
597 videodata->RegisterTouchWindow(hwnd, (TWF_FINETOUCH | TWF_WANTPALM));
598 }
599#endif
600
601 if (data->parent && !window->parent) {
602 data->destroy_parent_with_window = true;
603 }
604
605 data->initializing = false;
606
607#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
608 if (window->flags & SDL_WINDOW_EXTERNAL) {
609 // Query the title from the existing window
610 LPTSTR title;
611 int titleLen;
612 bool isstack;
613
614 titleLen = GetWindowTextLength(hwnd);
615 title = SDL_small_alloc(TCHAR, titleLen + 1, &isstack);
616 if (title) {
617 titleLen = GetWindowText(hwnd, title, titleLen + 1);
618 } else {
619 titleLen = 0;
620 }
621 if (titleLen > 0) {
622 window->title = WIN_StringToUTF8(title);
623 }
624 if (title) {
625 SDL_small_free(title, isstack);
626 }
627 }
628#endif // !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
629
630 SDL_PropertiesID props = SDL_GetWindowProperties(window);
631 SDL_SetPointerProperty(props, SDL_PROP_WINDOW_WIN32_HWND_POINTER, data->hwnd);
632 SDL_SetPointerProperty(props, SDL_PROP_WINDOW_WIN32_HDC_POINTER, data->hdc);
633 SDL_SetPointerProperty(props, SDL_PROP_WINDOW_WIN32_INSTANCE_POINTER, data->hinstance);
634
635 // All done!
636 return true;
637}
638
639static void CleanupWindowData(SDL_VideoDevice *_this, SDL_Window *window)
640{
641 SDL_WindowData *data = window->internal;
642
643 if (data) {
644
645#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
646 if (data->drop_target) {
647 WIN_AcceptDragAndDrop(window, false);
648 }
649 if (data->ICMFileName) {
650 SDL_free(data->ICMFileName);
651 }
652 if (data->keyboard_hook) {
653 UnhookWindowsHookEx(data->keyboard_hook);
654 }
655 ReleaseDC(data->hwnd, data->hdc);
656 RemoveProp(data->hwnd, TEXT("SDL_WindowData"));
657#endif
658 if (!(window->flags & SDL_WINDOW_EXTERNAL)) {
659 DestroyWindow(data->hwnd);
660 if (data->destroy_parent_with_window && data->parent) {
661 DestroyWindow(data->parent);
662 }
663 } else {
664 // Restore any original event handler...
665 if (data->wndproc) {
666#ifdef GWLP_WNDPROC
667 SetWindowLongPtr(data->hwnd, GWLP_WNDPROC,
668 (LONG_PTR)data->wndproc);
669#else
670 SetWindowLong(data->hwnd, GWL_WNDPROC,
671 (LONG_PTR)data->wndproc);
672#endif
673 }
674 }
675 SDL_free(data);
676 }
677 window->internal = NULL;
678}
679
680static void WIN_ConstrainPopup(SDL_Window *window, bool output_to_pending)
681{
682 // Clamp popup windows to the output borders
683 if (SDL_WINDOW_IS_POPUP(window)) {
684 SDL_Window *w;
685 SDL_DisplayID displayID;
686 SDL_Rect rect;
687 int abs_x = window->last_position_pending ? window->pending.x : window->floating.x;
688 int abs_y = window->last_position_pending ? window->pending.y : window->floating.y;
689 const int width = window->last_size_pending ? window->pending.w : window->floating.w;
690 const int height = window->last_size_pending ? window->pending.h : window->floating.h;
691 int offset_x = 0, offset_y = 0;
692
693 // Calculate the total offset from the parents
694 for (w = window->parent; SDL_WINDOW_IS_POPUP(w); w = w->parent) {
695 offset_x += w->x;
696 offset_y += w->y;
697 }
698
699 offset_x += w->x;
700 offset_y += w->y;
701 abs_x += offset_x;
702 abs_y += offset_y;
703
704 // Constrain the popup window to the display of the toplevel parent
705 displayID = SDL_GetDisplayForWindow(w);
706 SDL_GetDisplayBounds(displayID, &rect);
707 if (abs_x + width > rect.x + rect.w) {
708 abs_x -= (abs_x + width) - (rect.x + rect.w);
709 }
710 if (abs_y + height > rect.y + rect.h) {
711 abs_y -= (abs_y + height) - (rect.y + rect.h);
712 }
713 abs_x = SDL_max(abs_x, rect.x);
714 abs_y = SDL_max(abs_y, rect.y);
715
716 if (output_to_pending) {
717 window->pending.x = abs_x - offset_x;
718 window->pending.y = abs_y - offset_y;
719 window->pending.w = width;
720 window->pending.h = height;
721 } else {
722 window->floating.x = abs_x - offset_x;
723 window->floating.y = abs_y - offset_y;
724 window->floating.w = width;
725 window->floating.h = height;
726 }
727 }
728}
729
730static void WIN_SetKeyboardFocus(SDL_Window *window, bool set_active_focus)
731{
732 SDL_Window *toplevel = window;
733
734 // Find the topmost parent
735 while (SDL_WINDOW_IS_POPUP(toplevel)) {
736 toplevel = toplevel->parent;
737 }
738
739 toplevel->internal->keyboard_focus = window;
740
741 if (set_active_focus && !window->is_hiding && !window->is_destroying) {
742 SDL_SetKeyboardFocus(window);
743 }
744}
745
746bool WIN_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID create_props)
747{
748 HWND hwnd = (HWND)SDL_GetPointerProperty(create_props, SDL_PROP_WINDOW_CREATE_WIN32_HWND_POINTER, SDL_GetPointerProperty(create_props, "sdl2-compat.external_window", NULL));
749 HWND parent = NULL;
750 if (hwnd) {
751 window->flags |= SDL_WINDOW_EXTERNAL;
752
753 if (!SetupWindowData(_this, window, hwnd, parent)) {
754 return false;
755 }
756 } else {
757 DWORD style = STYLE_BASIC;
758 DWORD styleEx = 0;
759 int x, y;
760 int w, h;
761
762 if (window->flags & SDL_WINDOW_UTILITY) {
763 parent = CreateWindow(SDL_Appname, TEXT(""), STYLE_BASIC, 0, 0, 32, 32, NULL, NULL, SDL_Instance, NULL);
764 } else if (window->parent) {
765 parent = window->parent->internal->hwnd;
766 }
767
768 style |= GetWindowStyle(window);
769 styleEx |= GetWindowStyleEx(window);
770
771 // Figure out what the window area will be
772 WIN_ConstrainPopup(window, false);
773 WIN_AdjustWindowRectWithStyle(window, style, styleEx, FALSE, &x, &y, &w, &h, SDL_WINDOWRECT_FLOATING);
774
775 hwnd = CreateWindowEx(styleEx, SDL_Appname, TEXT(""), style,
776 x, y, w, h, parent, NULL, SDL_Instance, NULL);
777 if (!hwnd) {
778 return WIN_SetError("Couldn't create window");
779 }
780
781 WIN_UpdateDarkModeForHWND(hwnd);
782
783 WIN_PumpEvents(_this);
784
785 if (!SetupWindowData(_this, window, hwnd, parent)) {
786 DestroyWindow(hwnd);
787 if (parent) {
788 DestroyWindow(parent);
789 }
790 return false;
791 }
792
793 // Inform Windows of the frame change so we can respond to WM_NCCALCSIZE
794 SetWindowPos(hwnd, NULL, 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_NOSIZE | SWP_NOMOVE | SWP_NOZORDER | SWP_NOOWNERZORDER | SWP_NOACTIVATE);
795
796 if (window->flags & SDL_WINDOW_MINIMIZED) {
797 /* TODO: We have to clear SDL_WINDOW_HIDDEN here to ensure the window flags match the window state. The
798 window is already shown after this and windows with WS_MINIMIZE do not generate a WM_SHOWWINDOW. This
799 means you can't currently create a window that is initially hidden and is minimized when shown.
800 */
801 window->flags &= ~SDL_WINDOW_HIDDEN;
802 ShowWindow(hwnd, SW_SHOWMINNOACTIVE);
803 }
804 }
805
806#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
807 // FIXME: does not work on all hardware configurations with different renders (i.e. hybrid GPUs)
808 if (window->flags & SDL_WINDOW_TRANSPARENT) {
809 SDL_SharedObject *handle = SDL_LoadObject("dwmapi.dll");
810 if (handle) {
811 DwmEnableBlurBehindWindow_t DwmEnableBlurBehindWindowFunc = (DwmEnableBlurBehindWindow_t)SDL_LoadFunction(handle, "DwmEnableBlurBehindWindow");
812 if (DwmEnableBlurBehindWindowFunc) {
813 /* The region indicates which part of the window will be blurred and rest will be transparent. This
814 is because the alpha value of the window will be used for non-blurred areas
815 We can use (-1, -1, 0, 0) boundary to make sure no pixels are being blurred
816 */
817 HRGN rgn = CreateRectRgn(-1, -1, 0, 0);
818 DWM_BLURBEHIND bb;
819 bb.flags = (DWM_BB_ENABLE | DWM_BB_BLURREGION);
820 bb.enable = TRUE;
821 bb.blur_region = rgn;
822 bb.transition_on_maxed = FALSE;
823 DwmEnableBlurBehindWindowFunc(hwnd, &bb);
824 DeleteObject(rgn);
825 }
826 SDL_UnloadObject(handle);
827 }
828 }
829
830 HWND share_hwnd = (HWND)SDL_GetPointerProperty(create_props, SDL_PROP_WINDOW_CREATE_WIN32_PIXEL_FORMAT_HWND_POINTER, NULL);
831 if (share_hwnd) {
832 HDC hdc = GetDC(share_hwnd);
833 int pixel_format = GetPixelFormat(hdc);
834 PIXELFORMATDESCRIPTOR pfd;
835
836 SDL_zero(pfd);
837 DescribePixelFormat(hdc, pixel_format, sizeof(pfd), &pfd);
838 ReleaseDC(share_hwnd, hdc);
839
840 if (!SetPixelFormat(window->internal->hdc, pixel_format, &pfd)) {
841 WIN_DestroyWindow(_this, window);
842 return WIN_SetError("SetPixelFormat()");
843 }
844 } else {
845#endif // !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
846 if (!(window->flags & SDL_WINDOW_OPENGL)) {
847 return true;
848 }
849
850 // The rest of this macro mess is for OpenGL or OpenGL ES windows
851#ifdef SDL_VIDEO_OPENGL_ES2
852 if ((_this->gl_config.profile_mask == SDL_GL_CONTEXT_PROFILE_ES ||
853 SDL_GetHintBoolean(SDL_HINT_VIDEO_FORCE_EGL, false))
854#ifdef SDL_VIDEO_OPENGL_WGL
855 && (!_this->gl_data || WIN_GL_UseEGL(_this))
856#endif // SDL_VIDEO_OPENGL_WGL
857 ) {
858#ifdef SDL_VIDEO_OPENGL_EGL
859 if (!WIN_GLES_SetupWindow(_this, window)) {
860 WIN_DestroyWindow(_this, window);
861 return false;
862 }
863 return true;
864#else
865 return SDL_SetError("Could not create GLES window surface (EGL support not configured)");
866#endif // SDL_VIDEO_OPENGL_EGL
867 }
868#endif // SDL_VIDEO_OPENGL_ES2
869
870#ifdef SDL_VIDEO_OPENGL_WGL
871 if (!WIN_GL_SetupWindow(_this, window)) {
872 WIN_DestroyWindow(_this, window);
873 return false;
874 }
875#else
876 return SDL_SetError("Could not create GL window (WGL support not configured)");
877#endif
878#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
879 }
880#endif // !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
881
882 return true;
883}
884
885void WIN_SetWindowTitle(SDL_VideoDevice *_this, SDL_Window *window)
886{
887#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
888 HWND hwnd = window->internal->hwnd;
889 LPTSTR title = WIN_UTF8ToString(window->title);
890 SetWindowText(hwnd, title);
891 SDL_free(title);
892#endif
893}
894
895bool WIN_SetWindowIcon(SDL_VideoDevice *_this, SDL_Window *window, SDL_Surface *icon)
896{
897#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
898 HWND hwnd = window->internal->hwnd;
899 HICON hicon = NULL;
900 BYTE *icon_bmp;
901 int icon_len, mask_len, row_len, y;
902 BITMAPINFOHEADER *bmi;
903 Uint8 *dst;
904 bool isstack;
905 bool result = true;
906
907 // Create temporary buffer for ICONIMAGE structure
908 SDL_COMPILE_TIME_ASSERT(WIN_SetWindowIcon_uses_BITMAPINFOHEADER_to_prepare_an_ICONIMAGE, sizeof(BITMAPINFOHEADER) == 40);
909 mask_len = (icon->h * (icon->w + 7) / 8);
910 icon_len = sizeof(BITMAPINFOHEADER) + icon->h * icon->w * sizeof(Uint32) + mask_len;
911 icon_bmp = SDL_small_alloc(BYTE, icon_len, &isstack);
912 if (!icon_bmp) {
913 return false;
914 }
915
916 // Write the BITMAPINFO header
917 bmi = (BITMAPINFOHEADER *)icon_bmp;
918 bmi->biSize = SDL_Swap32LE(sizeof(BITMAPINFOHEADER));
919 bmi->biWidth = SDL_Swap32LE(icon->w);
920 bmi->biHeight = SDL_Swap32LE(icon->h * 2);
921 bmi->biPlanes = SDL_Swap16LE(1);
922 bmi->biBitCount = SDL_Swap16LE(32);
923 bmi->biCompression = SDL_Swap32LE(BI_RGB);
924 bmi->biSizeImage = SDL_Swap32LE(icon->h * icon->w * sizeof(Uint32));
925 bmi->biXPelsPerMeter = SDL_Swap32LE(0);
926 bmi->biYPelsPerMeter = SDL_Swap32LE(0);
927 bmi->biClrUsed = SDL_Swap32LE(0);
928 bmi->biClrImportant = SDL_Swap32LE(0);
929
930 // Write the pixels upside down into the bitmap buffer
931 SDL_assert(icon->format == SDL_PIXELFORMAT_ARGB8888);
932 dst = &icon_bmp[sizeof(BITMAPINFOHEADER)];
933 row_len = icon->w * sizeof(Uint32);
934 y = icon->h;
935 while (y--) {
936 Uint8 *src = (Uint8 *)icon->pixels + y * icon->pitch;
937 SDL_memcpy(dst, src, row_len);
938 dst += row_len;
939 }
940
941 // Write the mask
942 SDL_memset(icon_bmp + icon_len - mask_len, 0xFF, mask_len);
943
944 hicon = CreateIconFromResource(icon_bmp, icon_len, TRUE, 0x00030000);
945
946 SDL_small_free(icon_bmp, isstack);
947
948 if (!hicon) {
949 result = SDL_SetError("SetWindowIcon() failed, error %08X", (unsigned int)GetLastError());
950 }
951
952 // Set the icon for the window
953 SendMessage(hwnd, WM_SETICON, ICON_SMALL, (LPARAM)hicon);
954
955 // Set the icon in the task manager (should we do this?)
956 SendMessage(hwnd, WM_SETICON, ICON_BIG, (LPARAM)hicon);
957 return result;
958#else
959 return SDL_Unsupported();
960#endif
961}
962
963bool WIN_SetWindowPosition(SDL_VideoDevice *_this, SDL_Window *window)
964{
965 /* HighDPI support: removed SWP_NOSIZE. If the move results in a DPI change, we need to allow
966 * the window to resize (e.g. AdjustWindowRectExForDpi frame sizes are different).
967 */
968 if (!(window->flags & SDL_WINDOW_FULLSCREEN)) {
969 if (!(window->flags & (SDL_WINDOW_MAXIMIZED | SDL_WINDOW_MINIMIZED))) {
970 WIN_ConstrainPopup(window, true);
971 return WIN_SetWindowPositionInternal(window,
972 window->internal->copybits_flag | SWP_NOZORDER | SWP_NOOWNERZORDER | SWP_NOSIZE |
973 SWP_NOACTIVATE, SDL_WINDOWRECT_PENDING);
974 }
975 } else {
976 return SDL_UpdateFullscreenMode(window, SDL_FULLSCREEN_OP_ENTER, true);
977 }
978
979 return true;
980}
981
982void WIN_SetWindowSize(SDL_VideoDevice *_this, SDL_Window *window)
983{
984 if (!(window->flags & (SDL_WINDOW_FULLSCREEN | SDL_WINDOW_MAXIMIZED))) {
985 WIN_SetWindowPositionInternal(window, window->internal->copybits_flag | SWP_NOMOVE | SWP_NOZORDER | SWP_NOOWNERZORDER | SWP_NOACTIVATE, SDL_WINDOWRECT_PENDING);
986 } else {
987 // Can't resize the window
988 window->last_size_pending = false;
989 }
990}
991
992bool WIN_GetWindowBordersSize(SDL_VideoDevice *_this, SDL_Window *window, int *top, int *left, int *bottom, int *right)
993{
994#if defined(SDL_PLATFORM_XBOXONE) || defined(SDL_PLATFORM_XBOXSERIES)
995 HWND hwnd = window->internal->hwnd;
996 RECT rcClient;
997
998 /* rcClient stores the size of the inner window, while rcWindow stores the outer size relative to the top-left
999 * screen position; so the top/left values of rcClient are always {0,0} and bottom/right are {height,width} */
1000 GetClientRect(hwnd, &rcClient);
1001
1002 *top = rcClient.top;
1003 *left = rcClient.left;
1004 *bottom = rcClient.bottom;
1005 *right = rcClient.right;
1006
1007 return true;
1008#else // !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
1009 HWND hwnd = window->internal->hwnd;
1010 RECT rcClient, rcWindow;
1011 POINT ptDiff;
1012
1013 /* rcClient stores the size of the inner window, while rcWindow stores the outer size relative to the top-left
1014 * screen position; so the top/left values of rcClient are always {0,0} and bottom/right are {height,width} */
1015 if (!GetClientRect(hwnd, &rcClient)) {
1016 return SDL_SetError("GetClientRect() failed, error %08X", (unsigned int)GetLastError());
1017 }
1018
1019 if (!GetWindowRect(hwnd, &rcWindow)) {
1020 return SDL_SetError("GetWindowRect() failed, error %08X", (unsigned int)GetLastError());
1021 }
1022
1023 /* convert the top/left values to make them relative to
1024 * the window; they will end up being slightly negative */
1025 ptDiff.y = rcWindow.top;
1026 ptDiff.x = rcWindow.left;
1027
1028 if (!ScreenToClient(hwnd, &ptDiff)) {
1029 return SDL_SetError("ScreenToClient() failed, error %08X", (unsigned int)GetLastError());
1030 }
1031
1032 rcWindow.top = ptDiff.y;
1033 rcWindow.left = ptDiff.x;
1034
1035 /* convert the bottom/right values to make them relative to the window,
1036 * these will be slightly bigger than the inner width/height */
1037 ptDiff.y = rcWindow.bottom;
1038 ptDiff.x = rcWindow.right;
1039
1040 if (!ScreenToClient(hwnd, &ptDiff)) {
1041 return SDL_SetError("ScreenToClient() failed, error %08X", (unsigned int)GetLastError());
1042 }
1043
1044 rcWindow.bottom = ptDiff.y;
1045 rcWindow.right = ptDiff.x;
1046
1047 /* Now that both the inner and outer rects use the same coordinate system we can subtract them to get the border size.
1048 * Keep in mind that the top/left coordinates of rcWindow are negative because the border lies slightly before {0,0},
1049 * so switch them around because SDL3 wants them in positive. */
1050 *top = rcClient.top - rcWindow.top;
1051 *left = rcClient.left - rcWindow.left;
1052 *bottom = rcWindow.bottom - rcClient.bottom;
1053 *right = rcWindow.right - rcClient.right;
1054
1055 return true;
1056#endif // !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
1057}
1058
1059void WIN_GetWindowSizeInPixels(SDL_VideoDevice *_this, SDL_Window *window, int *w, int *h)
1060{
1061 const SDL_WindowData *data = window->internal;
1062 HWND hwnd = data->hwnd;
1063 RECT rect;
1064
1065 if (GetClientRect(hwnd, &rect) && !WIN_IsRectEmpty(&rect)) {
1066 *w = rect.right;
1067 *h = rect.bottom;
1068 } else if (window->last_pixel_w && window->last_pixel_h) {
1069 *w = window->last_pixel_w;
1070 *h = window->last_pixel_h;
1071 } else {
1072 // Probably created minimized, use the restored size
1073 *w = window->floating.w;
1074 *h = window->floating.h;
1075 }
1076}
1077
1078void WIN_ShowWindow(SDL_VideoDevice *_this, SDL_Window *window)
1079{
1080 DWORD style;
1081 HWND hwnd;
1082
1083 bool bActivate = SDL_GetHintBoolean(SDL_HINT_WINDOW_ACTIVATE_WHEN_SHOWN, true);
1084
1085 if (SDL_WINDOW_IS_POPUP(window)) {
1086 // Update our position in case our parent moved while we were hidden
1087 WIN_SetWindowPosition(_this, window);
1088 }
1089
1090 hwnd = window->internal->hwnd;
1091 style = GetWindowLong(hwnd, GWL_EXSTYLE);
1092 if (style & WS_EX_NOACTIVATE) {
1093 bActivate = false;
1094 }
1095 if (bActivate) {
1096 ShowWindow(hwnd, SW_SHOW);
1097 } else {
1098 // Use SetWindowPos instead of ShowWindow to avoid activating the parent window if this is a child window
1099 SetWindowPos(hwnd, NULL, 0, 0, 0, 0, window->internal->copybits_flag | SWP_SHOWWINDOW | SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOOWNERZORDER);
1100 }
1101
1102 if (window->flags & SDL_WINDOW_POPUP_MENU && bActivate) {
1103 WIN_SetKeyboardFocus(window, window->parent == SDL_GetKeyboardFocus());
1104 }
1105 if (window->flags & SDL_WINDOW_MODAL) {
1106 WIN_SetWindowModal(_this, window, true);
1107 }
1108}
1109
1110void WIN_HideWindow(SDL_VideoDevice *_this, SDL_Window *window)
1111{
1112 HWND hwnd = window->internal->hwnd;
1113
1114 if (window->flags & SDL_WINDOW_MODAL) {
1115 WIN_SetWindowModal(_this, window, false);
1116 }
1117
1118 ShowWindow(hwnd, SW_HIDE);
1119
1120 // Transfer keyboard focus back to the parent
1121 if (window->flags & SDL_WINDOW_POPUP_MENU) {
1122 SDL_Window *new_focus = window->parent;
1123 bool set_focus = window == SDL_GetKeyboardFocus();
1124
1125 // Find the highest level window, up to the toplevel parent, that isn't being hidden or destroyed.
1126 while (SDL_WINDOW_IS_POPUP(new_focus) && (new_focus->is_hiding || new_focus->is_destroying)) {
1127 new_focus = new_focus->parent;
1128
1129 // If some window in the chain currently had keyboard focus, set it to the new lowest-level window.
1130 if (!set_focus) {
1131 set_focus = new_focus == SDL_GetKeyboardFocus();
1132 }
1133 }
1134
1135 WIN_SetKeyboardFocus(new_focus, set_focus);
1136 }
1137}
1138
1139void WIN_RaiseWindow(SDL_VideoDevice *_this, SDL_Window *window)
1140{
1141#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
1142 /* If desired, raise the window more forcefully.
1143 * Technique taken from http://stackoverflow.com/questions/916259/ .
1144 * Specifically, http://stackoverflow.com/a/34414846 .
1145 *
1146 * The issue is that Microsoft has gone through a lot of trouble to make it
1147 * nearly impossible to programmatically move a window to the foreground,
1148 * for "security" reasons. Apparently, the following song-and-dance gets
1149 * around their objections. */
1150 bool bForce = SDL_GetHintBoolean(SDL_HINT_FORCE_RAISEWINDOW, false);
1151 bool bActivate = SDL_GetHintBoolean(SDL_HINT_WINDOW_ACTIVATE_WHEN_RAISED, true);
1152
1153 HWND hCurWnd = NULL;
1154 DWORD dwMyID = 0u;
1155 DWORD dwCurID = 0u;
1156
1157 SDL_WindowData *data = window->internal;
1158 HWND hwnd = data->hwnd;
1159 if (bForce) {
1160 hCurWnd = GetForegroundWindow();
1161 dwMyID = GetCurrentThreadId();
1162 dwCurID = GetWindowThreadProcessId(hCurWnd, NULL);
1163 ShowWindow(hwnd, SW_RESTORE);
1164 AttachThreadInput(dwCurID, dwMyID, TRUE);
1165 SetWindowPos(hwnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE);
1166 if (!SDL_ShouldAllowTopmost() || !(window->flags & SDL_WINDOW_ALWAYS_ON_TOP)) {
1167 SetWindowPos(hwnd, HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE);
1168 }
1169 }
1170 if (bActivate) {
1171 SetForegroundWindow(hwnd);
1172 if (window->flags & SDL_WINDOW_POPUP_MENU) {
1173 WIN_SetKeyboardFocus(window, window->parent == SDL_GetKeyboardFocus());
1174 }
1175 } else {
1176 SetWindowPos(hwnd, HWND_TOP, 0, 0, 0, 0, data->copybits_flag | SWP_NOMOVE | SWP_NOSIZE | SWP_NOOWNERZORDER | SWP_NOACTIVATE);
1177 }
1178 if (bForce) {
1179 AttachThreadInput(dwCurID, dwMyID, FALSE);
1180 SetFocus(hwnd);
1181 SetActiveWindow(hwnd);
1182 }
1183#endif // !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
1184}
1185
1186void WIN_MaximizeWindow(SDL_VideoDevice *_this, SDL_Window *window)
1187{
1188 SDL_WindowData *data = window->internal;
1189
1190 if (!(window->flags & SDL_WINDOW_FULLSCREEN)) {
1191 HWND hwnd = data->hwnd;
1192 data->expected_resize = true;
1193 ShowWindow(hwnd, SW_MAXIMIZE);
1194 data->expected_resize = false;
1195
1196 /* Clamp the maximized window size to the max window size.
1197 * This is automatic if maximizing from the window controls.
1198 */
1199 if (window->max_w || window->max_h) {
1200 int fx, fy, fw, fh;
1201
1202 window->windowed.w = window->max_w ? SDL_min(window->w, window->max_w) : window->windowed.w;
1203 window->windowed.h = window->max_h ? SDL_min(window->h, window->max_h) : window->windowed.h;
1204 WIN_AdjustWindowRect(window, &fx, &fy, &fw, &fh, SDL_WINDOWRECT_WINDOWED);
1205
1206 data->expected_resize = true;
1207 SetWindowPos(hwnd, HWND_TOP, fx, fy, fw, fh, data->copybits_flag | SWP_NOMOVE | SWP_NOOWNERZORDER | SWP_NOACTIVATE);
1208 data->expected_resize = false;
1209 }
1210 } else {
1211 data->windowed_mode_was_maximized = true;
1212 }
1213}
1214
1215void WIN_MinimizeWindow(SDL_VideoDevice *_this, SDL_Window *window)
1216{
1217 HWND hwnd = window->internal->hwnd;
1218 ShowWindow(hwnd, SW_MINIMIZE);
1219}
1220
1221void WIN_SetWindowBordered(SDL_VideoDevice *_this, SDL_Window *window, bool bordered)
1222{
1223 SDL_WindowData *data = window->internal;
1224 HWND hwnd = data->hwnd;
1225 DWORD style;
1226
1227 style = GetWindowLong(hwnd, GWL_STYLE);
1228 style &= ~STYLE_MASK;
1229 style |= GetWindowStyle(window);
1230
1231 data->in_border_change = true;
1232 SetWindowLong(hwnd, GWL_STYLE, style);
1233 WIN_SetWindowPositionInternal(window, data->copybits_flag | SWP_FRAMECHANGED | SWP_NOZORDER | SWP_NOOWNERZORDER | SWP_NOACTIVATE, SDL_WINDOWRECT_CURRENT);
1234 data->in_border_change = false;
1235}
1236
1237void WIN_SetWindowResizable(SDL_VideoDevice *_this, SDL_Window *window, bool resizable)
1238{
1239 SDL_WindowData *data = window->internal;
1240 HWND hwnd = data->hwnd;
1241 DWORD style;
1242
1243 style = GetWindowLong(hwnd, GWL_STYLE);
1244 style &= ~STYLE_MASK;
1245 style |= GetWindowStyle(window);
1246
1247 SetWindowLong(hwnd, GWL_STYLE, style);
1248}
1249
1250void WIN_SetWindowAlwaysOnTop(SDL_VideoDevice *_this, SDL_Window *window, bool on_top)
1251{
1252 WIN_SetWindowPositionInternal(window, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE, SDL_WINDOWRECT_CURRENT);
1253}
1254
1255void WIN_RestoreWindow(SDL_VideoDevice *_this, SDL_Window *window)
1256{
1257 SDL_WindowData *data = window->internal;
1258 if (!(window->flags & SDL_WINDOW_FULLSCREEN)) {
1259 HWND hwnd = data->hwnd;
1260 data->expected_resize = true;
1261 ShowWindow(hwnd, SW_RESTORE);
1262 data->expected_resize = false;
1263 } else {
1264 data->windowed_mode_was_maximized = false;
1265 }
1266}
1267
1268static DWM_WINDOW_CORNER_PREFERENCE WIN_UpdateCornerRoundingForHWND(HWND hwnd, DWM_WINDOW_CORNER_PREFERENCE cornerPref)
1269{
1270 DWM_WINDOW_CORNER_PREFERENCE oldPref = DWMWCP_DEFAULT;
1271
1272 SDL_SharedObject *handle = SDL_LoadObject("dwmapi.dll");
1273 if (handle) {
1274 DwmGetWindowAttribute_t DwmGetWindowAttributeFunc = (DwmGetWindowAttribute_t)SDL_LoadFunction(handle, "DwmGetWindowAttribute");
1275 DwmSetWindowAttribute_t DwmSetWindowAttributeFunc = (DwmSetWindowAttribute_t)SDL_LoadFunction(handle, "DwmSetWindowAttribute");
1276 if (DwmGetWindowAttributeFunc && DwmSetWindowAttributeFunc) {
1277 DwmGetWindowAttributeFunc(hwnd, DWMWA_WINDOW_CORNER_PREFERENCE, &oldPref, sizeof(oldPref));
1278 DwmSetWindowAttributeFunc(hwnd, DWMWA_WINDOW_CORNER_PREFERENCE, &cornerPref, sizeof(cornerPref));
1279 }
1280
1281 SDL_UnloadObject(handle);
1282 }
1283
1284 return oldPref;
1285}
1286
1287static COLORREF WIN_UpdateBorderColorForHWND(HWND hwnd, COLORREF colorRef)
1288{
1289 COLORREF oldPref = DWMWA_COLOR_DEFAULT;
1290
1291 SDL_SharedObject *handle = SDL_LoadObject("dwmapi.dll");
1292 if (handle) {
1293 DwmGetWindowAttribute_t DwmGetWindowAttributeFunc = (DwmGetWindowAttribute_t)SDL_LoadFunction(handle, "DwmGetWindowAttribute");
1294 DwmSetWindowAttribute_t DwmSetWindowAttributeFunc = (DwmSetWindowAttribute_t)SDL_LoadFunction(handle, "DwmSetWindowAttribute");
1295 if (DwmGetWindowAttributeFunc && DwmSetWindowAttributeFunc) {
1296 DwmGetWindowAttributeFunc(hwnd, DWMWA_BORDER_COLOR, &oldPref, sizeof(oldPref));
1297 DwmSetWindowAttributeFunc(hwnd, DWMWA_BORDER_COLOR, &colorRef, sizeof(colorRef));
1298 }
1299
1300 SDL_UnloadObject(handle);
1301 }
1302
1303 return oldPref;
1304}
1305
1306/**
1307 * Reconfigures the window to fill the given display, if fullscreen is true, otherwise restores the window.
1308 */
1309SDL_FullscreenResult WIN_SetWindowFullscreen(SDL_VideoDevice *_this, SDL_Window *window, SDL_VideoDisplay *display, SDL_FullscreenOp fullscreen)
1310{
1311#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
1312 SDL_DisplayData *displaydata = display->internal;
1313 SDL_WindowData *data = window->internal;
1314 HWND hwnd = data->hwnd;
1315 MONITORINFO minfo;
1316 DWORD style, styleEx;
1317 HWND top;
1318 int x, y;
1319 int w, h;
1320 bool enterMaximized = false;
1321
1322#ifdef HIGHDPI_DEBUG
1323 SDL_Log("WIN_SetWindowFullscreen: %d", (int)fullscreen);
1324#endif
1325
1326 /* Early out if already not in fullscreen, or the styling on
1327 * external windows may end up being overridden.
1328 */
1329 if (!(window->flags & SDL_WINDOW_FULLSCREEN) && !fullscreen) {
1330 return SDL_FULLSCREEN_SUCCEEDED;
1331 }
1332
1333 if (SDL_ShouldAllowTopmost() && (window->flags & SDL_WINDOW_ALWAYS_ON_TOP)) {
1334 top = HWND_TOPMOST;
1335 } else {
1336 top = HWND_NOTOPMOST;
1337 }
1338
1339 /* Use GetMonitorInfo instead of WIN_GetDisplayBounds because we want the
1340 monitor bounds in Windows coordinates (pixels) rather than SDL coordinates (points). */
1341 SDL_zero(minfo);
1342 minfo.cbSize = sizeof(MONITORINFO);
1343 if (!GetMonitorInfo(displaydata->MonitorHandle, &minfo)) {
1344 SDL_SetError("GetMonitorInfo failed");
1345 return SDL_FULLSCREEN_FAILED;
1346 }
1347
1348 SDL_SendWindowEvent(window, fullscreen ? SDL_EVENT_WINDOW_ENTER_FULLSCREEN : SDL_EVENT_WINDOW_LEAVE_FULLSCREEN, 0, 0);
1349 style = GetWindowLong(hwnd, GWL_STYLE);
1350 style &= ~STYLE_MASK;
1351 style |= GetWindowStyle(window);
1352 styleEx = GetWindowLong(hwnd, GWL_EXSTYLE);
1353
1354 if (fullscreen) {
1355 x = minfo.rcMonitor.left;
1356 y = minfo.rcMonitor.top;
1357 w = minfo.rcMonitor.right - minfo.rcMonitor.left;
1358 h = minfo.rcMonitor.bottom - minfo.rcMonitor.top;
1359
1360 /* Unset the maximized flag. This fixes
1361 https://bugzilla.libsdl.org/show_bug.cgi?id=3215
1362 */
1363 if (style & WS_MAXIMIZE) {
1364 data->windowed_mode_was_maximized = true;
1365 style &= ~WS_MAXIMIZE;
1366 }
1367
1368 // Disable corner rounding & border color (Windows 11+) so the window fills the full screen
1369 data->windowed_mode_corner_rounding = WIN_UpdateCornerRoundingForHWND(hwnd, DWMWCP_DONOTROUND);
1370 data->dwma_border_color = WIN_UpdateBorderColorForHWND(hwnd, DWMWA_COLOR_NONE);
1371 } else {
1372 BOOL menu;
1373
1374 WIN_UpdateCornerRoundingForHWND(hwnd, (DWM_WINDOW_CORNER_PREFERENCE)data->windowed_mode_corner_rounding);
1375 WIN_UpdateBorderColorForHWND(hwnd, data->dwma_border_color);
1376
1377 /* Restore window-maximization state, as applicable.
1378 Special care is taken to *not* do this if and when we're
1379 alt-tab'ing away (to some other window; as indicated by
1380 in_window_deactivation), otherwise
1381 https://bugzilla.libsdl.org/show_bug.cgi?id=3215 can reproduce!
1382 */
1383 if (data->windowed_mode_was_maximized && !data->in_window_deactivation) {
1384 enterMaximized = true;
1385 data->disable_move_size_events = true;
1386 }
1387
1388 menu = (style & WS_CHILDWINDOW) ? FALSE : (GetMenu(hwnd) != NULL);
1389 WIN_AdjustWindowRectWithStyle(window, style, styleEx, menu,
1390 &x, &y,
1391 &w, &h,
1392 SDL_WINDOWRECT_FLOATING);
1393 data->windowed_mode_was_maximized = false;
1394
1395 /* A window may have been maximized by dragging it to the top of another display, in which case the floating
1396 * position may be out-of-date. If the window is being restored to maximized, and the maximized and floating
1397 * position are on different displays, try to center the window on the maximized display for restoration, which
1398 * mimics native Windows behavior.
1399 */
1400 if (enterMaximized) {
1401 const SDL_Point windowed_point = { window->windowed.x, window->windowed.y };
1402 const SDL_Point floating_point = { window->floating.x, window->floating.y };
1403 const SDL_DisplayID floating_display = SDL_GetDisplayForPoint(&floating_point);
1404 const SDL_DisplayID windowed_display = SDL_GetDisplayForPoint(&windowed_point);
1405
1406 if (floating_display != windowed_display) {
1407 SDL_Rect bounds;
1408
1409 SDL_zero(bounds);
1410 SDL_GetDisplayUsableBounds(windowed_display, &bounds);
1411 if (w < bounds.w) {
1412 x = bounds.x + (bounds.w - w) / 2;
1413 } else {
1414 x = bounds.x;
1415 }
1416 if (h < bounds.h) {
1417 y = bounds.y + (bounds.h - h) / 2;
1418 } else {
1419 y = bounds.y;
1420 }
1421 }
1422 }
1423 }
1424
1425 /* Always reset the window to the base floating size before possibly re-applying the maximized state,
1426 * otherwise, the base floating size can seemingly be lost in some cases.
1427 */
1428 SetWindowLong(hwnd, GWL_STYLE, style);
1429 data->expected_resize = true;
1430 SetWindowPos(hwnd, top, x, y, w, h, data->copybits_flag | SWP_NOACTIVATE);
1431 data->expected_resize = false;
1432 data->disable_move_size_events = false;
1433
1434 if (enterMaximized) {
1435 WIN_MaximizeWindow(_this, window);
1436 }
1437
1438#ifdef HIGHDPI_DEBUG
1439 SDL_Log("WIN_SetWindowFullscreen: %d finished. Set window to %d,%d, %dx%d", (int)fullscreen, x, y, w, h);
1440#endif
1441
1442#endif // !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
1443 return SDL_FULLSCREEN_SUCCEEDED;
1444}
1445
1446#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
1447void WIN_UpdateWindowICCProfile(SDL_Window *window, bool send_event)
1448{
1449 SDL_WindowData *data = window->internal;
1450 SDL_DisplayData *displaydata = SDL_GetDisplayDriverDataForWindow(window);
1451
1452 if (displaydata) {
1453 HDC hdc = CreateDCW(displaydata->DeviceName, NULL, NULL, NULL);
1454 if (hdc) {
1455 WCHAR fileName[MAX_PATH];
1456 DWORD fileNameSize = SDL_arraysize(fileName);
1457 if (GetICMProfileW(hdc, &fileNameSize, fileName)) {
1458 // fileNameSize includes '\0' on return
1459 if (!data->ICMFileName ||
1460 SDL_wcscmp(data->ICMFileName, fileName) != 0) {
1461 if (data->ICMFileName) {
1462 SDL_free(data->ICMFileName);
1463 }
1464 data->ICMFileName = SDL_wcsdup(fileName);
1465 if (send_event) {
1466 SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_ICCPROF_CHANGED, 0, 0);
1467 }
1468 }
1469 }
1470 DeleteDC(hdc);
1471 }
1472 }
1473}
1474
1475void *WIN_GetWindowICCProfile(SDL_VideoDevice *_this, SDL_Window *window, size_t *size)
1476{
1477 SDL_WindowData *data = window->internal;
1478 char *filename_utf8;
1479 void *iccProfileData = NULL;
1480
1481 filename_utf8 = WIN_StringToUTF8(data->ICMFileName);
1482 if (filename_utf8) {
1483 iccProfileData = SDL_LoadFile(filename_utf8, size);
1484 if (!iccProfileData) {
1485 SDL_SetError("Could not open ICC profile");
1486 }
1487 SDL_free(filename_utf8);
1488 }
1489 return iccProfileData;
1490}
1491
1492static void WIN_GrabKeyboard(SDL_Window *window)
1493{
1494 SDL_WindowData *data = window->internal;
1495 HMODULE module;
1496
1497 if (data->keyboard_hook) {
1498 return;
1499 }
1500
1501 /* SetWindowsHookEx() needs to know which module contains the hook we
1502 want to install. This is complicated by the fact that SDL can be
1503 linked statically or dynamically. Fortunately XP and later provide
1504 this nice API that will go through the loaded modules and find the
1505 one containing our code.
1506 */
1507 if (!GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT | GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS,
1508 (LPTSTR)WIN_KeyboardHookProc,
1509 &module)) {
1510 return;
1511 }
1512
1513 // Capture a snapshot of the current keyboard state before the hook
1514 if (!GetKeyboardState(data->videodata->pre_hook_key_state)) {
1515 return;
1516 }
1517
1518 /* To grab the keyboard, we have to install a low-level keyboard hook to
1519 intercept keys that would normally be captured by the OS. Intercepting
1520 all key events on the system is rather invasive, but it's what Microsoft
1521 actually documents that you do to capture these.
1522 */
1523 data->keyboard_hook = SetWindowsHookEx(WH_KEYBOARD_LL, WIN_KeyboardHookProc, module, 0);
1524}
1525
1526void WIN_UngrabKeyboard(SDL_Window *window)
1527{
1528 SDL_WindowData *data = window->internal;
1529
1530 if (data->keyboard_hook) {
1531 UnhookWindowsHookEx(data->keyboard_hook);
1532 data->keyboard_hook = NULL;
1533 }
1534}
1535
1536bool WIN_SetWindowMouseRect(SDL_VideoDevice *_this, SDL_Window *window)
1537{
1538 WIN_UpdateClipCursor(window);
1539 return true;
1540}
1541
1542bool WIN_SetWindowMouseGrab(SDL_VideoDevice *_this, SDL_Window *window, bool grabbed)
1543{
1544 WIN_UpdateClipCursor(window);
1545 return true;
1546}
1547
1548bool WIN_SetWindowKeyboardGrab(SDL_VideoDevice *_this, SDL_Window *window, bool grabbed)
1549{
1550 if (grabbed) {
1551 WIN_GrabKeyboard(window);
1552 } else {
1553 WIN_UngrabKeyboard(window);
1554 }
1555
1556 return true;
1557}
1558#endif // !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
1559
1560void WIN_DestroyWindow(SDL_VideoDevice *_this, SDL_Window *window)
1561{
1562 CleanupWindowData(_this, window);
1563}
1564
1565/*
1566 * Creates a HelperWindow used for DirectInput.
1567 */
1568bool SDL_HelperWindowCreate(void)
1569{
1570 HINSTANCE hInstance = GetModuleHandle(NULL);
1571 WNDCLASS wce;
1572
1573 // Make sure window isn't created twice.
1574 if (SDL_HelperWindow != NULL) {
1575 return true;
1576 }
1577
1578 // Create the class.
1579 SDL_zero(wce);
1580 wce.lpfnWndProc = DefWindowProc;
1581 wce.lpszClassName = SDL_HelperWindowClassName;
1582 wce.hInstance = hInstance;
1583
1584 // Register the class.
1585 SDL_HelperWindowClass = RegisterClass(&wce);
1586 if (SDL_HelperWindowClass == 0 && GetLastError() != ERROR_CLASS_ALREADY_EXISTS) {
1587 return WIN_SetError("Unable to create Helper Window Class");
1588 }
1589
1590 // Create the window.
1591 SDL_HelperWindow = CreateWindowEx(0, SDL_HelperWindowClassName,
1592 SDL_HelperWindowName,
1593 WS_OVERLAPPED, CW_USEDEFAULT,
1594 CW_USEDEFAULT, CW_USEDEFAULT,
1595 CW_USEDEFAULT, HWND_MESSAGE, NULL,
1596 hInstance, NULL);
1597 if (!SDL_HelperWindow) {
1598 UnregisterClass(SDL_HelperWindowClassName, hInstance);
1599 return WIN_SetError("Unable to create Helper Window");
1600 }
1601
1602 return true;
1603}
1604
1605/*
1606 * Destroys the HelperWindow previously created with SDL_HelperWindowCreate.
1607 */
1608void SDL_HelperWindowDestroy(void)
1609{
1610 HINSTANCE hInstance = GetModuleHandle(NULL);
1611
1612 // Destroy the window.
1613 if (SDL_HelperWindow != NULL) {
1614 if (DestroyWindow(SDL_HelperWindow) == 0) {
1615 WIN_SetError("Unable to destroy Helper Window");
1616 return;
1617 }
1618 SDL_HelperWindow = NULL;
1619 }
1620
1621 // Unregister the class.
1622 if (SDL_HelperWindowClass != 0) {
1623 if ((UnregisterClass(SDL_HelperWindowClassName, hInstance)) == 0) {
1624 WIN_SetError("Unable to destroy Helper Window Class");
1625 return;
1626 }
1627 SDL_HelperWindowClass = 0;
1628 }
1629}
1630
1631#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
1632void WIN_OnWindowEnter(SDL_VideoDevice *_this, SDL_Window *window)
1633{
1634 SDL_WindowData *data = window->internal;
1635
1636 if (!data || !data->hwnd) {
1637 // The window wasn't fully initialized
1638 return;
1639 }
1640
1641 if (window->flags & SDL_WINDOW_ALWAYS_ON_TOP) {
1642 WIN_SetWindowPositionInternal(window, data->copybits_flag | SWP_NOSIZE | SWP_NOACTIVATE, SDL_WINDOWRECT_CURRENT);
1643 }
1644}
1645
1646static BOOL GetClientScreenRect(HWND hwnd, RECT *rect)
1647{
1648 return GetClientRect(hwnd, rect) && // RECT( left , top , right , bottom )
1649 ClientToScreen(hwnd, (LPPOINT)rect) && // POINT( left , top )
1650 ClientToScreen(hwnd, (LPPOINT)rect + 1); // POINT( right , bottom )
1651}
1652
1653void WIN_UnclipCursorForWindow(SDL_Window *window) {
1654 SDL_WindowData *data = window->internal;
1655 RECT rect;
1656 if (GetClipCursor(&rect) && SDL_memcmp(&rect, &data->cursor_clipped_rect, sizeof(rect)) == 0) {
1657 ClipCursor(NULL);
1658 SDL_zero(data->cursor_clipped_rect);
1659 }
1660}
1661
1662void WIN_UpdateClipCursor(SDL_Window *window)
1663{
1664 SDL_WindowData *data = window->internal;
1665 if (data->in_title_click || data->focus_click_pending || data->skip_update_clipcursor) {
1666 return;
1667 }
1668
1669 SDL_Rect mouse_rect = window->mouse_rect;
1670 bool win_mouse_rect = (mouse_rect.w > 0 && mouse_rect.h > 0);
1671 bool win_have_focus = (window->flags & SDL_WINDOW_INPUT_FOCUS);
1672 bool win_is_grabbed = (window->flags & SDL_WINDOW_MOUSE_GRABBED);
1673 bool win_in_relmode = (window->flags & SDL_WINDOW_MOUSE_RELATIVE_MODE);
1674 bool cursor_confine = win_in_relmode || win_is_grabbed || win_mouse_rect;
1675
1676 // This is verbatim translation of the old logic,
1677 // but I don't quite get what it's trying to do.
1678 // A clean-room implementation according to MSDN
1679 // documentation of GetClipCursor is provided in
1680 // a commented-out block below.
1681 if (!win_have_focus || !cursor_confine) {
1682 SDL_VideoDevice *videodevice = SDL_GetVideoDevice();
1683 RECT current;
1684 if (!GetClipCursor(&current)) {
1685 return;
1686 }
1687 if (videodevice && (
1688 current.left != videodevice->desktop_bounds.x ||
1689 current.top != videodevice->desktop_bounds.y
1690 )) {
1691 POINT first, second;
1692 first.x = current.left;
1693 first.y = current.top;
1694 second.x = current.right - 1;
1695 second.y = current.bottom - 1;
1696 if (!PtInRect(&data->cursor_clipped_rect, first) ||
1697 !PtInRect(&data->cursor_clipped_rect, second)) {
1698 return;
1699 }
1700 }
1701 ClipCursor(NULL);
1702 SDL_zero(data->cursor_clipped_rect);
1703 return;
1704 }
1705
1706 // if (!win_have_focus || !cursor_confine) {
1707 // RECT current;
1708 // SDL_VideoDevice *videodevice = SDL_GetVideoDevice();
1709 // if (GetClipCursor(&current) && (!videodevice ||
1710 // current.left != videodevice->desktop_bounds.x ||
1711 // current.top != videodevice->desktop_bounds.y ||
1712 // current.right != videodevice->desktop_bounds.x + videodevice->desktop_bounds.w ||
1713 // current.bottom != videodevice->desktop_bounds.y + videodevice->desktop_bounds.h )) {
1714 // ClipCursor(NULL);
1715 // SDL_zero(data->cursor_clipped_rect);
1716 // }
1717 // return;
1718 // }
1719
1720 SDL_Mouse *mouse = SDL_GetMouse();
1721 bool lock_to_ctr = (mouse->relative_mode && mouse->relative_mode_center);
1722
1723 RECT client;
1724 if (!GetClientScreenRect(data->hwnd, &client)) {
1725 return;
1726 }
1727
1728 RECT target = client;
1729 if (lock_to_ctr) {
1730 LONG cx = (client.left + client.right ) / 2;
1731 LONG cy = (client.top + client.bottom) / 2;
1732 target = data->cursor_ctrlock_rect;
1733 target.left += cx;
1734 target.right += cx;
1735 target.top += cy;
1736 target.bottom += cy;
1737 } else if (win_mouse_rect) {
1738 RECT custom, overlap;
1739 custom.left = client.left + mouse_rect.x;
1740 custom.top = client.top + mouse_rect.y;
1741 custom.right = client.left + mouse_rect.x + mouse_rect.w;
1742 custom.bottom = client.top + mouse_rect.y + mouse_rect.h;
1743 if (IntersectRect(&overlap, &client, &custom)) {
1744 target = overlap;
1745 } else if (!win_is_grabbed) {
1746 WIN_UnclipCursorForWindow(window);
1747 return;
1748 }
1749 }
1750
1751 if (GetClipCursor(&client) &&
1752 0 != SDL_memcmp(&target, &client, sizeof(client)) &&
1753 ClipCursor(&target)) {
1754 data->cursor_clipped_rect = target; // ClipCursor may fail if rect beyond screen
1755 }
1756}
1757
1758bool WIN_SetWindowHitTest(SDL_Window *window, bool enabled)
1759{
1760 return true; // just succeed, the real work is done elsewhere.
1761}
1762#endif // !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
1763
1764bool WIN_SetWindowOpacity(SDL_VideoDevice *_this, SDL_Window *window, float opacity)
1765{
1766#if defined(SDL_PLATFORM_XBOXONE) || defined(SDL_PLATFORM_XBOXSERIES)
1767 return false;
1768#else
1769 const SDL_WindowData *data = window->internal;
1770 HWND hwnd = data->hwnd;
1771 const LONG style = GetWindowLong(hwnd, GWL_EXSTYLE);
1772
1773 SDL_assert(style != 0);
1774
1775 if (opacity == 1.0f) {
1776 // want it fully opaque, just mark it unlayered if necessary.
1777 if (style & WS_EX_LAYERED) {
1778 if (SetWindowLong(hwnd, GWL_EXSTYLE, style & ~WS_EX_LAYERED) == 0) {
1779 return WIN_SetError("SetWindowLong()");
1780 }
1781 }
1782 } else {
1783 const BYTE alpha = (BYTE)((int)(opacity * 255.0f));
1784 // want it transparent, mark it layered if necessary.
1785 if (!(style & WS_EX_LAYERED)) {
1786 if (SetWindowLong(hwnd, GWL_EXSTYLE, style | WS_EX_LAYERED) == 0) {
1787 return WIN_SetError("SetWindowLong()");
1788 }
1789 }
1790
1791 if (SetLayeredWindowAttributes(hwnd, 0, alpha, LWA_ALPHA) == 0) {
1792 return WIN_SetError("SetLayeredWindowAttributes()");
1793 }
1794 }
1795
1796 return true;
1797#endif // !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
1798}
1799
1800#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
1801
1802static const char *SDLGetClipboardFormatName(UINT cf, char *text, int len)
1803{
1804 switch (cf) {
1805 case CF_TEXT:
1806 return "CF_TEXT";
1807 case CF_BITMAP:
1808 return "CF_BITMAP";
1809 case CF_METAFILEPICT:
1810 return "CF_METAFILEPICT";
1811 case CF_SYLK:
1812 return "CF_SYLK";
1813 case CF_DIF:
1814 return "CF_DIF";
1815 case CF_TIFF:
1816 return "CF_TIFF";
1817 case CF_OEMTEXT:
1818 return "CF_OEMTEXT";
1819 case CF_DIB:
1820 return "CF_DIB";
1821 case CF_PALETTE:
1822 return "CF_PALETTE";
1823 case CF_PENDATA:
1824 return "CF_PENDATA";
1825 case CF_RIFF:
1826 return "CF_RIFF";
1827 case CF_WAVE:
1828 return "CF_WAVE";
1829 case CF_UNICODETEXT:
1830 return "CF_UNICODETEXT";
1831 case CF_ENHMETAFILE:
1832 return "CF_ENHMETAFILE";
1833 case CF_HDROP:
1834 return "CF_HDROP";
1835 case CF_LOCALE:
1836 return "CF_LOCALE";
1837 case CF_DIBV5:
1838 return "CF_DIBV5";
1839 case CF_OWNERDISPLAY:
1840 return "CF_OWNERDISPLAY";
1841 case CF_DSPTEXT:
1842 return "CF_DSPTEXT";
1843 case CF_DSPBITMAP:
1844 return "CF_DSPBITMAP";
1845 case CF_DSPMETAFILEPICT:
1846 return "CF_DSPMETAFILEPICT";
1847 case CF_DSPENHMETAFILE:
1848 return "CF_DSPENHMETAFILE";
1849 default:
1850 if (GetClipboardFormatNameA(cf, text, len)) {
1851 return text;
1852 } else {
1853 return NULL;
1854 }
1855 }
1856}
1857
1858static STDMETHODIMP_(ULONG) SDLDropTarget_AddRef(SDLDropTarget *target)
1859{
1860 return ++target->refcount;
1861}
1862
1863static STDMETHODIMP_(ULONG) SDLDropTarget_Release(SDLDropTarget *target)
1864{
1865 --target->refcount;
1866 if (target->refcount == 0) {
1867 SDL_free(target);
1868 return 0;
1869 }
1870 return target->refcount;
1871}
1872
1873static STDMETHODIMP SDLDropTarget_QueryInterface(SDLDropTarget *target, REFIID riid, PVOID *ppv)
1874{
1875 if (ppv == NULL) {
1876 return E_INVALIDARG;
1877 }
1878
1879 *ppv = NULL;
1880 if (WIN_IsEqualIID(riid, &IID_IUnknown) ||
1881 WIN_IsEqualIID(riid, &IID_IDropTarget)) {
1882 *ppv = (void *)target;
1883 }
1884 if (*ppv) {
1885 SDLDropTarget_AddRef(target);
1886 return S_OK;
1887 }
1888 return E_NOINTERFACE;
1889}
1890
1891static STDMETHODIMP SDLDropTarget_DragEnter(SDLDropTarget *target,
1892 IDataObject *pDataObject, DWORD grfKeyState,
1893 POINTL pt, DWORD *pdwEffect)
1894{
1895 SDL_LogTrace(SDL_LOG_CATEGORY_INPUT,
1896 ". In DragEnter at %ld, %ld", pt.x, pt.y);
1897 *pdwEffect = DROPEFFECT_COPY;
1898 POINT pnt = { pt.x, pt.y };
1899 if (ScreenToClient(target->hwnd, &pnt)) {
1900 SDL_LogTrace(SDL_LOG_CATEGORY_INPUT,
1901 ". In DragEnter at %ld, %ld => window %u at %ld, %ld", pt.x, pt.y, target->window->id, pnt.x, pnt.y);
1902 SDL_SendDropPosition(target->window, pnt.x, pnt.y);
1903 } else {
1904 SDL_LogTrace(SDL_LOG_CATEGORY_INPUT,
1905 ". In DragEnter at %ld, %ld => nil, nil", pt.x, pt.y);
1906 }
1907 return S_OK;
1908}
1909
1910static STDMETHODIMP SDLDropTarget_DragOver(SDLDropTarget *target,
1911 DWORD grfKeyState,
1912 POINTL pt, DWORD *pdwEffect)
1913{
1914 SDL_LogTrace(SDL_LOG_CATEGORY_INPUT,
1915 ". In DragOver at %ld, %ld", pt.x, pt.y);
1916 *pdwEffect = DROPEFFECT_COPY;
1917 POINT pnt = { pt.x, pt.y };
1918 if (ScreenToClient(target->hwnd, &pnt)) {
1919 SDL_LogTrace(SDL_LOG_CATEGORY_INPUT,
1920 ". In DragOver at %ld, %ld => window %u at %ld, %ld", pt.x, pt.y, target->window->id, pnt.x, pnt.y);
1921 SDL_SendDropPosition(target->window, pnt.x, pnt.y);
1922 } else {
1923 SDL_LogTrace(SDL_LOG_CATEGORY_INPUT,
1924 ". In DragOver at %ld, %ld => nil, nil", pt.x, pt.y);
1925 }
1926 return S_OK;
1927}
1928
1929static STDMETHODIMP SDLDropTarget_DragLeave(SDLDropTarget *target)
1930{
1931 SDL_LogTrace(SDL_LOG_CATEGORY_INPUT,
1932 ". In DragLeave");
1933 SDL_SendDropComplete(target->window);
1934 return S_OK;
1935}
1936
1937static STDMETHODIMP SDLDropTarget_Drop(SDLDropTarget *target,
1938 IDataObject *pDataObject, DWORD grfKeyState,
1939 POINTL pt, DWORD *pdwEffect)
1940{
1941 *pdwEffect = DROPEFFECT_COPY;
1942 POINT pnt = { pt.x, pt.y };
1943 if (ScreenToClient(target->hwnd, &pnt)) {
1944 SDL_LogTrace(SDL_LOG_CATEGORY_INPUT,
1945 ". In Drop at %ld, %ld => window %u at %ld, %ld", pt.x, pt.y, target->window->id, pnt.x, pnt.y);
1946 SDL_SendDropPosition(target->window, pnt.x, pnt.y);
1947 } else {
1948 SDL_LogTrace(SDL_LOG_CATEGORY_INPUT,
1949 ". In Drop at %ld, %ld => nil, nil", pt.x, pt.y);
1950 }
1951
1952 {
1953 IEnumFORMATETC *pEnumFormatEtc;
1954 HRESULT hres;
1955 hres = pDataObject->lpVtbl->EnumFormatEtc(pDataObject, DATADIR_GET, &pEnumFormatEtc);
1956 SDL_LogTrace(SDL_LOG_CATEGORY_INPUT,
1957 ". In Drop for EnumFormatEtc, HRESULT is %08lx", hres);
1958 if (hres == S_OK) {
1959 FORMATETC fetc;
1960 while (pEnumFormatEtc->lpVtbl->Next(pEnumFormatEtc, 1, &fetc, NULL) == S_OK) {
1961 char name[257] = { 0 };
1962 const char *cfnm = SDLGetClipboardFormatName(fetc.cfFormat, name, 256);
1963 if (cfnm) {
1964 SDL_LogTrace(SDL_LOG_CATEGORY_INPUT,
1965 ". In Drop, Supported format is %08x, '%s'", fetc.cfFormat, cfnm);
1966 } else {
1967 SDL_LogTrace(SDL_LOG_CATEGORY_INPUT,
1968 ". In Drop, Supported format is %08x, Predefined", fetc.cfFormat);
1969 }
1970 }
1971 }
1972 }
1973
1974 {
1975 FORMATETC fetc;
1976 fetc.cfFormat = target->format_file;
1977 fetc.ptd = NULL;
1978 fetc.dwAspect = DVASPECT_CONTENT;
1979 fetc.lindex = -1;
1980 fetc.tymed = TYMED_HGLOBAL;
1981 const char *format_mime = "text/uri-list";
1982 if (SUCCEEDED(pDataObject->lpVtbl->QueryGetData(pDataObject, &fetc))) {
1983 SDL_LogTrace(SDL_LOG_CATEGORY_INPUT,
1984 ". In Drop File for QueryGetData, format %08x '%s', success",
1985 fetc.cfFormat, format_mime);
1986 STGMEDIUM med;
1987 HRESULT hres = pDataObject->lpVtbl->GetData(pDataObject, &fetc, &med);
1988 SDL_LogTrace(SDL_LOG_CATEGORY_INPUT,
1989 ". In Drop File for GetData, format %08x '%s', HRESULT is %08lx",
1990 fetc.cfFormat, format_mime, hres);
1991 if (SUCCEEDED(hres)) {
1992 const size_t bsize = GlobalSize(med.hGlobal);
1993 const void *buffer = (void *)GlobalLock(med.hGlobal);
1994 SDL_LogTrace(SDL_LOG_CATEGORY_INPUT,
1995 ". In Drop File for GlobalLock, format %08x '%s', memory (%lu) %p",
1996 fetc.cfFormat, format_mime, (unsigned long)bsize, buffer);
1997 if (buffer) {
1998 char *text = (char *)SDL_malloc(bsize + sizeof(Uint32));
1999 SDL_memcpy((Uint8 *)text, buffer, bsize);
2000 SDL_memset((Uint8 *)text + bsize, 0, sizeof(Uint32));
2001 char *saveptr = NULL;
2002 char *token = SDL_strtok_r(text, "\r\n", &saveptr);
2003 while (token != NULL) {
2004 if (SDL_URIToLocal(token, token) >= 0) {
2005 SDL_LogTrace(SDL_LOG_CATEGORY_INPUT,
2006 ". In Drop File, file (%lu of %lu) '%s'",
2007 (unsigned long)SDL_strlen(token), (unsigned long)bsize, token);
2008 SDL_SendDropFile(target->window, NULL, token);
2009 }
2010 token = SDL_strtok_r(NULL, "\r\n", &saveptr);
2011 }
2012 SDL_free(text);
2013 }
2014 GlobalUnlock(med.hGlobal);
2015 ReleaseStgMedium(&med);
2016 SDL_SendDropComplete(target->window);
2017 return S_OK;
2018 }
2019 }
2020 }
2021
2022 {
2023 FORMATETC fetc;
2024 fetc.cfFormat = target->format_text;
2025 fetc.ptd = NULL;
2026 fetc.dwAspect = DVASPECT_CONTENT;
2027 fetc.lindex = -1;
2028 fetc.tymed = TYMED_HGLOBAL;
2029 const char *format_mime = "text/plain;charset=utf-8";
2030 if (SUCCEEDED(pDataObject->lpVtbl->QueryGetData(pDataObject, &fetc))) {
2031 SDL_LogTrace(SDL_LOG_CATEGORY_INPUT,
2032 ". In Drop Text for QueryGetData, format %08x '%s', success",
2033 fetc.cfFormat, format_mime);
2034 STGMEDIUM med;
2035 HRESULT hres = pDataObject->lpVtbl->GetData(pDataObject, &fetc, &med);
2036 SDL_LogTrace(SDL_LOG_CATEGORY_INPUT,
2037 ". In Drop Text for GetData, format %08x '%s', HRESULT is %08lx",
2038 fetc.cfFormat, format_mime, hres);
2039 if (SUCCEEDED(hres)) {
2040 const size_t bsize = GlobalSize(med.hGlobal);
2041 const void *buffer = (void *)GlobalLock(med.hGlobal);
2042 SDL_LogTrace(SDL_LOG_CATEGORY_INPUT,
2043 ". In Drop Text for GlobalLock, format %08x '%s', memory (%lu) %p",
2044 fetc.cfFormat, format_mime, (unsigned long)bsize, buffer);
2045 if (buffer) {
2046 char *text = (char *)SDL_malloc(bsize + sizeof(Uint32));
2047 SDL_memcpy((Uint8 *)text, buffer, bsize);
2048 SDL_memset((Uint8 *)text + bsize, 0, sizeof(Uint32));
2049 char *saveptr = NULL;
2050 char *token = SDL_strtok_r(text, "\r\n", &saveptr);
2051 while (token != NULL) {
2052 SDL_LogTrace(SDL_LOG_CATEGORY_INPUT,
2053 ". In Drop Text, text (%lu of %lu) '%s'",
2054 (unsigned long)SDL_strlen(token), (unsigned long)bsize, token);
2055 SDL_SendDropText(target->window, (char *)token);
2056 token = SDL_strtok_r(NULL, "\r\n", &saveptr);
2057 }
2058 SDL_free(text);
2059 }
2060 GlobalUnlock(med.hGlobal);
2061 ReleaseStgMedium(&med);
2062 SDL_SendDropComplete(target->window);
2063 return S_OK;
2064 }
2065 }
2066 }
2067
2068 {
2069 FORMATETC fetc;
2070 fetc.cfFormat = CF_UNICODETEXT;
2071 fetc.ptd = NULL;
2072 fetc.dwAspect = DVASPECT_CONTENT;
2073 fetc.lindex = -1;
2074 fetc.tymed = TYMED_HGLOBAL;
2075 const char *format_mime = "CF_UNICODETEXT";
2076 if (SUCCEEDED(pDataObject->lpVtbl->QueryGetData(pDataObject, &fetc))) {
2077 SDL_LogTrace(SDL_LOG_CATEGORY_INPUT,
2078 ". In Drop Text for QueryGetData, format %08x '%s', success",
2079 fetc.cfFormat, format_mime);
2080 STGMEDIUM med;
2081 HRESULT hres = pDataObject->lpVtbl->GetData(pDataObject, &fetc, &med);
2082 SDL_LogTrace(SDL_LOG_CATEGORY_INPUT,
2083 ". In Drop Text for GetData, format %08x '%s', HRESULT is %08lx",
2084 fetc.cfFormat, format_mime, hres);
2085 if (SUCCEEDED(hres)) {
2086 const size_t bsize = GlobalSize(med.hGlobal);
2087 const void *buffer = (void *)GlobalLock(med.hGlobal);
2088 SDL_LogTrace(SDL_LOG_CATEGORY_INPUT,
2089 ". In Drop Text for GlobalLock, format %08x '%s', memory (%lu) %p",
2090 fetc.cfFormat, format_mime, (unsigned long)bsize, buffer);
2091 if (buffer) {
2092 buffer = WIN_StringToUTF8((const wchar_t *)buffer);
2093 if (buffer) {
2094 const size_t lbuffer = SDL_strlen((const char *)buffer);
2095 SDL_LogTrace(SDL_LOG_CATEGORY_INPUT,
2096 ". In Drop Text for StringToUTF8, format %08x '%s', memory (%lu) %p",
2097 fetc.cfFormat, format_mime, (unsigned long)lbuffer, buffer);
2098 char *text = (char *)SDL_malloc(lbuffer + sizeof(Uint32));
2099 SDL_memcpy((Uint8 *)text, buffer, lbuffer);
2100 SDL_memset((Uint8 *)text + lbuffer, 0, sizeof(Uint32));
2101 char *saveptr = NULL;
2102 char *token = SDL_strtok_r(text, "\r\n", &saveptr);
2103 while (token != NULL) {
2104 SDL_LogTrace(SDL_LOG_CATEGORY_INPUT,
2105 ". In Drop Text, text (%lu of %lu) '%s'",
2106 (unsigned long)SDL_strlen(token), (unsigned long)lbuffer, token);
2107 SDL_SendDropText(target->window, (char *)token);
2108 token = SDL_strtok_r(NULL, "\r\n", &saveptr);
2109 }
2110 SDL_free(text);
2111 SDL_free((void *)buffer);
2112 }
2113 }
2114 GlobalUnlock(med.hGlobal);
2115 ReleaseStgMedium(&med);
2116 SDL_SendDropComplete(target->window);
2117 return S_OK;
2118 }
2119 }
2120 }
2121
2122 {
2123 FORMATETC fetc;
2124 fetc.cfFormat = CF_TEXT;
2125 fetc.ptd = NULL;
2126 fetc.dwAspect = DVASPECT_CONTENT;
2127 fetc.lindex = -1;
2128 fetc.tymed = TYMED_HGLOBAL;
2129 const char *format_mime = "CF_TEXT";
2130 if (SUCCEEDED(pDataObject->lpVtbl->QueryGetData(pDataObject, &fetc))) {
2131 SDL_LogTrace(SDL_LOG_CATEGORY_INPUT,
2132 ". In Drop Text for QueryGetData, format %08x '%s', success",
2133 fetc.cfFormat, format_mime);
2134 STGMEDIUM med;
2135 HRESULT hres = pDataObject->lpVtbl->GetData(pDataObject, &fetc, &med);
2136 SDL_LogTrace(SDL_LOG_CATEGORY_INPUT,
2137 ". In Drop Text for GetData, format %08x '%s', HRESULT is %08lx",
2138 fetc.cfFormat, format_mime, hres);
2139 if (SUCCEEDED(hres)) {
2140 const size_t bsize = GlobalSize(med.hGlobal);
2141 const void *buffer = (void *)GlobalLock(med.hGlobal);
2142 SDL_LogTrace(SDL_LOG_CATEGORY_INPUT,
2143 ". In Drop Text for GlobalLock, format %08x '%s', memory (%lu) %p",
2144 fetc.cfFormat, format_mime, (unsigned long)bsize, buffer);
2145 if (buffer) {
2146 char *text = (char *)SDL_malloc(bsize + sizeof(Uint32));
2147 SDL_memcpy((Uint8 *)text, buffer, bsize);
2148 SDL_memset((Uint8 *)text + bsize, 0, sizeof(Uint32));
2149 char *saveptr = NULL;
2150 char *token = SDL_strtok_r(text, "\r\n", &saveptr);
2151 while (token != NULL) {
2152 SDL_LogTrace(SDL_LOG_CATEGORY_INPUT,
2153 ". In Drop Text, text (%lu of %lu) '%s'",
2154 (unsigned long)SDL_strlen(token), (unsigned long)bsize, token);
2155 SDL_SendDropText(target->window, (char *)token);
2156 token = SDL_strtok_r(NULL, "\r\n", &saveptr);
2157 }
2158 SDL_free(text);
2159 }
2160 GlobalUnlock(med.hGlobal);
2161 ReleaseStgMedium(&med);
2162 SDL_SendDropComplete(target->window);
2163 return S_OK;
2164 }
2165 }
2166 }
2167
2168 {
2169 FORMATETC fetc;
2170 fetc.cfFormat = CF_HDROP;
2171 fetc.ptd = NULL;
2172 fetc.dwAspect = DVASPECT_CONTENT;
2173 fetc.lindex = -1;
2174 fetc.tymed = TYMED_HGLOBAL;
2175 const char *format_mime = "CF_HDROP";
2176 if (SUCCEEDED(pDataObject->lpVtbl->QueryGetData(pDataObject, &fetc))) {
2177 SDL_LogTrace(SDL_LOG_CATEGORY_INPUT,
2178 ". In Drop File for QueryGetData, format %08x '%s', success",
2179 fetc.cfFormat, format_mime);
2180 STGMEDIUM med;
2181 HRESULT hres = pDataObject->lpVtbl->GetData(pDataObject, &fetc, &med);
2182 SDL_LogTrace(SDL_LOG_CATEGORY_INPUT,
2183 ". In Drop File for GetData, format %08x '%s', HRESULT is %08lx",
2184 fetc.cfFormat, format_mime, hres);
2185 if (SUCCEEDED(hres)) {
2186 const size_t bsize = GlobalSize(med.hGlobal);
2187 HDROP drop = (HDROP)GlobalLock(med.hGlobal);
2188 SDL_LogTrace(SDL_LOG_CATEGORY_INPUT,
2189 ". In Drop File for GlobalLock, format %08x '%s', memory (%lu) %p",
2190 fetc.cfFormat, format_mime, (unsigned long)bsize, drop);
2191 UINT count = DragQueryFile(drop, 0xFFFFFFFF, NULL, 0);
2192 for (UINT i = 0; i < count; ++i) {
2193 UINT size = DragQueryFile(drop, i, NULL, 0) + 1;
2194 LPTSTR buffer = (LPTSTR)SDL_malloc(size * sizeof(TCHAR));
2195 if (buffer) {
2196 if (DragQueryFile(drop, i, buffer, size)) {
2197 char *file = WIN_StringToUTF8(buffer);
2198 SDL_LogTrace(SDL_LOG_CATEGORY_INPUT,
2199 ". In Drop File, file (%lu of %lu) '%s'",
2200 (unsigned long)SDL_strlen(file), (unsigned long)bsize, file);
2201 SDL_SendDropFile(target->window, NULL, file);
2202 SDL_free(file);
2203 }
2204 SDL_free(buffer);
2205 }
2206 }
2207 GlobalUnlock(med.hGlobal);
2208 ReleaseStgMedium(&med);
2209 SDL_SendDropComplete(target->window);
2210 return S_OK;
2211 }
2212 }
2213 }
2214
2215 SDL_SendDropComplete(target->window);
2216 return S_OK;
2217}
2218
2219static void *vtDropTarget[] = {
2220 (void *)(SDLDropTarget_QueryInterface),
2221 (void *)(SDLDropTarget_AddRef),
2222 (void *)(SDLDropTarget_Release),
2223 (void *)(SDLDropTarget_DragEnter),
2224 (void *)(SDLDropTarget_DragOver),
2225 (void *)(SDLDropTarget_DragLeave),
2226 (void *)(SDLDropTarget_Drop)
2227};
2228
2229void WIN_AcceptDragAndDrop(SDL_Window *window, bool accept)
2230{
2231 SDL_WindowData *data = window->internal;
2232 if (data->videodata->oleinitialized) {
2233 if (accept && !data->drop_target) {
2234 SDLDropTarget *drop_target = (SDLDropTarget *)SDL_calloc(1, sizeof(SDLDropTarget));
2235 if (drop_target != NULL) {
2236 drop_target->lpVtbl = vtDropTarget;
2237 drop_target->window = window;
2238 drop_target->hwnd = data->hwnd;
2239 drop_target->format_file = RegisterClipboardFormat(L"text/uri-list");
2240 drop_target->format_text = RegisterClipboardFormat(L"text/plain;charset=utf-8");
2241 data->drop_target = drop_target;
2242 SDLDropTarget_AddRef(drop_target);
2243 RegisterDragDrop(data->hwnd, (LPDROPTARGET)drop_target);
2244 SDL_LogTrace(SDL_LOG_CATEGORY_INPUT,
2245 ". In Accept Drag and Drop, window %u, enabled Full OLE IDropTarget",
2246 window->id);
2247 }
2248 } else if (!accept && data->drop_target) {
2249 RevokeDragDrop(data->hwnd);
2250 SDLDropTarget_Release(data->drop_target);
2251 data->drop_target = NULL;
2252 SDL_LogTrace(SDL_LOG_CATEGORY_INPUT,
2253 ". In Accept Drag and Drop, window %u, disabled Full OLE IDropTarget",
2254 window->id);
2255 }
2256 } else {
2257 DragAcceptFiles(data->hwnd, accept ? TRUE : FALSE);
2258 SDL_LogTrace(SDL_LOG_CATEGORY_INPUT,
2259 ". In Accept Drag and Drop, window %u, %s Fallback WM_DROPFILES",
2260 window->id, (accept ? "enabled" : "disabled"));
2261 }
2262}
2263
2264bool WIN_FlashWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_FlashOperation operation)
2265{
2266 FLASHWINFO desc;
2267
2268 SDL_zero(desc);
2269 desc.cbSize = sizeof(desc);
2270 desc.hwnd = window->internal->hwnd;
2271 switch (operation) {
2272 case SDL_FLASH_CANCEL:
2273 desc.dwFlags = FLASHW_STOP;
2274 break;
2275 case SDL_FLASH_BRIEFLY:
2276 desc.dwFlags = FLASHW_TRAY;
2277 desc.uCount = 1;
2278 break;
2279 case SDL_FLASH_UNTIL_FOCUSED:
2280 desc.dwFlags = (FLASHW_TRAY | FLASHW_TIMERNOFG);
2281 break;
2282 default:
2283 return SDL_Unsupported();
2284 }
2285
2286 FlashWindowEx(&desc);
2287
2288 return true;
2289}
2290
2291void WIN_ShowWindowSystemMenu(SDL_Window *window, int x, int y)
2292{
2293 const SDL_WindowData *data = window->internal;
2294 POINT pt;
2295
2296 pt.x = x;
2297 pt.y = y;
2298 ClientToScreen(data->hwnd, &pt);
2299 SendMessage(data->hwnd, WM_POPUPSYSTEMMENU, 0, MAKELPARAM(pt.x, pt.y));
2300}
2301
2302bool WIN_SetWindowFocusable(SDL_VideoDevice *_this, SDL_Window *window, bool focusable)
2303{
2304 SDL_WindowData *data = window->internal;
2305 HWND hwnd = data->hwnd;
2306 const LONG style = GetWindowLong(hwnd, GWL_EXSTYLE);
2307
2308 SDL_assert(style != 0);
2309
2310 if (focusable) {
2311 if (style & WS_EX_NOACTIVATE) {
2312 if (SetWindowLong(hwnd, GWL_EXSTYLE, style & ~WS_EX_NOACTIVATE) == 0) {
2313 return WIN_SetError("SetWindowLong()");
2314 }
2315 }
2316 } else {
2317 if (!(style & WS_EX_NOACTIVATE)) {
2318 if (SetWindowLong(hwnd, GWL_EXSTYLE, style | WS_EX_NOACTIVATE) == 0) {
2319 return WIN_SetError("SetWindowLong()");
2320 }
2321 }
2322 }
2323
2324 return true;
2325}
2326#endif // !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
2327
2328void WIN_UpdateDarkModeForHWND(HWND hwnd)
2329{
2330#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
2331 SDL_SharedObject *ntdll = SDL_LoadObject("ntdll.dll");
2332 if (!ntdll) {
2333 return;
2334 }
2335 // There is no function to get Windows build number, so let's get it here via RtlGetVersion
2336 RtlGetVersion_t RtlGetVersionFunc = (RtlGetVersion_t)SDL_LoadFunction(ntdll, "RtlGetVersion");
2337 NT_OSVERSIONINFOW os_info;
2338 os_info.dwOSVersionInfoSize = sizeof(NT_OSVERSIONINFOW);
2339 os_info.dwBuildNumber = 0;
2340 if (RtlGetVersionFunc) {
2341 RtlGetVersionFunc(&os_info);
2342 }
2343 SDL_UnloadObject(ntdll);
2344 os_info.dwBuildNumber &= ~0xF0000000;
2345 if (os_info.dwBuildNumber < 17763) {
2346 // Too old to support dark mode
2347 return;
2348 }
2349 SDL_SharedObject *uxtheme = SDL_LoadObject("uxtheme.dll");
2350 if (!uxtheme) {
2351 return;
2352 }
2353 RefreshImmersiveColorPolicyState_t RefreshImmersiveColorPolicyStateFunc = (RefreshImmersiveColorPolicyState_t)SDL_LoadFunction(uxtheme, MAKEINTRESOURCEA(104));
2354 ShouldAppsUseDarkMode_t ShouldAppsUseDarkModeFunc = (ShouldAppsUseDarkMode_t)SDL_LoadFunction(uxtheme, MAKEINTRESOURCEA(132));
2355 AllowDarkModeForWindow_t AllowDarkModeForWindowFunc = (AllowDarkModeForWindow_t)SDL_LoadFunction(uxtheme, MAKEINTRESOURCEA(133));
2356 if (os_info.dwBuildNumber < 18362) {
2357 AllowDarkModeForApp_t AllowDarkModeForAppFunc = (AllowDarkModeForApp_t)SDL_LoadFunction(uxtheme, MAKEINTRESOURCEA(135));
2358 if (AllowDarkModeForAppFunc) {
2359 AllowDarkModeForAppFunc(true);
2360 }
2361 } else {
2362 SetPreferredAppMode_t SetPreferredAppModeFunc = (SetPreferredAppMode_t)SDL_LoadFunction(uxtheme, MAKEINTRESOURCEA(135));
2363 if (SetPreferredAppModeFunc) {
2364 SetPreferredAppModeFunc(UXTHEME_APPMODE_ALLOW_DARK);
2365 }
2366 }
2367 if (RefreshImmersiveColorPolicyStateFunc) {
2368 RefreshImmersiveColorPolicyStateFunc();
2369 }
2370 if (AllowDarkModeForWindowFunc) {
2371 AllowDarkModeForWindowFunc(hwnd, true);
2372 }
2373 BOOL value;
2374 // Check dark mode using ShouldAppsUseDarkMode, but use SDL_GetSystemTheme as a fallback
2375 if (ShouldAppsUseDarkModeFunc) {
2376 value = ShouldAppsUseDarkModeFunc() ? TRUE : FALSE;
2377 } else {
2378 value = (SDL_GetSystemTheme() == SDL_SYSTEM_THEME_DARK) ? TRUE : FALSE;
2379 }
2380 SDL_UnloadObject(uxtheme);
2381 if (os_info.dwBuildNumber < 18362) {
2382 SetProp(hwnd, TEXT("UseImmersiveDarkModeColors"), SDL_reinterpret_cast(HANDLE, SDL_static_cast(INT_PTR, value)));
2383 } else {
2384 HMODULE user32 = GetModuleHandle(TEXT("user32.dll"));
2385 if (user32) {
2386 SetWindowCompositionAttribute_t SetWindowCompositionAttributeFunc = (SetWindowCompositionAttribute_t)GetProcAddress(user32, "SetWindowCompositionAttribute");
2387 if (SetWindowCompositionAttributeFunc) {
2388 WINDOWCOMPOSITIONATTRIBDATA data = { WCA_USEDARKMODECOLORS, &value, sizeof(value) };
2389 SetWindowCompositionAttributeFunc(hwnd, &data);
2390 }
2391 }
2392 }
2393#endif
2394}
2395
2396bool WIN_SetWindowParent(SDL_VideoDevice *_this, SDL_Window *window, SDL_Window *parent)
2397{
2398#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
2399 SDL_WindowData *child_data = window->internal;
2400 const LONG_PTR parent_hwnd = (LONG_PTR)(parent ? parent->internal->hwnd : NULL);
2401 const DWORD style = GetWindowLong(child_data->hwnd, GWL_STYLE);
2402
2403 if (!(style & WS_CHILD)) {
2404 /* Despite the name, this changes the *owner* of a toplevel window, not
2405 * the parent of a child window.
2406 *
2407 * https://devblogs.microsoft.com/oldnewthing/20100315-00/?p=14613
2408 */
2409 SetWindowLongPtr(child_data->hwnd, GWLP_HWNDPARENT, parent_hwnd);
2410 } else {
2411 SetParent(child_data->hwnd, (HWND)parent_hwnd);
2412 }
2413#endif /*!defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)*/
2414
2415 return true;
2416}
2417
2418bool WIN_SetWindowModal(SDL_VideoDevice *_this, SDL_Window *window, bool modal)
2419{
2420#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
2421 const HWND parent_hwnd = window->parent->internal->hwnd;
2422
2423 if (modal) {
2424 // Disable the parent window.
2425 EnableWindow(parent_hwnd, FALSE);
2426 } else if (!(window->flags & SDL_WINDOW_HIDDEN)) {
2427 // Re-enable the parent window
2428 EnableWindow(parent_hwnd, TRUE);
2429 }
2430#endif // !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
2431
2432 return true;
2433}
2434
2435#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 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#ifndef SDL_windowswindow_h_
24#define SDL_windowswindow_h_
25
26#ifdef SDL_VIDEO_OPENGL_EGL
27#include "../SDL_egl_c.h"
28#else
29#include "../SDL_sysvideo.h"
30#endif
31
32// Set up for C function definitions, even when using C++
33#ifdef __cplusplus
34extern "C" {
35#endif
36
37typedef enum SDL_WindowRect
38{
39 SDL_WINDOWRECT_CURRENT,
40 SDL_WINDOWRECT_WINDOWED,
41 SDL_WINDOWRECT_FLOATING,
42 SDL_WINDOWRECT_PENDING
43} SDL_WindowRect;
44
45typedef enum SDL_WindowEraseBackgroundMode
46{
47 SDL_ERASEBACKGROUNDMODE_NEVER,
48 SDL_ERASEBACKGROUNDMODE_INITIAL,
49 SDL_ERASEBACKGROUNDMODE_ALWAYS,
50} SDL_WindowEraseBackgroundMode;
51
52typedef struct
53{
54 void **lpVtbl;
55 int refcount;
56 SDL_Window *window;
57 HWND hwnd;
58 UINT format_text;
59 UINT format_file;
60} SDLDropTarget;
61
62struct SDL_WindowData
63{
64 SDL_Window *window;
65 HWND hwnd;
66 HWND parent;
67 HDC hdc;
68 HDC mdc;
69 HINSTANCE hinstance;
70 HBITMAP hbm;
71 WNDPROC wndproc;
72 HHOOK keyboard_hook;
73 WPARAM mouse_button_flags;
74 LPARAM last_pointer_update;
75 WCHAR high_surrogate;
76 bool initializing;
77 bool expected_resize;
78 bool in_border_change;
79 bool in_title_click;
80 Uint8 focus_click_pending;
81 bool skip_update_clipcursor;
82 bool windowed_mode_was_maximized;
83 bool in_window_deactivation;
84 bool force_ws_maximizebox;
85 bool disable_move_size_events;
86 int in_modal_loop;
87 RECT initial_size_rect;
88 RECT cursor_clipped_rect; // last successfully committed clipping rect for this window
89 RECT cursor_ctrlock_rect; // this is Windows-specific, but probably does not need to be per-window
90 UINT windowed_mode_corner_rounding;
91 COLORREF dwma_border_color;
92 bool mouse_tracked;
93 bool destroy_parent_with_window;
94 SDL_DisplayID last_displayID;
95 WCHAR *ICMFileName;
96 SDL_Window *keyboard_focus;
97 SDL_WindowEraseBackgroundMode hint_erase_background_mode;
98 struct SDL_VideoData *videodata;
99#ifdef SDL_VIDEO_OPENGL_EGL
100 EGLSurface egl_surface;
101#endif
102
103 // Whether we retain the content of the window when changing state
104 UINT copybits_flag;
105 SDLDropTarget *drop_target;
106};
107
108extern bool WIN_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID create_props);
109extern void WIN_SetWindowTitle(SDL_VideoDevice *_this, SDL_Window *window);
110extern bool WIN_SetWindowIcon(SDL_VideoDevice *_this, SDL_Window *window, SDL_Surface *icon);
111extern bool WIN_SetWindowPosition(SDL_VideoDevice *_this, SDL_Window *window);
112extern void WIN_SetWindowSize(SDL_VideoDevice *_this, SDL_Window *window);
113extern bool WIN_GetWindowBordersSize(SDL_VideoDevice *_this, SDL_Window *window, int *top, int *left, int *bottom, int *right);
114extern void WIN_GetWindowSizeInPixels(SDL_VideoDevice *_this, SDL_Window *window, int *width, int *height);
115extern bool WIN_SetWindowOpacity(SDL_VideoDevice *_this, SDL_Window *window, float opacity);
116extern void WIN_ShowWindow(SDL_VideoDevice *_this, SDL_Window *window);
117extern void WIN_HideWindow(SDL_VideoDevice *_this, SDL_Window *window);
118extern void WIN_RaiseWindow(SDL_VideoDevice *_this, SDL_Window *window);
119extern void WIN_MaximizeWindow(SDL_VideoDevice *_this, SDL_Window *window);
120extern void WIN_MinimizeWindow(SDL_VideoDevice *_this, SDL_Window *window);
121extern void WIN_RestoreWindow(SDL_VideoDevice *_this, SDL_Window *window);
122extern void WIN_SetWindowBordered(SDL_VideoDevice *_this, SDL_Window *window, bool bordered);
123extern void WIN_SetWindowResizable(SDL_VideoDevice *_this, SDL_Window *window, bool resizable);
124extern void WIN_SetWindowAlwaysOnTop(SDL_VideoDevice *_this, SDL_Window *window, bool on_top);
125extern SDL_FullscreenResult WIN_SetWindowFullscreen(SDL_VideoDevice *_this, SDL_Window *window, SDL_VideoDisplay *display, SDL_FullscreenOp fullscreen);
126extern void WIN_UpdateWindowICCProfile(SDL_Window *window, bool send_event);
127extern void *WIN_GetWindowICCProfile(SDL_VideoDevice *_this, SDL_Window *window, size_t *size);
128extern bool WIN_SetWindowMouseRect(SDL_VideoDevice *_this, SDL_Window *window);
129extern bool WIN_SetWindowMouseGrab(SDL_VideoDevice *_this, SDL_Window *window, bool grabbed);
130extern bool WIN_SetWindowKeyboardGrab(SDL_VideoDevice *_this, SDL_Window *window, bool grabbed);
131extern void WIN_DestroyWindow(SDL_VideoDevice *_this, SDL_Window *window);
132extern void WIN_OnWindowEnter(SDL_VideoDevice *_this, SDL_Window *window);
133extern void WIN_UpdateClipCursor(SDL_Window *window);
134extern void WIN_UnclipCursorForWindow(SDL_Window *window);
135extern bool WIN_SetWindowHitTest(SDL_Window *window, bool enabled);
136extern void WIN_AcceptDragAndDrop(SDL_Window *window, bool accept);
137extern bool WIN_FlashWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_FlashOperation operation);
138extern void WIN_UpdateDarkModeForHWND(HWND hwnd);
139extern bool WIN_SetWindowPositionInternal(SDL_Window *window, UINT flags, SDL_WindowRect rect_type);
140extern void WIN_ShowWindowSystemMenu(SDL_Window *window, int x, int y);
141extern bool WIN_SetWindowFocusable(SDL_VideoDevice *_this, SDL_Window *window, bool focusable);
142extern bool WIN_AdjustWindowRect(SDL_Window *window, int *x, int *y, int *width, int *height, SDL_WindowRect rect_type);
143extern bool WIN_AdjustWindowRectForHWND(HWND hwnd, LPRECT lpRect, UINT frame_dpi);
144extern bool WIN_SetWindowParent(SDL_VideoDevice *_this, SDL_Window *window, SDL_Window *parent);
145extern bool WIN_SetWindowModal(SDL_VideoDevice *_this, SDL_Window *window, bool modal);
146
147// Ends C function definitions when using C++
148#ifdef __cplusplus
149}
150#endif
151
152#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 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21
22#define MAX_WMMSG (sizeof(wmtab) / sizeof(wmtab[0]))
23
24const char *wmtab[] = {
25 "WM_NULL",
26 "WM_CREATE",
27 "WM_DESTROY",
28 "WM_MOVE",
29 "UNKNOWN (4)",
30 "WM_SIZE",
31 "WM_ACTIVATE",
32 "WM_SETFOCUS",
33 "WM_KILLFOCUS",
34 "UNKNOWN (9)",
35 "WM_ENABLE",
36 "WM_SETREDRAW",
37 "WM_SETTEXT",
38 "WM_GETTEXT",
39 "WM_GETTEXTLENGTH",
40 "WM_PAINT",
41 "WM_CLOSE",
42 "WM_QUERYENDSESSION",
43 "WM_QUIT",
44 "WM_QUERYOPEN",
45 "WM_ERASEBKGND",
46 "WM_SYSCOLORCHANGE",
47 "WM_ENDSESSION",
48 "UNKNOWN (23)",
49 "WM_SHOWWINDOW",
50 "UNKNOWN (25)",
51 "WM_SETTINGCHANGE",
52 "WM_DEVMODECHANGE",
53 "WM_ACTIVATEAPP",
54 "WM_FONTCHANGE",
55 "WM_TIMECHANGE",
56 "WM_CANCELMODE",
57 "WM_SETCURSOR",
58 "WM_MOUSEACTIVATE",
59 "WM_CHILDACTIVATE",
60 "WM_QUEUESYNC",
61 "WM_GETMINMAXINFO",
62 "UNKNOWN (37)",
63 "WM_PAINTICON",
64 "WM_ICONERASEBKGND",
65 "WM_NEXTDLGCTL",
66 "UNKNOWN (41)",
67 "WM_SPOOLERSTATUS",
68 "WM_DRAWITEM",
69 "WM_MEASUREITEM",
70 "WM_DELETEITEM",
71 "WM_VKEYTOITEM",
72 "WM_CHARTOITEM",
73 "WM_SETFONT",
74 "WM_GETFONT",
75 "WM_SETHOTKEY",
76 "WM_GETHOTKEY",
77 "UNKNOWN (52)",
78 "UNKNOWN (53)",
79 "UNKNOWN (54)",
80 "WM_QUERYDRAGICON",
81 "UNKNOWN (56)",
82 "WM_COMPAREITEM",
83 "UNKNOWN (58)",
84 "UNKNOWN (59)",
85 "UNKNOWN (60)",
86 "WM_GETOBJECT",
87 "UNKNOWN (62)",
88 "UNKNOWN (63)",
89 "UNKNOWN (64)",
90 "WM_COMPACTING",
91 "UNKNOWN (66)",
92 "UNKNOWN (67)",
93 "WM_COMMNOTIFY",
94 "UNKNOWN (69)",
95 "WM_WINDOWPOSCHANGING",
96 "WM_WINDOWPOSCHANGED",
97 "WM_POWER",
98 "UNKNOWN (73)",
99 "WM_COPYDATA",
100 "WM_CANCELJOURNAL",
101 "UNKNOWN (76)",
102 "UNKNOWN (77)",
103 "WM_NOTIFY",
104 "UNKNOWN (79)",
105 "WM_INPUTLANGCHANGEREQUEST",
106 "WM_INPUTLANGCHANGE",
107 "WM_TCARD",
108 "WM_HELP",
109 "WM_USERCHANGED",
110 "WM_NOTIFYFORMAT",
111 "UNKNOWN (86)",
112 "UNKNOWN (87)",
113 "UNKNOWN (88)",
114 "UNKNOWN (89)",
115 "UNKNOWN (90)",
116 "UNKNOWN (91)",
117 "UNKNOWN (92)",
118 "UNKNOWN (93)",
119 "UNKNOWN (94)",
120 "UNKNOWN (95)",
121 "UNKNOWN (96)",
122 "UNKNOWN (97)",
123 "UNKNOWN (98)",
124 "UNKNOWN (99)",
125 "UNKNOWN (100)",
126 "UNKNOWN (101)",
127 "UNKNOWN (102)",
128 "UNKNOWN (103)",
129 "UNKNOWN (104)",
130 "UNKNOWN (105)",
131 "UNKNOWN (106)",
132 "UNKNOWN (107)",
133 "UNKNOWN (108)",
134 "UNKNOWN (109)",
135 "UNKNOWN (110)",
136 "UNKNOWN (111)",
137 "UNKNOWN (112)",
138 "UNKNOWN (113)",
139 "UNKNOWN (114)",
140 "UNKNOWN (115)",
141 "UNKNOWN (116)",
142 "UNKNOWN (117)",
143 "UNKNOWN (118)",
144 "UNKNOWN (119)",
145 "UNKNOWN (120)",
146 "UNKNOWN (121)",
147 "UNKNOWN (122)",
148 "WM_CONTEXTMENU",
149 "WM_STYLECHANGING",
150 "WM_STYLECHANGED",
151 "WM_DISPLAYCHANGE",
152 "WM_GETICON",
153 "WM_SETICON",
154 "WM_NCCREATE",
155 "WM_NCDESTROY",
156 "WM_NCCALCSIZE",
157 "WM_NCHITTEST",
158 "WM_NCPAINT",
159 "WM_NCACTIVATE",
160 "WM_GETDLGCODE",
161 "WM_SYNCPAINT",
162 "UNKNOWN (137)",
163 "UNKNOWN (138)",
164 "UNKNOWN (139)",
165 "UNKNOWN (140)",
166 "UNKNOWN (141)",
167 "UNKNOWN (142)",
168 "UNKNOWN (143)",
169 "UNKNOWN (144)",
170 "UNKNOWN (145)",
171 "UNKNOWN (146)",
172 "UNKNOWN (147)",
173 "UNKNOWN (148)",
174 "UNKNOWN (149)",
175 "UNKNOWN (150)",
176 "UNKNOWN (151)",
177 "UNKNOWN (152)",
178 "UNKNOWN (153)",
179 "UNKNOWN (154)",
180 "UNKNOWN (155)",
181 "UNKNOWN (156)",
182 "UNKNOWN (157)",
183 "UNKNOWN (158)",
184 "UNKNOWN (159)",
185 "WM_NCMOUSEMOVE",
186 "WM_NCLBUTTONDOWN",
187 "WM_NCLBUTTONUP",
188 "WM_NCLBUTTONDBLCLK",
189 "WM_NCRBUTTONDOWN",
190 "WM_NCRBUTTONUP",
191 "WM_NCRBUTTONDBLCLK",
192 "WM_NCMBUTTONDOWN",
193 "WM_NCMBUTTONUP",
194 "WM_NCMBUTTONDBLCLK",
195 "UNKNOWN (170)",
196 "WM_NCXBUTTONDOWN",
197 "WM_NCXBUTTONUP",
198 "WM_NCXBUTTONDBLCLK",
199 "WM_NCUAHDRAWCAPTION",
200 "WM_NCUAHDRAWFRAME",
201 "UNKNOWN (176)",
202 "UNKNOWN (177)",
203 "UNKNOWN (178)",
204 "UNKNOWN (179)",
205 "UNKNOWN (180)",
206 "UNKNOWN (181)",
207 "UNKNOWN (182)",
208 "UNKNOWN (183)",
209 "UNKNOWN (184)",
210 "UNKNOWN (185)",
211 "UNKNOWN (186)",
212 "UNKNOWN (187)",
213 "UNKNOWN (188)",
214 "UNKNOWN (189)",
215 "UNKNOWN (190)",
216 "UNKNOWN (191)",
217 "UNKNOWN (192)",
218 "UNKNOWN (193)",
219 "UNKNOWN (194)",
220 "UNKNOWN (195)",
221 "UNKNOWN (196)",
222 "UNKNOWN (197)",
223 "UNKNOWN (198)",
224 "UNKNOWN (199)",
225 "UNKNOWN (200)",
226 "UNKNOWN (201)",
227 "UNKNOWN (202)",
228 "UNKNOWN (203)",
229 "UNKNOWN (204)",
230 "UNKNOWN (205)",
231 "UNKNOWN (206)",
232 "UNKNOWN (207)",
233 "UNKNOWN (208)",
234 "UNKNOWN (209)",
235 "UNKNOWN (210)",
236 "UNKNOWN (211)",
237 "UNKNOWN (212)",
238 "UNKNOWN (213)",
239 "UNKNOWN (214)",
240 "UNKNOWN (215)",
241 "UNKNOWN (216)",
242 "UNKNOWN (217)",
243 "UNKNOWN (218)",
244 "UNKNOWN (219)",
245 "UNKNOWN (220)",
246 "UNKNOWN (221)",
247 "UNKNOWN (222)",
248 "UNKNOWN (223)",
249 "UNKNOWN (224)",
250 "UNKNOWN (225)",
251 "UNKNOWN (226)",
252 "UNKNOWN (227)",
253 "UNKNOWN (228)",
254 "UNKNOWN (229)",
255 "UNKNOWN (230)",
256 "UNKNOWN (231)",
257 "UNKNOWN (232)",
258 "UNKNOWN (233)",
259 "UNKNOWN (234)",
260 "UNKNOWN (235)",
261 "UNKNOWN (236)",
262 "UNKNOWN (237)",
263 "UNKNOWN (238)",
264 "UNKNOWN (239)",
265 "UNKNOWN (240)",
266 "UNKNOWN (241)",
267 "UNKNOWN (242)",
268 "UNKNOWN (243)",
269 "UNKNOWN (244)",
270 "UNKNOWN (245)",
271 "UNKNOWN (246)",
272 "UNKNOWN (247)",
273 "UNKNOWN (248)",
274 "UNKNOWN (249)",
275 "UNKNOWN (250)",
276 "UNKNOWN (251)",
277 "UNKNOWN (252)",
278 "UNKNOWN (253)",
279 "UNKNOWN (254)",
280 "WM_INPUT",
281 "WM_KEYDOWN",
282 "WM_KEYUP",
283 "WM_CHAR",
284 "WM_DEADCHAR",
285 "WM_SYSKEYDOWN",
286 "WM_SYSKEYUP",
287 "WM_SYSCHAR",
288 "WM_SYSDEADCHAR",
289 "WM_KEYLAST",
290 "UNKNOWN (265)",
291 "UNKNOWN (266)",
292 "UNKNOWN (267)",
293 "UNKNOWN (268)",
294 "UNKNOWN (269)",
295 "UNKNOWN (270)",
296 "UNKNOWN (271)",
297 "WM_INITDIALOG",
298 "WM_COMMAND",
299 "WM_SYSCOMMAND",
300 "WM_TIMER",
301 "WM_HSCROLL",
302 "WM_VSCROLL",
303 "WM_INITMENU",
304 "WM_INITMENUPOPUP",
305 "UNKNOWN (280)",
306 "WM_GESTURE",
307 "UNKNOWN (282)",
308 "UNKNOWN (283)",
309 "UNKNOWN (284)",
310 "UNKNOWN (285)",
311 "UNKNOWN (286)",
312 "WM_MENUSELECT",
313 "WM_MENUCHAR",
314 "WM_ENTERIDLE",
315 "WM_MENURBUTTONUP",
316 "WM_MENUDRAG",
317 "WM_MENUGETOBJECT",
318 "WM_UNINITMENUPOPUP",
319 "WM_MENUCOMMAND",
320 "UNKNOWN (295)",
321 "UNKNOWN (296)",
322 "UNKNOWN (297)",
323 "UNKNOWN (298)",
324 "UNKNOWN (299)",
325 "UNKNOWN (300)",
326 "UNKNOWN (301)",
327 "UNKNOWN (302)",
328 "UNKNOWN (303)",
329 "UNKNOWN (304)",
330 "UNKNOWN (305)",
331 "WM_CTLCOLORMSGBOX",
332 "WM_CTLCOLOREDIT",
333 "WM_CTLCOLORLISTBOX",
334 "WM_CTLCOLORBTN",
335 "WM_CTLCOLORDLG",
336 "WM_CTLCOLORSCROLLBAR",
337 "WM_CTLCOLORSTATIC",
338 "UNKNOWN (313)",
339 "UNKNOWN (314)",
340 "UNKNOWN (315)",
341 "UNKNOWN (316)",
342 "UNKNOWN (317)",
343 "UNKNOWN (318)",
344 "UNKNOWN (319)",
345 "UNKNOWN (320)",
346 "UNKNOWN (321)",
347 "UNKNOWN (322)",
348 "UNKNOWN (323)",
349 "UNKNOWN (324)",
350 "UNKNOWN (325)",
351 "UNKNOWN (326)",
352 "UNKNOWN (327)",
353 "UNKNOWN (328)",
354 "UNKNOWN (329)",
355 "UNKNOWN (330)",
356 "UNKNOWN (331)",
357 "UNKNOWN (332)",
358 "UNKNOWN (333)",
359 "UNKNOWN (334)",
360 "UNKNOWN (335)",
361 "UNKNOWN (336)",
362 "UNKNOWN (337)",
363 "UNKNOWN (338)",
364 "UNKNOWN (339)",
365 "UNKNOWN (340)",
366 "UNKNOWN (341)",
367 "UNKNOWN (342)",
368 "UNKNOWN (343)",
369 "UNKNOWN (344)",
370 "UNKNOWN (345)",
371 "UNKNOWN (346)",
372 "UNKNOWN (347)",
373 "UNKNOWN (348)",
374 "UNKNOWN (349)",
375 "UNKNOWN (350)",
376 "UNKNOWN (351)",
377 "UNKNOWN (352)",
378 "UNKNOWN (353)",
379 "UNKNOWN (354)",
380 "UNKNOWN (355)",
381 "UNKNOWN (356)",
382 "UNKNOWN (357)",
383 "UNKNOWN (358)",
384 "UNKNOWN (359)",
385 "UNKNOWN (360)",
386 "UNKNOWN (361)",
387 "UNKNOWN (362)",
388 "UNKNOWN (363)",
389 "UNKNOWN (364)",
390 "UNKNOWN (365)",
391 "UNKNOWN (366)",
392 "UNKNOWN (367)",
393 "UNKNOWN (368)",
394 "UNKNOWN (369)",
395 "UNKNOWN (370)",
396 "UNKNOWN (371)",
397 "UNKNOWN (372)",
398 "UNKNOWN (373)",
399 "UNKNOWN (374)",
400 "UNKNOWN (375)",
401 "UNKNOWN (376)",
402 "UNKNOWN (377)",
403 "UNKNOWN (378)",
404 "UNKNOWN (379)",
405 "UNKNOWN (380)",
406 "UNKNOWN (381)",
407 "UNKNOWN (382)",
408 "UNKNOWN (383)",
409 "UNKNOWN (384)",
410 "UNKNOWN (385)",
411 "UNKNOWN (386)",
412 "UNKNOWN (387)",
413 "UNKNOWN (388)",
414 "UNKNOWN (389)",
415 "UNKNOWN (390)",
416 "UNKNOWN (391)",
417 "UNKNOWN (392)",
418 "UNKNOWN (393)",
419 "UNKNOWN (394)",
420 "UNKNOWN (395)",
421 "UNKNOWN (396)",
422 "UNKNOWN (397)",
423 "UNKNOWN (398)",
424 "UNKNOWN (399)",
425 "UNKNOWN (400)",
426 "UNKNOWN (401)",
427 "UNKNOWN (402)",
428 "UNKNOWN (403)",
429 "UNKNOWN (404)",
430 "UNKNOWN (405)",
431 "UNKNOWN (406)",
432 "UNKNOWN (407)",
433 "UNKNOWN (408)",
434 "UNKNOWN (409)",
435 "UNKNOWN (410)",
436 "UNKNOWN (411)",
437 "UNKNOWN (412)",
438 "UNKNOWN (413)",
439 "UNKNOWN (414)",
440 "UNKNOWN (415)",
441 "UNKNOWN (416)",
442 "UNKNOWN (417)",
443 "UNKNOWN (418)",
444 "UNKNOWN (419)",
445 "UNKNOWN (420)",
446 "UNKNOWN (421)",
447 "UNKNOWN (422)",
448 "UNKNOWN (423)",
449 "UNKNOWN (424)",
450 "UNKNOWN (425)",
451 "UNKNOWN (426)",
452 "UNKNOWN (427)",
453 "UNKNOWN (428)",
454 "UNKNOWN (429)",
455 "UNKNOWN (430)",
456 "UNKNOWN (431)",
457 "UNKNOWN (432)",
458 "UNKNOWN (433)",
459 "UNKNOWN (434)",
460 "UNKNOWN (435)",
461 "UNKNOWN (436)",
462 "UNKNOWN (437)",
463 "UNKNOWN (438)",
464 "UNKNOWN (439)",
465 "UNKNOWN (440)",
466 "UNKNOWN (441)",
467 "UNKNOWN (442)",
468 "UNKNOWN (443)",
469 "UNKNOWN (444)",
470 "UNKNOWN (445)",
471 "UNKNOWN (446)",
472 "UNKNOWN (447)",
473 "UNKNOWN (448)",
474 "UNKNOWN (449)",
475 "UNKNOWN (450)",
476 "UNKNOWN (451)",
477 "UNKNOWN (452)",
478 "UNKNOWN (453)",
479 "UNKNOWN (454)",
480 "UNKNOWN (455)",
481 "UNKNOWN (456)",
482 "UNKNOWN (457)",
483 "UNKNOWN (458)",
484 "UNKNOWN (459)",
485 "UNKNOWN (460)",
486 "UNKNOWN (461)",
487 "UNKNOWN (462)",
488 "UNKNOWN (463)",
489 "UNKNOWN (464)",
490 "UNKNOWN (465)",
491 "UNKNOWN (466)",
492 "UNKNOWN (467)",
493 "UNKNOWN (468)",
494 "UNKNOWN (469)",
495 "UNKNOWN (470)",
496 "UNKNOWN (471)",
497 "UNKNOWN (472)",
498 "UNKNOWN (473)",
499 "UNKNOWN (474)",
500 "UNKNOWN (475)",
501 "UNKNOWN (476)",
502 "UNKNOWN (477)",
503 "UNKNOWN (478)",
504 "UNKNOWN (479)",
505 "UNKNOWN (480)",
506 "UNKNOWN (481)",
507 "UNKNOWN (482)",
508 "UNKNOWN (483)",
509 "UNKNOWN (484)",
510 "UNKNOWN (485)",
511 "UNKNOWN (486)",
512 "UNKNOWN (487)",
513 "UNKNOWN (488)",
514 "UNKNOWN (489)",
515 "UNKNOWN (490)",
516 "UNKNOWN (491)",
517 "UNKNOWN (492)",
518 "UNKNOWN (493)",
519 "UNKNOWN (494)",
520 "UNKNOWN (495)",
521 "UNKNOWN (496)",
522 "UNKNOWN (497)",
523 "UNKNOWN (498)",
524 "UNKNOWN (499)",
525 "UNKNOWN (500)",
526 "UNKNOWN (501)",
527 "UNKNOWN (502)",
528 "UNKNOWN (503)",
529 "UNKNOWN (504)",
530 "UNKNOWN (505)",
531 "UNKNOWN (506)",
532 "UNKNOWN (507)",
533 "UNKNOWN (508)",
534 "UNKNOWN (509)",
535 "UNKNOWN (510)",
536 "UNKNOWN (511)",
537 "WM_MOUSEMOVE",
538 "WM_LBUTTONDOWN",
539 "WM_LBUTTONUP",
540 "WM_LBUTTONDBLCLK",
541 "WM_RBUTTONDOWN",
542 "WM_RBUTTONUP",
543 "WM_RBUTTONDBLCLK",
544 "WM_MBUTTONDOWN",
545 "WM_MBUTTONUP",
546 "WM_MOUSELAST",
547 "WM_MOUSEWHEEL",
548 "WM_XBUTTONDOWN",
549 "WM_XBUTTONUP",
550 "UNKNOWN (525)",
551 "UNKNOWN (526)",
552 "UNKNOWN (527)",
553 "WM_PARENTNOTIFY",
554 "WM_ENTERMENULOOP",
555 "WM_EXITMENULOOP",
556 "WM_NEXTMENU",
557 "WM_SIZING",
558 "WM_CAPTURECHANGED",
559 "WM_MOVING",
560 "UNKNOWN (535)",
561 "WM_POWERBROADCAST",
562 "WM_DEVICECHANGE",
563 "UNKNOWN (538)",
564 "UNKNOWN (539)",
565 "UNKNOWN (540)",
566 "UNKNOWN (541)",
567 "UNKNOWN (542)",
568 "UNKNOWN (543)",
569 "WM_MDICREATE",
570 "WM_MDIDESTROY",
571 "WM_MDIACTIVATE",
572 "WM_MDIRESTORE",
573 "WM_MDINEXT",
574 "WM_MDIMAXIMIZE",
575 "WM_MDITILE",
576 "WM_MDICASCADE",
577 "WM_MDIICONARRANGE",
578 "WM_MDIGETACTIVE",
579 "UNKNOWN (554)",
580 "UNKNOWN (555)",
581 "UNKNOWN (556)",
582 "UNKNOWN (557)",
583 "UNKNOWN (558)",
584 "UNKNOWN (559)",
585 "WM_MDISETMENU",
586 "WM_ENTERSIZEMOVE",
587 "WM_EXITSIZEMOVE",
588 "WM_DROPFILES",
589 "WM_MDIREFRESHMENU",
590 "UNKNOWN (565)",
591 "UNKNOWN (566)",
592 "UNKNOWN (567)",
593 "WM_POINTERDEVICECHANGE",
594 "WM_POINTERDEVICEINRANGE",
595 "WM_POINTERDEVICEOUTOFRANGE",
596 "UNKNOWN (571)",
597 "UNKNOWN (572)",
598 "UNKNOWN (573)",
599 "UNKNOWN (574)",
600 "UNKNOWN (575)",
601 "WM_TOUCH",
602 "WM_NCPOINTERUPDATE",
603 "WM_NCPOINTERDOWN",
604 "WM_NCPOINTERUP",
605 "UNKNOWN (580)",
606 "WM_POINTERUPDATE",
607 "WM_POINTERDOWN",
608 "WM_POINTERUP",
609 "WM_POINTERENTER",
610 "WM_POINTERLEAVE",
611 "WM_POINTERACTIVATE",
612 "WM_POINTERCAPTURECHANGED",
613 "WM_TOUCHHITTESTING",
614 "WM_POINTERWHEEL",
615 "WM_POINTERHWHEEL",
616 "DM_POINTERHITTEST",
617 "UNKNOWN (592)",
618 "UNKNOWN (593)",
619 "UNKNOWN (594)",
620 "UNKNOWN (595)",
621 "UNKNOWN (596)",
622 "UNKNOWN (597)",
623 "UNKNOWN (598)",
624 "UNKNOWN (599)",
625 "UNKNOWN (600)",
626 "UNKNOWN (601)",
627 "UNKNOWN (602)",
628 "UNKNOWN (603)",
629 "UNKNOWN (604)",
630 "UNKNOWN (605)",
631 "UNKNOWN (606)",
632 "UNKNOWN (607)",
633 "UNKNOWN (608)",
634 "UNKNOWN (609)",
635 "UNKNOWN (610)",
636 "UNKNOWN (611)",
637 "UNKNOWN (612)",
638 "UNKNOWN (613)",
639 "UNKNOWN (614)",
640 "UNKNOWN (615)",
641 "UNKNOWN (616)",
642 "UNKNOWN (617)",
643 "UNKNOWN (618)",
644 "UNKNOWN (619)",
645 "UNKNOWN (620)",
646 "UNKNOWN (621)",
647 "UNKNOWN (622)",
648 "UNKNOWN (623)",
649 "UNKNOWN (624)",
650 "UNKNOWN (625)",
651 "UNKNOWN (626)",
652 "UNKNOWN (627)",
653 "UNKNOWN (628)",
654 "UNKNOWN (629)",
655 "UNKNOWN (630)",
656 "UNKNOWN (631)",
657 "UNKNOWN (632)",
658 "UNKNOWN (633)",
659 "UNKNOWN (634)",
660 "UNKNOWN (635)",
661 "UNKNOWN (636)",
662 "UNKNOWN (637)",
663 "UNKNOWN (638)",
664 "UNKNOWN (639)",
665 "UNKNOWN (640)",
666 "WM_IME_SETCONTEXT",
667 "WM_IME_NOTIFY",
668 "WM_IME_CONTROL",
669 "WM_IME_COMPOSITIONFULL",
670 "WM_IME_SELECT",
671 "WM_IME_CHAR",
672 "UNKNOWN (647)",
673 "WM_IME_REQUEST",
674 "UNKNOWN (649)",
675 "UNKNOWN (650)",
676 "UNKNOWN (651)",
677 "UNKNOWN (652)",
678 "UNKNOWN (653)",
679 "UNKNOWN (654)",
680 "UNKNOWN (655)",
681 "WM_IME_KEYDOWN",
682 "WM_IME_KEYUP",
683 "UNKNOWN (658)",
684 "UNKNOWN (659)",
685 "UNKNOWN (660)",
686 "UNKNOWN (661)",
687 "UNKNOWN (662)",
688 "UNKNOWN (663)",
689 "UNKNOWN (664)",
690 "UNKNOWN (665)",
691 "UNKNOWN (666)",
692 "UNKNOWN (667)",
693 "UNKNOWN (668)",
694 "UNKNOWN (669)",
695 "UNKNOWN (670)",
696 "UNKNOWN (671)",
697 "WM_NCMOUSEHOVER",
698 "WM_MOUSEHOVER",
699 "WM_NCMOUSELEAVE",
700 "WM_MOUSELEAVE",
701 "UNKNOWN (676)",
702 "UNKNOWN (677)",
703 "UNKNOWN (678)",
704 "UNKNOWN (679)",
705 "UNKNOWN (680)",
706 "UNKNOWN (681)",
707 "UNKNOWN (682)",
708 "UNKNOWN (683)",
709 "UNKNOWN (684)",
710 "UNKNOWN (685)",
711 "UNKNOWN (686)",
712 "UNKNOWN (687)",
713 "UNKNOWN (688)",
714 "WM_WTSSESSION_CHANGE",
715 "UNKNOWN (690)",
716 "UNKNOWN (691)",
717 "UNKNOWN (692)",
718 "UNKNOWN (693)",
719 "UNKNOWN (694)",
720 "UNKNOWN (695)",
721 "UNKNOWN (696)",
722 "UNKNOWN (697)",
723 "UNKNOWN (698)",
724 "UNKNOWN (699)",
725 "UNKNOWN (700)",
726 "UNKNOWN (701)",
727 "UNKNOWN (702)",
728 "UNKNOWN (703)",
729 "UNKNOWN (704)",
730 "UNKNOWN (705)",
731 "UNKNOWN (706)",
732 "UNKNOWN (707)",
733 "UNKNOWN (708)",
734 "UNKNOWN (709)",
735 "UNKNOWN (710)",
736 "UNKNOWN (711)",
737 "UNKNOWN (712)",
738 "UNKNOWN (713)",
739 "UNKNOWN (714)",
740 "UNKNOWN (715)",
741 "UNKNOWN (716)",
742 "UNKNOWN (717)",
743 "UNKNOWN (718)",
744 "UNKNOWN (719)",
745 "UNKNOWN (720)",
746 "UNKNOWN (721)",
747 "UNKNOWN (722)",
748 "UNKNOWN (723)",
749 "UNKNOWN (724)",
750 "UNKNOWN (725)",
751 "UNKNOWN (726)",
752 "UNKNOWN (727)",
753 "UNKNOWN (728)",
754 "UNKNOWN (729)",
755 "UNKNOWN (730)",
756 "UNKNOWN (731)",
757 "UNKNOWN (732)",
758 "UNKNOWN (733)",
759 "UNKNOWN (734)",
760 "UNKNOWN (735)",
761 "WM_DPICHANGED",
762 "UNKNOWN (737)",
763 "UNKNOWN (738)",
764 "UNKNOWN (739)",
765 "WM_GETDPISCALEDSIZE",
766 "UNKNOWN (741)",
767 "UNKNOWN (742)",
768 "UNKNOWN (743)",
769 "UNKNOWN (744)",
770 "UNKNOWN (745)",
771 "UNKNOWN (746)",
772 "UNKNOWN (747)",
773 "UNKNOWN (748)",
774 "UNKNOWN (749)",
775 "UNKNOWN (750)",
776 "UNKNOWN (751)",
777 "UNKNOWN (752)",
778 "UNKNOWN (753)",
779 "UNKNOWN (754)",
780 "UNKNOWN (755)",
781 "UNKNOWN (756)",
782 "UNKNOWN (757)",
783 "UNKNOWN (758)",
784 "UNKNOWN (759)",
785 "UNKNOWN (760)",
786 "UNKNOWN (761)",
787 "UNKNOWN (762)",
788 "UNKNOWN (763)",
789 "UNKNOWN (764)",
790 "UNKNOWN (765)",
791 "UNKNOWN (766)",
792 "UNKNOWN (767)",
793 "WM_CUT",
794 "WM_COPY",
795 "WM_PASTE",
796 "WM_CLEAR",
797 "WM_UNDO",
798 "WM_RENDERFORMAT",
799 "WM_RENDERALLFORMATS",
800 "WM_DESTROYCLIPBOARD",
801 "WM_DRAWCLIPBOARD",
802 "WM_PAINTCLIPBOARD",
803 "WM_VSCROLLCLIPBOARD",
804 "WM_SIZECLIPBOARD",
805 "WM_ASKCBFORMATNAME",
806 "WM_CHANGECBCHAIN",
807 "WM_HSCROLLCLIPBOARD",
808 "WM_QUERYNEWPALETTE",
809 "WM_PALETTEISCHANGING",
810 "WM_PALETTECHANGED",
811 "WM_HOTKEY",
812 "UNKNOWN (787)",
813 "UNKNOWN (788)",
814 "UNKNOWN (789)",
815 "UNKNOWN (790)",
816 "WM_PRINT",
817 "WM_PRINTCLIENT",
818 "WM_APPCOMMAND",
819 "WM_THEMECHANGED",
820 "UNKNOWN (795)",
821 "UNKNOWN (796)",
822 "WM_CLIPBOARDUPDATE",
823 "WM_DWMCOMPOSITIONCHANGED",
824 "WM_DWMNCRENDERINGCHANGED",
825 "WM_DWMCOLORIZATIONCOLORCHANGED",
826 "WM_DWMWINDOWMAXIMIZEDCHANGE",
827 "UNKNOWN (802)",
828 "WM_DWMSENDICONICTHUMBNAIL",
829 "UNKNOWN (804)",
830 "UNKNOWN (805)",
831 "WM_DWMSENDICONICLIVEPREVIEWBITMAP",
832 "UNKNOWN (807)",
833 "UNKNOWN (808)",
834 "UNKNOWN (809)",
835 "UNKNOWN (810)",
836 "UNKNOWN (811)",
837 "UNKNOWN (812)",
838 "UNKNOWN (813)",
839 "UNKNOWN (814)",
840 "UNKNOWN (815)",
841 "UNKNOWN (816)",
842 "UNKNOWN (817)",
843 "UNKNOWN (818)",
844 "UNKNOWN (819)",
845 "UNKNOWN (820)",
846 "UNKNOWN (821)",
847 "UNKNOWN (822)",
848 "UNKNOWN (823)",
849 "UNKNOWN (824)",
850 "UNKNOWN (825)",
851 "UNKNOWN (826)",
852 "UNKNOWN (827)",
853 "UNKNOWN (828)",
854 "UNKNOWN (829)",
855 "UNKNOWN (830)",
856 "WM_GETTITLEBARINFOEX",
857 "UNKNOWN (832)",
858 "UNKNOWN (833)",
859 "UNKNOWN (834)",
860 "UNKNOWN (835)",
861 "UNKNOWN (836)",
862 "UNKNOWN (837)",
863 "UNKNOWN (838)",
864 "UNKNOWN (839)",
865 "UNKNOWN (840)",
866 "UNKNOWN (841)",
867 "UNKNOWN (842)",
868 "UNKNOWN (843)",
869 "UNKNOWN (844)",
870 "UNKNOWN (845)",
871 "UNKNOWN (846)",
872 "UNKNOWN (847)",
873 "UNKNOWN (848)",
874 "UNKNOWN (849)",
875 "UNKNOWN (850)",
876 "UNKNOWN (851)",
877 "UNKNOWN (852)",
878 "UNKNOWN (853)",
879 "UNKNOWN (854)",
880 "UNKNOWN (855)",
881 "WM_HANDHELDFIRST",
882 "UNKNOWN (857)",
883 "UNKNOWN (858)",
884 "UNKNOWN (859)",
885 "UNKNOWN (860)",
886 "UNKNOWN (861)",
887 "UNKNOWN (862)",
888 "WM_HANDHELDLAST",
889 "WM_AFXFIRST",
890 "UNKNOWN (865)",
891 "UNKNOWN (866)",
892 "UNKNOWN (867)",
893 "UNKNOWN (868)",
894 "UNKNOWN (869)",
895 "UNKNOWN (870)",
896 "UNKNOWN (871)",
897 "UNKNOWN (872)",
898 "UNKNOWN (873)",
899 "UNKNOWN (874)",
900 "UNKNOWN (875)",
901 "UNKNOWN (876)",
902 "UNKNOWN (877)",
903 "UNKNOWN (878)",
904 "UNKNOWN (879)",
905 "UNKNOWN (880)",
906 "UNKNOWN (881)",
907 "UNKNOWN (882)",
908 "UNKNOWN (883)",
909 "UNKNOWN (884)",
910 "UNKNOWN (885)",
911 "UNKNOWN (886)",
912 "UNKNOWN (887)",
913 "UNKNOWN (888)",
914 "UNKNOWN (889)",
915 "UNKNOWN (890)",
916 "UNKNOWN (891)",
917 "UNKNOWN (892)",
918 "UNKNOWN (893)",
919 "UNKNOWN (894)",
920 "WM_AFXLAST",
921 "WM_PENWINFIRST",
922 "UNKNOWN (897)",
923 "UNKNOWN (898)",
924 "UNKNOWN (899)",
925 "UNKNOWN (900)",
926 "UNKNOWN (901)",
927 "UNKNOWN (902)",
928 "UNKNOWN (903)",
929 "UNKNOWN (904)",
930 "UNKNOWN (905)",
931 "UNKNOWN (906)",
932 "UNKNOWN (907)",
933 "UNKNOWN (908)",
934 "UNKNOWN (909)",
935 "UNKNOWN (910)",
936 "WM_PENWINLAST",
937 "UNKNOWN (912)",
938 "UNKNOWN (913)",
939 "UNKNOWN (914)",
940 "UNKNOWN (915)",
941 "UNKNOWN (916)",
942 "UNKNOWN (917)",
943 "UNKNOWN (918)",
944 "UNKNOWN (919)",
945 "UNKNOWN (920)",
946 "UNKNOWN (921)",
947 "UNKNOWN (922)",
948 "UNKNOWN (923)",
949 "UNKNOWN (924)",
950 "UNKNOWN (925)",
951 "UNKNOWN (926)",
952 "UNKNOWN (927)",
953 "UNKNOWN (928)",
954 "UNKNOWN (929)",
955 "UNKNOWN (930)",
956 "UNKNOWN (931)",
957 "UNKNOWN (932)",
958 "UNKNOWN (933)",
959 "UNKNOWN (934)",
960 "UNKNOWN (935)",
961 "UNKNOWN (936)",
962 "UNKNOWN (937)",
963 "UNKNOWN (938)",
964 "UNKNOWN (939)",
965 "UNKNOWN (940)",
966 "UNKNOWN (941)",
967 "UNKNOWN (942)",
968 "UNKNOWN (943)",
969 "UNKNOWN (944)",
970 "UNKNOWN (945)",
971 "UNKNOWN (946)",
972 "UNKNOWN (947)",
973 "UNKNOWN (948)",
974 "UNKNOWN (949)",
975 "UNKNOWN (950)",
976 "UNKNOWN (951)",
977 "UNKNOWN (952)",
978 "UNKNOWN (953)",
979 "UNKNOWN (954)",
980 "UNKNOWN (955)",
981 "UNKNOWN (956)",
982 "UNKNOWN (957)",
983 "UNKNOWN (958)",
984 "UNKNOWN (959)",
985 "UNKNOWN (960)",
986 "UNKNOWN (961)",
987 "UNKNOWN (962)",
988 "UNKNOWN (963)",
989 "UNKNOWN (964)",
990 "UNKNOWN (965)",
991 "UNKNOWN (966)",
992 "UNKNOWN (967)",
993 "UNKNOWN (968)",
994 "UNKNOWN (969)",
995 "UNKNOWN (970)",
996 "UNKNOWN (971)",
997 "UNKNOWN (972)",
998 "UNKNOWN (973)",
999 "UNKNOWN (974)",
1000 "UNKNOWN (975)",
1001 "UNKNOWN (976)",
1002 "UNKNOWN (977)",
1003 "UNKNOWN (978)",
1004 "UNKNOWN (979)",
1005 "UNKNOWN (980)",
1006 "UNKNOWN (981)",
1007 "UNKNOWN (982)",
1008 "UNKNOWN (983)",
1009 "UNKNOWN (984)",
1010 "UNKNOWN (985)",
1011 "UNKNOWN (986)",
1012 "UNKNOWN (987)",
1013 "UNKNOWN (988)",
1014 "UNKNOWN (989)",
1015 "UNKNOWN (990)",
1016 "UNKNOWN (991)",
1017 "UNKNOWN (992)",
1018 "UNKNOWN (993)",
1019 "UNKNOWN (994)",
1020 "UNKNOWN (995)",
1021 "UNKNOWN (996)",
1022 "UNKNOWN (997)",
1023 "UNKNOWN (998)",
1024 "UNKNOWN (999)",
1025 "UNKNOWN (1000)",
1026 "UNKNOWN (1001)",
1027 "UNKNOWN (1002)",
1028 "UNKNOWN (1003)",
1029 "UNKNOWN (1004)",
1030 "UNKNOWN (1005)",
1031 "UNKNOWN (1006)",
1032 "UNKNOWN (1007)",
1033 "UNKNOWN (1008)",
1034 "UNKNOWN (1009)",
1035 "UNKNOWN (1010)",
1036 "UNKNOWN (1011)",
1037 "UNKNOWN (1012)",
1038 "UNKNOWN (1013)",
1039 "UNKNOWN (1014)",
1040 "UNKNOWN (1015)",
1041 "UNKNOWN (1016)",
1042 "UNKNOWN (1017)",
1043 "UNKNOWN (1018)",
1044 "UNKNOWN (1019)",
1045 "UNKNOWN (1020)",
1046 "UNKNOWN (1021)",
1047 "UNKNOWN (1022)",
1048 "UNKNOWN (1023)",
1049 "WM_USER"
1050};