summaryrefslogtreecommitdiff
path: root/contrib/SDL-3.2.8/src/video/windows/SDL_windowswindow.c
diff options
context:
space:
mode:
Diffstat (limited to 'contrib/SDL-3.2.8/src/video/windows/SDL_windowswindow.c')
-rw-r--r--contrib/SDL-3.2.8/src/video/windows/SDL_windowswindow.c2435
1 files changed, 2435 insertions, 0 deletions
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