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#include "SDL_internal.h"
23
24// The high-level video driver subsystem
25
26#include "SDL_sysvideo.h"
27#include "SDL_clipboard_c.h"
28#include "SDL_egl_c.h"
29#include "SDL_surface_c.h"
30#include "SDL_pixels_c.h"
31#include "SDL_rect_c.h"
32#include "SDL_video_c.h"
33#include "../events/SDL_events_c.h"
34#include "../SDL_hints_c.h"
35#include "../SDL_properties_c.h"
36#include "../timer/SDL_timer_c.h"
37#include "../camera/SDL_camera_c.h"
38#include "../render/SDL_sysrender.h"
39#include "../main/SDL_main_callbacks.h"
40
41#ifdef SDL_VIDEO_OPENGL
42#include <SDL3/SDL_opengl.h>
43#endif // SDL_VIDEO_OPENGL
44
45#if defined(SDL_VIDEO_OPENGL_ES) && !defined(SDL_VIDEO_OPENGL)
46#include <SDL3/SDL_opengles.h>
47#endif // SDL_VIDEO_OPENGL_ES && !SDL_VIDEO_OPENGL
48
49// GL and GLES2 headers conflict on Linux 32 bits
50#if defined(SDL_VIDEO_OPENGL_ES2) && !defined(SDL_VIDEO_OPENGL)
51#include <SDL3/SDL_opengles2.h>
52#endif // SDL_VIDEO_OPENGL_ES2 && !SDL_VIDEO_OPENGL
53
54// GL_CONTEXT_RELEASE_BEHAVIOR and GL_CONTEXT_RELEASE_BEHAVIOR_KHR have the same number.
55#ifndef GL_CONTEXT_RELEASE_BEHAVIOR
56#define GL_CONTEXT_RELEASE_BEHAVIOR 0x82FB
57#endif
58
59// GL_CONTEXT_RELEASE_BEHAVIOR_FLUSH and GL_CONTEXT_RELEASE_BEHAVIOR_FLUSH_KHR have the same number.
60#ifndef GL_CONTEXT_RELEASE_BEHAVIOR_FLUSH
61#define GL_CONTEXT_RELEASE_BEHAVIOR_FLUSH 0x82FC
62#endif
63
64#ifdef SDL_PLATFORM_EMSCRIPTEN
65#include <emscripten.h>
66#endif
67
68#ifdef SDL_PLATFORM_3DS
69#include <3ds.h>
70#endif
71
72#ifdef SDL_PLATFORM_LINUX
73#include <sys/types.h>
74#include <sys/stat.h>
75#include <unistd.h>
76#endif
77
78// Available video drivers
79static VideoBootStrap *bootstrap[] = {
80#ifdef SDL_VIDEO_DRIVER_PRIVATE
81 &PRIVATE_bootstrap,
82#endif
83#ifdef SDL_VIDEO_DRIVER_COCOA
84 &COCOA_bootstrap,
85#endif
86#ifdef SDL_VIDEO_DRIVER_X11
87#ifdef SDL_VIDEO_DRIVER_WAYLAND
88 &Wayland_preferred_bootstrap,
89#endif
90 &X11_bootstrap,
91#endif
92#ifdef SDL_VIDEO_DRIVER_WAYLAND
93 &Wayland_bootstrap,
94#endif
95#ifdef SDL_VIDEO_DRIVER_VIVANTE
96 &VIVANTE_bootstrap,
97#endif
98#ifdef SDL_VIDEO_DRIVER_WINDOWS
99 &WINDOWS_bootstrap,
100#endif
101#ifdef SDL_VIDEO_DRIVER_HAIKU
102 &HAIKU_bootstrap,
103#endif
104#ifdef SDL_VIDEO_DRIVER_UIKIT
105 &UIKIT_bootstrap,
106#endif
107#ifdef SDL_VIDEO_DRIVER_ANDROID
108 &Android_bootstrap,
109#endif
110#ifdef SDL_VIDEO_DRIVER_PS2
111 &PS2_bootstrap,
112#endif
113#ifdef SDL_VIDEO_DRIVER_PSP
114 &PSP_bootstrap,
115#endif
116#ifdef SDL_VIDEO_DRIVER_VITA
117 &VITA_bootstrap,
118#endif
119#ifdef SDL_VIDEO_DRIVER_N3DS
120 &N3DS_bootstrap,
121#endif
122#ifdef SDL_VIDEO_DRIVER_KMSDRM
123 &KMSDRM_bootstrap,
124#endif
125#ifdef SDL_VIDEO_DRIVER_RISCOS
126 &RISCOS_bootstrap,
127#endif
128#ifdef SDL_VIDEO_DRIVER_RPI
129 &RPI_bootstrap,
130#endif
131#ifdef SDL_VIDEO_DRIVER_EMSCRIPTEN
132 &Emscripten_bootstrap,
133#endif
134#ifdef SDL_VIDEO_DRIVER_QNX
135 &QNX_bootstrap,
136#endif
137#ifdef SDL_VIDEO_DRIVER_OFFSCREEN
138 &OFFSCREEN_bootstrap,
139#endif
140#ifdef SDL_VIDEO_DRIVER_DUMMY
141 &DUMMY_bootstrap,
142#ifdef SDL_INPUT_LINUXEV
143 &DUMMY_evdev_bootstrap,
144#endif
145#endif
146#ifdef SDL_VIDEO_DRIVER_OPENVR
147 &OPENVR_bootstrap,
148#endif
149 NULL
150};
151
152#define CHECK_WINDOW_MAGIC(window, result) \
153 if (!_this) { \
154 SDL_UninitializedVideo(); \
155 return result; \
156 } \
157 if (!SDL_ObjectValid(window, SDL_OBJECT_TYPE_WINDOW)) { \
158 SDL_SetError("Invalid window"); \
159 return result; \
160 }
161
162#define CHECK_DISPLAY_MAGIC(display, result) \
163 if (!display) { \
164 return result; \
165 } \
166
167#define CHECK_WINDOW_NOT_POPUP(window, result) \
168 if (SDL_WINDOW_IS_POPUP(window)) { \
169 SDL_SetError("Operation invalid on popup windows"); \
170 return result; \
171 }
172
173#if defined(SDL_PLATFORM_MACOS) && defined(SDL_VIDEO_DRIVER_COCOA)
174// Support for macOS fullscreen spaces
175extern bool Cocoa_IsWindowInFullscreenSpace(SDL_Window *window);
176extern bool Cocoa_SetWindowFullscreenSpace(SDL_Window *window, bool state, bool blocking);
177#endif
178
179#ifdef SDL_VIDEO_DRIVER_UIKIT
180extern void SDL_UpdateLifecycleObserver(void);
181#endif
182
183static void SDL_CheckWindowDisplayChanged(SDL_Window *window);
184static void SDL_CheckWindowDisplayScaleChanged(SDL_Window *window);
185static void SDL_CheckWindowSafeAreaChanged(SDL_Window *window);
186
187// Convenience functions for reading driver flags
188static bool SDL_ModeSwitchingEmulated(SDL_VideoDevice *_this)
189{
190 if (_this->device_caps & VIDEO_DEVICE_CAPS_MODE_SWITCHING_EMULATED) {
191 return true;
192 }
193 return false;
194}
195
196static bool SDL_SendsFullscreenDimensions(SDL_VideoDevice *_this)
197{
198 return !!(_this->device_caps & VIDEO_DEVICE_CAPS_SENDS_FULLSCREEN_DIMENSIONS);
199}
200
201static bool IsFullscreenOnly(SDL_VideoDevice *_this)
202{
203 return !!(_this->device_caps & VIDEO_DEVICE_CAPS_FULLSCREEN_ONLY);
204}
205
206static bool SDL_SendsDisplayChanges(SDL_VideoDevice *_this)
207{
208 return !!(_this->device_caps & VIDEO_DEVICE_CAPS_SENDS_DISPLAY_CHANGES);
209}
210
211static bool SDL_DisableMouseWarpOnFullscreenTransitions(SDL_VideoDevice *_this)
212{
213 return !!(_this->device_caps & VIDEO_DEVICE_CAPS_DISABLE_MOUSE_WARP_ON_FULLSCREEN_TRANSITIONS);
214}
215
216static bool SDL_DriverSendsHDRChanges(SDL_VideoDevice *_this)
217{
218 return !!(_this->device_caps & VIDEO_DEVICE_CAPS_SENDS_HDR_CHANGES);
219}
220
221// Hint to treat all window ops as synchronous
222static bool syncHint;
223
224static void SDL_SyncHintWatcher(void *userdata, const char *name, const char *oldValue, const char *newValue)
225{
226 syncHint = SDL_GetStringBoolean(newValue, false);
227}
228
229static void SDL_SyncIfRequired(SDL_Window *window)
230{
231 if (syncHint) {
232 SDL_SyncWindow(window);
233 }
234}
235
236static void SDL_UpdateWindowHierarchy(SDL_Window *window, SDL_Window *parent)
237{
238 // Unlink the window from the existing parent.
239 if (window->parent) {
240 if (window->next_sibling) {
241 window->next_sibling->prev_sibling = window->prev_sibling;
242 }
243 if (window->prev_sibling) {
244 window->prev_sibling->next_sibling = window->next_sibling;
245 } else {
246 window->parent->first_child = window->next_sibling;
247 }
248
249 window->parent = NULL;
250 }
251
252 if (parent) {
253 window->parent = parent;
254
255 window->next_sibling = parent->first_child;
256 if (parent->first_child) {
257 parent->first_child->prev_sibling = window;
258 }
259 parent->first_child = window;
260 }
261}
262
263// Support for framebuffer emulation using an accelerated renderer
264
265#define SDL_PROP_WINDOW_TEXTUREDATA_POINTER "SDL.internal.window.texturedata"
266
267typedef struct
268{
269 SDL_Renderer *renderer;
270 SDL_Texture *texture;
271 void *pixels;
272 int pitch;
273 int bytes_per_pixel;
274} SDL_WindowTextureData;
275
276static Uint32 SDL_DefaultGraphicsBackends(SDL_VideoDevice *_this)
277{
278#if (defined(SDL_VIDEO_OPENGL) && defined(SDL_PLATFORM_MACOS)) || (defined(SDL_PLATFORM_IOS) && !TARGET_OS_MACCATALYST)
279 if (_this->GL_CreateContext) {
280 return SDL_WINDOW_OPENGL;
281 }
282#endif
283#if defined(SDL_VIDEO_METAL) && (TARGET_OS_MACCATALYST || defined(SDL_PLATFORM_MACOS) || defined(SDL_PLATFORM_IOS))
284 if (_this->Metal_CreateView) {
285 return SDL_WINDOW_METAL;
286 }
287#endif
288#if defined(SDL_VIDEO_OPENGL) && defined(SDL_VIDEO_DRIVER_OPENVR)
289 if (SDL_strcmp(_this->name, "openvr") == 0) {
290 return SDL_WINDOW_OPENGL;
291 }
292#endif
293 return 0;
294}
295
296static void SDLCALL SDL_CleanupWindowTextureData(void *userdata, void *value)
297{
298 SDL_WindowTextureData *data = (SDL_WindowTextureData *)value;
299
300 if (data->texture) {
301 SDL_DestroyTexture(data->texture);
302 }
303 if (data->renderer) {
304 SDL_DestroyRenderer(data->renderer);
305 }
306 SDL_free(data->pixels);
307 SDL_free(data);
308}
309
310static bool SDL_CreateWindowTexture(SDL_VideoDevice *_this, SDL_Window *window, SDL_PixelFormat *format, void **pixels, int *pitch)
311{
312 SDL_PropertiesID props = SDL_GetWindowProperties(window);
313 SDL_WindowTextureData *data = (SDL_WindowTextureData *)SDL_GetPointerProperty(props, SDL_PROP_WINDOW_TEXTUREDATA_POINTER, NULL);
314 const bool transparent = (window->flags & SDL_WINDOW_TRANSPARENT) ? true : false;
315 int i;
316 int w, h;
317 const SDL_PixelFormat *texture_formats;
318
319 SDL_GetWindowSizeInPixels(window, &w, &h);
320
321 if (!data) {
322 SDL_Renderer *renderer = NULL;
323 const char *render_driver = NULL;
324
325 // See if there's a render driver being requested
326 const char *hint = SDL_GetHint(SDL_HINT_FRAMEBUFFER_ACCELERATION);
327 if (hint && *hint != '0' && *hint != '1' &&
328 SDL_strcasecmp(hint, "true") != 0 &&
329 SDL_strcasecmp(hint, "false") != 0 &&
330 SDL_strcasecmp(hint, SDL_SOFTWARE_RENDERER) != 0) {
331 render_driver = hint;
332 }
333
334 if (!render_driver) {
335 render_driver = SDL_GetHint(SDL_HINT_RENDER_DRIVER);
336 }
337 if (render_driver && SDL_strcasecmp(render_driver, SDL_SOFTWARE_RENDERER) == 0) {
338 render_driver = NULL;
339 }
340
341 char *render_driver_copy = NULL;
342 if (render_driver && *render_driver) {
343 render_driver_copy = SDL_strdup(render_driver);
344 render_driver = render_driver_copy;
345 if (render_driver_copy) { // turn any "software" requests into "xxxxxxxx" so we don't end up in infinite recursion.
346 char *prev = render_driver_copy;
347 char *ptr = prev;
348 while ((ptr = SDL_strchr(ptr, ',')) != NULL) {
349 *ptr = '\0';
350 const bool is_sw = (SDL_strcasecmp(prev, SDL_SOFTWARE_RENDERER) == 0);
351 *ptr = ',';
352 if (is_sw) {
353 SDL_memset(prev, 'x', SDL_strlen(SDL_SOFTWARE_RENDERER));
354 ptr = prev;
355 } else {
356 ptr++;
357 prev = ptr;
358 }
359 }
360
361 if (SDL_strcasecmp(prev, SDL_SOFTWARE_RENDERER) == 0) {
362 SDL_memset(prev, 'x', SDL_strlen(SDL_SOFTWARE_RENDERER));
363 }
364 }
365 }
366
367 // Check to see if there's a specific driver requested
368 if (render_driver) {
369 renderer = SDL_CreateRenderer(window, render_driver);
370 SDL_free(render_driver_copy);
371 if (!renderer) {
372 // The error for this specific renderer has already been set
373 return false;
374 }
375 } else {
376 SDL_assert(render_driver_copy == NULL);
377 const int total = SDL_GetNumRenderDrivers();
378 for (i = 0; i < total; ++i) {
379 const char *name = SDL_GetRenderDriver(i);
380 if (name && SDL_strcmp(name, SDL_SOFTWARE_RENDERER) != 0) {
381 renderer = SDL_CreateRenderer(window, name);
382 if (renderer) {
383 break; // this will work.
384 }
385 }
386 }
387 if (!renderer) {
388 return SDL_SetError("No hardware accelerated renderers available");
389 }
390 }
391
392 SDL_assert(renderer != NULL); // should have explicitly checked this above.
393
394 // Create the data after we successfully create the renderer (bug #1116)
395 data = (SDL_WindowTextureData *)SDL_calloc(1, sizeof(*data));
396 if (!data) {
397 SDL_DestroyRenderer(renderer);
398 return false;
399 }
400 if (!SDL_SetPointerPropertyWithCleanup(props, SDL_PROP_WINDOW_TEXTUREDATA_POINTER, data, SDL_CleanupWindowTextureData, NULL)) {
401 SDL_DestroyRenderer(renderer);
402 return false;
403 }
404
405 data->renderer = renderer;
406 }
407
408 texture_formats = (const SDL_PixelFormat *)SDL_GetPointerProperty(SDL_GetRendererProperties(data->renderer), SDL_PROP_RENDERER_TEXTURE_FORMATS_POINTER, NULL);
409 if (!texture_formats) {
410 return false;
411 }
412
413 // Free any old texture and pixel data
414 if (data->texture) {
415 SDL_DestroyTexture(data->texture);
416 data->texture = NULL;
417 }
418 SDL_free(data->pixels);
419 data->pixels = NULL;
420
421 // Find the first format with or without an alpha channel
422 *format = texture_formats[0];
423
424 for (i = 0; texture_formats[i] != SDL_PIXELFORMAT_UNKNOWN; ++i) {
425 SDL_PixelFormat texture_format = texture_formats[i];
426 if (!SDL_ISPIXELFORMAT_FOURCC(texture_format) &&
427 !SDL_ISPIXELFORMAT_10BIT(texture_format) &&
428 !SDL_ISPIXELFORMAT_FLOAT(texture_format) &&
429 transparent == SDL_ISPIXELFORMAT_ALPHA(texture_format)) {
430 *format = texture_format;
431 break;
432 }
433 }
434
435 data->texture = SDL_CreateTexture(data->renderer, *format,
436 SDL_TEXTUREACCESS_STREAMING,
437 w, h);
438 if (!data->texture) {
439 // codechecker_false_positive [Malloc] Static analyzer doesn't realize allocated `data` is saved to SDL_PROP_WINDOW_TEXTUREDATA_POINTER and not leaked here.
440 return false; // NOLINT(clang-analyzer-unix.Malloc)
441 }
442
443 // Create framebuffer data
444 data->bytes_per_pixel = SDL_BYTESPERPIXEL(*format);
445 data->pitch = (((w * data->bytes_per_pixel) + 3) & ~3);
446
447 {
448 // Make static analysis happy about potential SDL_malloc(0) calls.
449 const size_t allocsize = (size_t)h * data->pitch;
450 data->pixels = SDL_malloc((allocsize > 0) ? allocsize : 1);
451 if (!data->pixels) {
452 return false;
453 }
454 }
455
456 *pixels = data->pixels;
457 *pitch = data->pitch;
458
459 // Make sure we're not double-scaling the viewport
460 SDL_SetRenderViewport(data->renderer, NULL);
461
462 return true;
463}
464
465bool SDL_SetWindowTextureVSync(SDL_VideoDevice *_this, SDL_Window *window, int vsync)
466{
467 SDL_WindowTextureData *data;
468
469 data = (SDL_WindowTextureData *)SDL_GetPointerProperty(SDL_GetWindowProperties(window), SDL_PROP_WINDOW_TEXTUREDATA_POINTER, NULL);
470 if (!data) {
471 return false;
472 }
473 if (!data->renderer) {
474 return false;
475 }
476 return SDL_SetRenderVSync(data->renderer, vsync);
477}
478
479static bool SDL_GetWindowTextureVSync(SDL_VideoDevice *_this, SDL_Window *window, int *vsync)
480{
481 SDL_WindowTextureData *data;
482
483 data = (SDL_WindowTextureData *)SDL_GetPointerProperty(SDL_GetWindowProperties(window), SDL_PROP_WINDOW_TEXTUREDATA_POINTER, NULL);
484 if (!data) {
485 return false;
486 }
487 if (!data->renderer) {
488 return false;
489 }
490 return SDL_GetRenderVSync(data->renderer, vsync);
491}
492
493static bool SDL_UpdateWindowTexture(SDL_VideoDevice *_this, SDL_Window *window, const SDL_Rect *rects, int numrects)
494{
495 SDL_WindowTextureData *data;
496 SDL_Rect rect;
497 void *src;
498 int w, h;
499
500 SDL_GetWindowSizeInPixels(window, &w, &h);
501
502 data = (SDL_WindowTextureData *)SDL_GetPointerProperty(SDL_GetWindowProperties(window), SDL_PROP_WINDOW_TEXTUREDATA_POINTER, NULL);
503 if (!data || !data->texture) {
504 return SDL_SetError("No window texture data");
505 }
506
507 // Update a single rect that contains subrects for best DMA performance
508 if (SDL_GetSpanEnclosingRect(w, h, numrects, rects, &rect)) {
509 src = (void *)((Uint8 *)data->pixels +
510 rect.y * data->pitch +
511 rect.x * data->bytes_per_pixel);
512 if (!SDL_UpdateTexture(data->texture, &rect, src, data->pitch)) {
513 return false;
514 }
515
516 if (!SDL_RenderTexture(data->renderer, data->texture, NULL, NULL)) {
517 return false;
518 }
519
520 SDL_RenderPresent(data->renderer);
521 }
522 return true;
523}
524
525static void SDL_DestroyWindowTexture(SDL_VideoDevice *_this, SDL_Window *window)
526{
527 SDL_ClearProperty(SDL_GetWindowProperties(window), SDL_PROP_WINDOW_TEXTUREDATA_POINTER);
528}
529
530static SDL_VideoDevice *_this = NULL;
531static SDL_AtomicInt SDL_messagebox_count;
532
533static int SDLCALL cmpmodes(const void *A, const void *B)
534{
535 const SDL_DisplayMode *a = (const SDL_DisplayMode *)A;
536 const SDL_DisplayMode *b = (const SDL_DisplayMode *)B;
537 int a_refresh_rate = (int)(a->refresh_rate * 100);
538 int b_refresh_rate = (int)(b->refresh_rate * 100);
539 int a_pixel_density = (int)(a->pixel_density * 100);
540 int b_pixel_density = (int)(b->pixel_density * 100);
541
542 if (a->w != b->w) {
543 return b->w - a->w;
544 } else if (a->h != b->h) {
545 return b->h - a->h;
546 } else if (SDL_BITSPERPIXEL(a->format) != SDL_BITSPERPIXEL(b->format)) {
547 return SDL_BITSPERPIXEL(b->format) - SDL_BITSPERPIXEL(a->format);
548 } else if (SDL_PIXELLAYOUT(a->format) != SDL_PIXELLAYOUT(b->format)) {
549 return SDL_PIXELLAYOUT(b->format) - SDL_PIXELLAYOUT(a->format);
550 } else if (a_refresh_rate != b_refresh_rate) {
551 return b_refresh_rate - a_refresh_rate;
552 } else if (a_pixel_density != b_pixel_density) {
553 return a_pixel_density - b_pixel_density;
554 }
555 return 0;
556}
557
558bool SDL_UninitializedVideo(void)
559{
560 return SDL_SetError("Video subsystem has not been initialized");
561}
562
563// Deduplicated list of video bootstrap drivers.
564static const VideoBootStrap *deduped_bootstrap[SDL_arraysize(bootstrap) - 1];
565
566int SDL_GetNumVideoDrivers(void)
567{
568 static int num_drivers = -1;
569
570 if (num_drivers >= 0) {
571 return num_drivers;
572 }
573
574 num_drivers = 0;
575
576 // Build a list of unique video drivers.
577 for (int i = 0; bootstrap[i] != NULL; ++i) {
578 bool duplicate = false;
579 for (int j = 0; j < i; ++j) {
580 if (SDL_strcmp(bootstrap[i]->name, bootstrap[j]->name) == 0) {
581 duplicate = true;
582 break;
583 }
584 }
585
586 if (!duplicate) {
587 deduped_bootstrap[num_drivers++] = bootstrap[i];
588 }
589 }
590
591 return num_drivers;
592}
593
594const char *SDL_GetVideoDriver(int index)
595{
596 if (index >= 0 && index < SDL_GetNumVideoDrivers()) {
597 return deduped_bootstrap[index]->name;
598 }
599 SDL_InvalidParamError("index");
600 return NULL;
601}
602
603/*
604 * Initialize the video and event subsystems -- determine native pixel format
605 */
606bool SDL_VideoInit(const char *driver_name)
607{
608 SDL_VideoDevice *video;
609 bool init_events = false;
610 bool init_keyboard = false;
611 bool init_mouse = false;
612 bool init_touch = false;
613 bool init_pen = false;
614 int i = 0;
615
616 // Check to make sure we don't overwrite '_this'
617 if (_this) {
618 SDL_VideoQuit();
619 }
620
621 SDL_InitTicks();
622
623 // Start the event loop
624 if (!SDL_InitSubSystem(SDL_INIT_EVENTS)) {
625 goto pre_driver_error;
626 }
627 init_events = true;
628 if (!SDL_InitKeyboard()) {
629 goto pre_driver_error;
630 }
631 init_keyboard = true;
632 if (!SDL_PreInitMouse()) {
633 goto pre_driver_error;
634 }
635 init_mouse = true;
636 if (!SDL_InitTouch()) {
637 goto pre_driver_error;
638 }
639 init_touch = true;
640 if (!SDL_InitPen()) {
641 goto pre_driver_error;
642 }
643 init_pen = true;
644
645 // Select the proper video driver
646 video = NULL;
647 if (!driver_name) {
648 driver_name = SDL_GetHint(SDL_HINT_VIDEO_DRIVER);
649 }
650 if (driver_name && *driver_name != 0) {
651 const char *driver_attempt = driver_name;
652 while (driver_attempt && *driver_attempt != 0 && !video) {
653 const char *driver_attempt_end = SDL_strchr(driver_attempt, ',');
654 size_t driver_attempt_len = (driver_attempt_end) ? (driver_attempt_end - driver_attempt)
655 : SDL_strlen(driver_attempt);
656
657 for (i = 0; bootstrap[i]; ++i) {
658 if (!bootstrap[i]->is_preferred &&
659 (driver_attempt_len == SDL_strlen(bootstrap[i]->name)) &&
660 (SDL_strncasecmp(bootstrap[i]->name, driver_attempt, driver_attempt_len) == 0)) {
661 video = bootstrap[i]->create();
662 if (video) {
663 break;
664 }
665 }
666 }
667
668 driver_attempt = (driver_attempt_end) ? (driver_attempt_end + 1) : NULL;
669 }
670 } else {
671 for (i = 0; bootstrap[i]; ++i) {
672 video = bootstrap[i]->create();
673 if (video) {
674 break;
675 }
676 }
677 }
678 if (!video) {
679 if (driver_name) {
680 SDL_SetError("%s not available", driver_name);
681 goto pre_driver_error;
682 }
683 SDL_SetError("No available video device");
684 goto pre_driver_error;
685 }
686
687 /* From this point on, use SDL_VideoQuit to cleanup on error, rather than
688 pre_driver_error. */
689 _this = video;
690 _this->name = bootstrap[i]->name;
691 _this->thread = SDL_GetCurrentThreadID();
692
693 // Set some very sane GL defaults
694 _this->gl_config.driver_loaded = 0;
695 _this->gl_config.dll_handle = NULL;
696 SDL_GL_ResetAttributes();
697
698 // Initialize the video subsystem
699 if (!_this->VideoInit(_this)) {
700 SDL_VideoQuit();
701 return false;
702 }
703
704 // Make sure some displays were added
705 if (_this->num_displays == 0) {
706 SDL_VideoQuit();
707 return SDL_SetError("The video driver did not add any displays");
708 }
709
710 SDL_AddHintCallback(SDL_HINT_VIDEO_SYNC_WINDOW_OPERATIONS, SDL_SyncHintWatcher, NULL);
711
712 /* Disable the screen saver by default. This is a change from <= 2.0.1,
713 but most things using SDL are games or media players; you wouldn't
714 want a screensaver to trigger if you're playing exclusively with a
715 joystick, or passively watching a movie. Things that use SDL but
716 function more like a normal desktop app should explicitly reenable the
717 screensaver. */
718 if (!SDL_GetHintBoolean(SDL_HINT_VIDEO_ALLOW_SCREENSAVER, false)) {
719 SDL_DisableScreenSaver();
720 }
721
722 SDL_PostInitMouse();
723
724 // We're ready to go!
725 return true;
726
727pre_driver_error:
728 SDL_assert(_this == NULL);
729 if (init_pen) {
730 SDL_QuitPen();
731 }
732 if (init_touch) {
733 SDL_QuitTouch();
734 }
735 if (init_mouse) {
736 SDL_QuitMouse();
737 }
738 if (init_keyboard) {
739 SDL_QuitKeyboard();
740 }
741 if (init_events) {
742 SDL_QuitSubSystem(SDL_INIT_EVENTS);
743 }
744 return false;
745}
746
747const char *SDL_GetCurrentVideoDriver(void)
748{
749 if (!_this) {
750 SDL_UninitializedVideo();
751 return NULL;
752 }
753 return _this->name;
754}
755
756SDL_VideoDevice *SDL_GetVideoDevice(void)
757{
758 return _this;
759}
760
761bool SDL_OnVideoThread(void)
762{
763 return (_this && SDL_GetCurrentThreadID() == _this->thread);
764}
765
766void SDL_SetSystemTheme(SDL_SystemTheme theme)
767{
768 if (_this && theme != _this->system_theme) {
769 _this->system_theme = theme;
770 SDL_SendSystemThemeChangedEvent();
771 }
772}
773
774SDL_SystemTheme SDL_GetSystemTheme(void)
775{
776 if (_this) {
777 return _this->system_theme;
778 } else {
779 return SDL_SYSTEM_THEME_UNKNOWN;
780 }
781}
782
783void SDL_UpdateDesktopBounds(void)
784{
785 SDL_Rect rect;
786 SDL_zero(rect);
787
788 SDL_DisplayID *displays = SDL_GetDisplays(NULL);
789 if (displays) {
790 for (int i = 0; displays[i]; ++i) {
791 SDL_Rect bounds;
792 if (SDL_GetDisplayBounds(displays[i], &bounds)) {
793 if (i == 0) {
794 SDL_copyp(&rect, &bounds);
795 } else {
796 SDL_GetRectUnion(&rect, &bounds, &rect);
797 }
798 }
799 }
800 SDL_free(displays);
801 }
802 SDL_copyp(&_this->desktop_bounds, &rect);
803}
804
805static void SDL_FinalizeDisplayMode(SDL_DisplayMode *mode)
806{
807 // Make sure all the fields are set up correctly
808 if (mode->pixel_density <= 0.0f) {
809 mode->pixel_density = 1.0f;
810 }
811
812 if (mode->refresh_rate_numerator > 0) {
813 if (mode->refresh_rate_denominator <= 0) {
814 mode->refresh_rate_denominator = 1;
815 }
816 mode->refresh_rate = ((100 * (Sint64)mode->refresh_rate_numerator) / mode->refresh_rate_denominator) / 100.0f;
817 } else {
818 SDL_CalculateFraction(mode->refresh_rate, &mode->refresh_rate_numerator, &mode->refresh_rate_denominator);
819 mode->refresh_rate = (int)(mode->refresh_rate * 100) / 100.0f;
820 }
821}
822
823SDL_DisplayID SDL_AddBasicVideoDisplay(const SDL_DisplayMode *desktop_mode)
824{
825 SDL_VideoDisplay display;
826
827 SDL_zero(display);
828 if (desktop_mode) {
829 SDL_memcpy(&display.desktop_mode, desktop_mode, sizeof(display.desktop_mode));
830 }
831 return SDL_AddVideoDisplay(&display, false);
832}
833
834SDL_DisplayID SDL_AddVideoDisplay(const SDL_VideoDisplay *display, bool send_event)
835{
836 SDL_VideoDisplay **displays, *new_display;
837 SDL_DisplayID id;
838 SDL_PropertiesID props;
839 int i;
840
841 new_display = (SDL_VideoDisplay *)SDL_malloc(sizeof(*new_display));
842 if (!new_display) {
843 return true;
844 }
845
846 displays = (SDL_VideoDisplay **)SDL_realloc(_this->displays, (_this->num_displays + 1) * sizeof(*displays));
847 if (!displays) {
848 SDL_free(new_display);
849 return true;
850 }
851 _this->displays = displays;
852 _this->displays[_this->num_displays++] = new_display;
853
854 id = SDL_GetNextObjectID();
855 SDL_copyp(new_display, display);
856 new_display->id = id;
857 new_display->device = _this;
858 if (display->name) {
859 new_display->name = SDL_strdup(display->name);
860 } else {
861 char name[32];
862
863 SDL_itoa(id, name, 10);
864 new_display->name = SDL_strdup(name);
865 }
866 if (new_display->content_scale == 0.0f) {
867 new_display->content_scale = 1.0f;
868 }
869
870 new_display->desktop_mode.displayID = id;
871 new_display->current_mode = &new_display->desktop_mode;
872 SDL_FinalizeDisplayMode(&new_display->desktop_mode);
873
874 for (i = 0; i < new_display->num_fullscreen_modes; ++i) {
875 new_display->fullscreen_modes[i].displayID = id;
876 }
877
878 new_display->HDR.HDR_headroom = SDL_max(display->HDR.HDR_headroom, 1.0f);
879 new_display->HDR.SDR_white_level = SDL_max(display->HDR.SDR_white_level, 1.0f);
880
881 props = SDL_GetDisplayProperties(id);
882 SDL_SetBooleanProperty(props, SDL_PROP_DISPLAY_HDR_ENABLED_BOOLEAN, new_display->HDR.HDR_headroom > 1.0f);
883
884 SDL_UpdateDesktopBounds();
885
886 if (send_event) {
887 SDL_SendDisplayEvent(new_display, SDL_EVENT_DISPLAY_ADDED, 0, 0);
888 }
889
890 return id;
891}
892
893void SDL_OnDisplayAdded(SDL_VideoDisplay *display)
894{
895 SDL_Window *window;
896
897 // See if any windows have changed to the new display
898 for (window = _this->windows; window; window = window->next) {
899 SDL_CheckWindowDisplayChanged(window);
900 }
901}
902
903void SDL_OnDisplayMoved(SDL_VideoDisplay *display)
904{
905 SDL_UpdateDesktopBounds();
906}
907
908void SDL_DelVideoDisplay(SDL_DisplayID displayID, bool send_event)
909{
910 SDL_VideoDisplay *display;
911 int display_index = SDL_GetDisplayIndex(displayID);
912 if (display_index < 0) {
913 return;
914 }
915
916 display = _this->displays[display_index];
917
918 if (send_event) {
919 SDL_SendDisplayEvent(display, SDL_EVENT_DISPLAY_REMOVED, 0, 0);
920 }
921
922 SDL_DestroyProperties(display->props);
923 SDL_free(display->name);
924 SDL_ResetFullscreenDisplayModes(display);
925 SDL_free(display->desktop_mode.internal);
926 display->desktop_mode.internal = NULL;
927 SDL_free(display->internal);
928 display->internal = NULL;
929 SDL_free(display);
930
931 if (display_index < (_this->num_displays - 1)) {
932 SDL_memmove(&_this->displays[display_index], &_this->displays[display_index + 1], (_this->num_displays - display_index - 1) * sizeof(_this->displays[display_index]));
933 }
934 --_this->num_displays;
935
936 SDL_UpdateDesktopBounds();
937}
938
939SDL_DisplayID *SDL_GetDisplays(int *count)
940{
941 int i;
942 SDL_DisplayID *displays;
943
944 if (!_this) {
945 if (count) {
946 *count = 0;
947 }
948
949 SDL_UninitializedVideo();
950 return NULL;
951 }
952
953 displays = (SDL_DisplayID *)SDL_malloc((_this->num_displays + 1) * sizeof(*displays));
954 if (displays) {
955 if (count) {
956 *count = _this->num_displays;
957 }
958
959 for (i = 0; i < _this->num_displays; ++i) {
960 displays[i] = _this->displays[i]->id;
961 }
962 displays[i] = 0;
963 } else {
964 if (count) {
965 *count = 0;
966 }
967 }
968 return displays;
969}
970
971SDL_VideoDisplay *SDL_GetVideoDisplay(SDL_DisplayID displayID)
972{
973 int display_index;
974
975 display_index = SDL_GetDisplayIndex(displayID);
976 if (display_index < 0) {
977 return NULL;
978 }
979 return _this->displays[display_index];
980}
981
982SDL_VideoDisplay *SDL_GetVideoDisplayForWindow(SDL_Window *window)
983{
984 return SDL_GetVideoDisplay(SDL_GetDisplayForWindow(window));
985}
986
987SDL_DisplayID SDL_GetPrimaryDisplay(void)
988{
989 if (!_this || _this->num_displays == 0) {
990 SDL_UninitializedVideo();
991 return 0;
992 }
993 return _this->displays[0]->id;
994}
995
996int SDL_GetDisplayIndex(SDL_DisplayID displayID)
997{
998 int display_index;
999
1000 if (!_this) {
1001 SDL_UninitializedVideo();
1002 return -1;
1003 }
1004
1005 for (display_index = 0; display_index < _this->num_displays; ++display_index) {
1006 if (displayID == _this->displays[display_index]->id) {
1007 return display_index;
1008 }
1009 }
1010 SDL_SetError("Invalid display");
1011 return -1;
1012}
1013
1014SDL_DisplayData *SDL_GetDisplayDriverData(SDL_DisplayID displayID)
1015{
1016 SDL_VideoDisplay *display = SDL_GetVideoDisplay(displayID);
1017
1018 CHECK_DISPLAY_MAGIC(display, NULL);
1019
1020 return display->internal;
1021}
1022
1023SDL_DisplayData *SDL_GetDisplayDriverDataForWindow(SDL_Window *window)
1024{
1025 return SDL_GetDisplayDriverData(SDL_GetDisplayForWindow(window));
1026}
1027
1028SDL_PropertiesID SDL_GetDisplayProperties(SDL_DisplayID displayID)
1029{
1030 SDL_VideoDisplay *display = SDL_GetVideoDisplay(displayID);
1031
1032 CHECK_DISPLAY_MAGIC(display, 0);
1033
1034 if (display->props == 0) {
1035 display->props = SDL_CreateProperties();
1036 }
1037 return display->props;
1038}
1039
1040const char *SDL_GetDisplayName(SDL_DisplayID displayID)
1041{
1042 SDL_VideoDisplay *display = SDL_GetVideoDisplay(displayID);
1043
1044 CHECK_DISPLAY_MAGIC(display, NULL);
1045
1046 return display->name;
1047}
1048
1049bool SDL_GetDisplayBounds(SDL_DisplayID displayID, SDL_Rect *rect)
1050{
1051 SDL_VideoDisplay *display = SDL_GetVideoDisplay(displayID);
1052
1053 CHECK_DISPLAY_MAGIC(display, false);
1054
1055 if (!rect) {
1056 return SDL_InvalidParamError("rect");
1057 }
1058
1059 if (_this->GetDisplayBounds) {
1060 if (_this->GetDisplayBounds(_this, display, rect)) {
1061 return true;
1062 }
1063 }
1064
1065 // Assume that the displays are left to right
1066 if (displayID == SDL_GetPrimaryDisplay()) {
1067 rect->x = 0;
1068 rect->y = 0;
1069 } else {
1070 SDL_GetDisplayBounds(_this->displays[SDL_GetDisplayIndex(displayID) - 1]->id, rect);
1071 rect->x += rect->w;
1072 }
1073 rect->w = display->current_mode->w;
1074 rect->h = display->current_mode->h;
1075 return true;
1076}
1077
1078static int ParseDisplayUsableBoundsHint(SDL_Rect *rect)
1079{
1080 const char *hint = SDL_GetHint(SDL_HINT_DISPLAY_USABLE_BOUNDS);
1081 return hint && (SDL_sscanf(hint, "%d,%d,%d,%d", &rect->x, &rect->y, &rect->w, &rect->h) == 4);
1082}
1083
1084bool SDL_GetDisplayUsableBounds(SDL_DisplayID displayID, SDL_Rect *rect)
1085{
1086 SDL_VideoDisplay *display = SDL_GetVideoDisplay(displayID);
1087
1088 CHECK_DISPLAY_MAGIC(display, false);
1089
1090 if (!rect) {
1091 return SDL_InvalidParamError("rect");
1092 }
1093
1094 if (displayID == SDL_GetPrimaryDisplay() && ParseDisplayUsableBoundsHint(rect)) {
1095 return true;
1096 }
1097
1098 if (_this->GetDisplayUsableBounds) {
1099 if (_this->GetDisplayUsableBounds(_this, display, rect)) {
1100 return true;
1101 }
1102 }
1103
1104 // Oh well, just give the entire display bounds.
1105 return SDL_GetDisplayBounds(displayID, rect);
1106}
1107
1108SDL_DisplayOrientation SDL_GetNaturalDisplayOrientation(SDL_DisplayID displayID)
1109{
1110 SDL_VideoDisplay *display = SDL_GetVideoDisplay(displayID);
1111
1112 CHECK_DISPLAY_MAGIC(display, SDL_ORIENTATION_UNKNOWN);
1113
1114 if (display->natural_orientation != SDL_ORIENTATION_UNKNOWN) {
1115 return display->natural_orientation;
1116 } else {
1117 // Default to landscape if the driver hasn't set it
1118 return SDL_ORIENTATION_LANDSCAPE;
1119 }
1120}
1121
1122SDL_DisplayOrientation SDL_GetCurrentDisplayOrientation(SDL_DisplayID displayID)
1123{
1124 SDL_VideoDisplay *display = SDL_GetVideoDisplay(displayID);
1125
1126 CHECK_DISPLAY_MAGIC(display, SDL_ORIENTATION_UNKNOWN);
1127
1128 if (display->current_orientation != SDL_ORIENTATION_UNKNOWN) {
1129 return display->current_orientation;
1130 } else {
1131 // Default to landscape if the driver hasn't set it
1132 return SDL_ORIENTATION_LANDSCAPE;
1133 }
1134}
1135
1136void SDL_SetDisplayContentScale(SDL_VideoDisplay *display, float scale)
1137{
1138 if (scale != display->content_scale) {
1139 SDL_Window *window;
1140
1141 display->content_scale = scale;
1142 SDL_SendDisplayEvent(display, SDL_EVENT_DISPLAY_CONTENT_SCALE_CHANGED, 0, 0);
1143
1144 // Check the windows on this display
1145 for (window = _this->windows; window; window = window->next) {
1146 if (display->id == window->last_displayID) {
1147 SDL_CheckWindowDisplayScaleChanged(window);
1148 }
1149 }
1150 }
1151}
1152
1153float SDL_GetDisplayContentScale(SDL_DisplayID displayID)
1154{
1155 SDL_VideoDisplay *display = SDL_GetVideoDisplay(displayID);
1156
1157 CHECK_DISPLAY_MAGIC(display, 0.0f);
1158
1159 return display->content_scale;
1160}
1161
1162void SDL_SetWindowHDRProperties(SDL_Window *window, const SDL_HDROutputProperties *HDR, bool send_event)
1163{
1164 if (window->HDR.HDR_headroom != HDR->HDR_headroom || window->HDR.SDR_white_level != window->HDR.SDR_white_level) {
1165 SDL_PropertiesID window_props = SDL_GetWindowProperties(window);
1166
1167 SDL_SetFloatProperty(window_props, SDL_PROP_WINDOW_HDR_HEADROOM_FLOAT, SDL_max(HDR->HDR_headroom, 1.0f));
1168 SDL_SetFloatProperty(window_props, SDL_PROP_WINDOW_SDR_WHITE_LEVEL_FLOAT, SDL_max(HDR->SDR_white_level, 1.0f));
1169 SDL_SetBooleanProperty(window_props, SDL_PROP_WINDOW_HDR_ENABLED_BOOLEAN, HDR->HDR_headroom > 1.0f);
1170 SDL_copyp(&window->HDR, HDR);
1171
1172 if (send_event) {
1173 SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_HDR_STATE_CHANGED, HDR->HDR_headroom > 1.0f, 0);
1174 }
1175 }
1176}
1177
1178void SDL_SetDisplayHDRProperties(SDL_VideoDisplay *display, const SDL_HDROutputProperties *HDR)
1179{
1180 bool changed = false;
1181
1182 if (HDR->SDR_white_level != display->HDR.SDR_white_level) {
1183 display->HDR.SDR_white_level = SDL_max(HDR->SDR_white_level, 1.0f);
1184 changed = true;
1185 }
1186 if (HDR->HDR_headroom != display->HDR.HDR_headroom) {
1187 display->HDR.HDR_headroom = SDL_max(HDR->HDR_headroom, 1.0f);
1188 changed = true;
1189 }
1190 SDL_copyp(&display->HDR, HDR);
1191
1192 if (changed && !SDL_DriverSendsHDRChanges(_this)) {
1193 for (SDL_Window *w = display->device->windows; w; w = w->next) {
1194 if (SDL_GetDisplayForWindow(w) == display->id) {
1195 SDL_SetWindowHDRProperties(w, &display->HDR, true);
1196 }
1197 }
1198 }
1199}
1200
1201static void SDL_UpdateFullscreenDisplayModes(SDL_VideoDisplay *display)
1202{
1203 if (display->num_fullscreen_modes == 0 && _this->GetDisplayModes) {
1204 _this->GetDisplayModes(_this, display);
1205 }
1206}
1207
1208// Return the matching mode as a pointer into our current mode list
1209static const SDL_DisplayMode *SDL_GetFullscreenModeMatch(const SDL_DisplayMode *mode)
1210{
1211 SDL_VideoDisplay *display;
1212 SDL_DisplayMode fullscreen_mode;
1213
1214 if (mode->w <= 0 || mode->h <= 0) {
1215 // Use the desktop mode
1216 return NULL;
1217 }
1218
1219 SDL_memcpy(&fullscreen_mode, mode, sizeof(fullscreen_mode));
1220 if (fullscreen_mode.displayID == 0) {
1221 fullscreen_mode.displayID = SDL_GetPrimaryDisplay();
1222 }
1223 SDL_FinalizeDisplayMode(&fullscreen_mode);
1224
1225 mode = NULL;
1226
1227 display = SDL_GetVideoDisplay(fullscreen_mode.displayID);
1228 if (display) {
1229 SDL_UpdateFullscreenDisplayModes(display);
1230
1231 // Search for an exact match
1232 if (!mode) {
1233 for (int i = 0; i < display->num_fullscreen_modes; ++i) {
1234 if (SDL_memcmp(&fullscreen_mode, &display->fullscreen_modes[i], sizeof(fullscreen_mode)) == 0) {
1235 mode = &display->fullscreen_modes[i];
1236 break;
1237 }
1238 }
1239 }
1240
1241 // Search for a mode with the same characteristics
1242 if (!mode) {
1243 for (int i = 0; i < display->num_fullscreen_modes; ++i) {
1244 if (cmpmodes(&fullscreen_mode, &display->fullscreen_modes[i]) == 0) {
1245 mode = &display->fullscreen_modes[i];
1246 break;
1247 }
1248 }
1249 }
1250 }
1251 return mode;
1252}
1253
1254bool SDL_AddFullscreenDisplayMode(SDL_VideoDisplay *display, const SDL_DisplayMode *mode)
1255{
1256 SDL_DisplayMode *modes;
1257 SDL_DisplayMode new_mode;
1258 int i, nmodes;
1259
1260 // Finalize the mode for the display
1261 SDL_memcpy(&new_mode, mode, sizeof(new_mode));
1262 new_mode.displayID = display->id;
1263 SDL_FinalizeDisplayMode(&new_mode);
1264
1265 // Make sure we don't already have the mode in the list
1266 modes = display->fullscreen_modes;
1267 nmodes = display->num_fullscreen_modes;
1268 for (i = 0; i < nmodes; ++i) {
1269 if (cmpmodes(&new_mode, &modes[i]) == 0) {
1270 return false;
1271 }
1272 }
1273
1274 // Go ahead and add the new mode
1275 if (nmodes == display->max_fullscreen_modes) {
1276 modes = (SDL_DisplayMode *)SDL_malloc((display->max_fullscreen_modes + 32) * sizeof(*modes));
1277 if (!modes) {
1278 return false;
1279 }
1280
1281 if (display->fullscreen_modes) {
1282 // Copy the list and update the current mode pointer, if necessary.
1283 SDL_memcpy(modes, display->fullscreen_modes, nmodes * sizeof(*modes));
1284 for (i = 0; i < nmodes; ++i) {
1285 if (display->current_mode == &display->fullscreen_modes[i]) {
1286 display->current_mode = &modes[i];
1287 }
1288 }
1289
1290 SDL_free(display->fullscreen_modes);
1291 }
1292
1293 display->fullscreen_modes = modes;
1294 display->max_fullscreen_modes += 32;
1295 }
1296 SDL_memcpy(&modes[display->num_fullscreen_modes++], &new_mode, sizeof(new_mode));
1297
1298 // Re-sort video modes
1299 SDL_qsort(display->fullscreen_modes, display->num_fullscreen_modes,
1300 sizeof(SDL_DisplayMode), cmpmodes);
1301
1302 return true;
1303}
1304
1305void SDL_ResetFullscreenDisplayModes(SDL_VideoDisplay *display)
1306{
1307 int i;
1308
1309 for (i = display->num_fullscreen_modes; i--;) {
1310 SDL_free(display->fullscreen_modes[i].internal);
1311 display->fullscreen_modes[i].internal = NULL;
1312 }
1313 SDL_free(display->fullscreen_modes);
1314 display->fullscreen_modes = NULL;
1315 display->num_fullscreen_modes = 0;
1316 display->max_fullscreen_modes = 0;
1317 display->current_mode = &display->desktop_mode;
1318}
1319
1320SDL_DisplayMode **SDL_GetFullscreenDisplayModes(SDL_DisplayID displayID, int *count)
1321{
1322 int i;
1323 int num_modes;
1324 SDL_DisplayMode **result;
1325 SDL_VideoDisplay *display = SDL_GetVideoDisplay(displayID);
1326
1327 if (count) {
1328 *count = 0;
1329 }
1330
1331 CHECK_DISPLAY_MAGIC(display, NULL);
1332
1333 SDL_UpdateFullscreenDisplayModes(display);
1334
1335 num_modes = display->num_fullscreen_modes;
1336 result = (SDL_DisplayMode **)SDL_malloc((num_modes + 1) * sizeof(*result) + num_modes * sizeof(**result));
1337 if (result) {
1338 SDL_DisplayMode *modes = (SDL_DisplayMode *)((Uint8 *)result + ((num_modes + 1) * sizeof(*result)));
1339 SDL_memcpy(modes, display->fullscreen_modes, num_modes * sizeof(*modes));
1340 for (i = 0; i < num_modes; ++i) {
1341 result[i] = modes++;
1342 }
1343 result[i] = NULL;
1344
1345 if (count) {
1346 *count = num_modes;
1347 }
1348 } else {
1349 if (count) {
1350 *count = 0;
1351 }
1352 }
1353 return result;
1354}
1355
1356bool SDL_GetClosestFullscreenDisplayMode(SDL_DisplayID displayID, int w, int h, float refresh_rate, bool include_high_density_modes, SDL_DisplayMode *result)
1357{
1358 if (!result) {
1359 return SDL_InvalidParamError("closest"); // Parameter `result` is called `closest` in the header.
1360 }
1361
1362 const SDL_DisplayMode *mode, *closest = NULL;
1363 float aspect_ratio;
1364 int i;
1365 SDL_VideoDisplay *display = SDL_GetVideoDisplay(displayID);
1366
1367 SDL_zerop(result);
1368
1369 CHECK_DISPLAY_MAGIC(display, false);
1370
1371 if (h > 0) {
1372 aspect_ratio = (float)w / h;
1373 } else {
1374 aspect_ratio = 1.0f;
1375 }
1376
1377 if (refresh_rate == 0.0f) {
1378 refresh_rate = display->desktop_mode.refresh_rate;
1379 }
1380
1381 SDL_UpdateFullscreenDisplayModes(display);
1382
1383 for (i = 0; i < display->num_fullscreen_modes; ++i) {
1384 mode = &display->fullscreen_modes[i];
1385
1386 if (w > mode->w) {
1387 // Out of sorted modes large enough here
1388 break;
1389 }
1390 if (h > mode->h) {
1391 /* Wider, but not tall enough, due to a different aspect ratio.
1392 * This mode must be skipped, but closer modes may still follow */
1393 continue;
1394 }
1395 if (mode->pixel_density > 1.0f && !include_high_density_modes) {
1396 continue;
1397 }
1398 if (closest) {
1399 float current_aspect_ratio = (float)mode->w / mode->h;
1400 float closest_aspect_ratio = (float)closest->w / closest->h;
1401 if (SDL_fabsf(aspect_ratio - closest_aspect_ratio) < SDL_fabsf(aspect_ratio - current_aspect_ratio)) {
1402 // The mode we already found has a better aspect ratio match
1403 continue;
1404 }
1405
1406 if (mode->w == closest->w && mode->h == closest->h &&
1407 SDL_fabsf(closest->refresh_rate - refresh_rate) < SDL_fabsf(mode->refresh_rate - refresh_rate)) {
1408 /* We already found a mode and the new mode is further from our
1409 * refresh rate target */
1410 continue;
1411 }
1412 }
1413
1414 closest = mode;
1415 }
1416 if (!closest) {
1417 return SDL_SetError("Couldn't find any matching video modes");
1418 }
1419
1420 SDL_copyp(result, closest);
1421
1422 return true;
1423}
1424
1425static bool DisplayModeChanged(const SDL_DisplayMode *old_mode, const SDL_DisplayMode *new_mode)
1426{
1427 return ((old_mode->displayID && old_mode->displayID != new_mode->displayID) ||
1428 (old_mode->format && old_mode->format != new_mode->format) ||
1429 (old_mode->w && old_mode->h && (old_mode->w != new_mode->w ||old_mode->h != new_mode->h)) ||
1430 (old_mode->pixel_density != 0.0f && old_mode->pixel_density != new_mode->pixel_density) ||
1431 (old_mode->refresh_rate != 0.0f && old_mode->refresh_rate != new_mode->refresh_rate));
1432}
1433
1434void SDL_SetDesktopDisplayMode(SDL_VideoDisplay *display, const SDL_DisplayMode *mode)
1435{
1436 SDL_DisplayMode last_mode;
1437
1438 if (display->fullscreen_active) {
1439 // This is a temporary mode change, don't save the desktop mode
1440 return;
1441 }
1442
1443 SDL_copyp(&last_mode, &display->desktop_mode);
1444
1445 if (display->desktop_mode.internal) {
1446 SDL_free(display->desktop_mode.internal);
1447 }
1448 SDL_copyp(&display->desktop_mode, mode);
1449 display->desktop_mode.displayID = display->id;
1450 SDL_FinalizeDisplayMode(&display->desktop_mode);
1451
1452 if (DisplayModeChanged(&last_mode, &display->desktop_mode)) {
1453 SDL_SendDisplayEvent(display, SDL_EVENT_DISPLAY_DESKTOP_MODE_CHANGED, mode->w, mode->h);
1454 if (display->current_mode == &display->desktop_mode) {
1455 SDL_SendDisplayEvent(display, SDL_EVENT_DISPLAY_CURRENT_MODE_CHANGED, mode->w, mode->h);
1456 }
1457 }
1458}
1459
1460const SDL_DisplayMode *SDL_GetDesktopDisplayMode(SDL_DisplayID displayID)
1461{
1462 SDL_VideoDisplay *display = SDL_GetVideoDisplay(displayID);
1463
1464 CHECK_DISPLAY_MAGIC(display, NULL);
1465
1466 return &display->desktop_mode;
1467}
1468
1469void SDL_SetCurrentDisplayMode(SDL_VideoDisplay *display, const SDL_DisplayMode *mode)
1470{
1471 SDL_DisplayMode last_mode;
1472
1473 if (display->current_mode) {
1474 SDL_copyp(&last_mode, display->current_mode);
1475 } else {
1476 SDL_zero(last_mode);
1477 }
1478
1479 display->current_mode = mode;
1480
1481 if (DisplayModeChanged(&last_mode, mode)) {
1482 SDL_SendDisplayEvent(display, SDL_EVENT_DISPLAY_CURRENT_MODE_CHANGED, mode->w, mode->h);
1483 }
1484}
1485
1486const SDL_DisplayMode *SDL_GetCurrentDisplayMode(SDL_DisplayID displayID)
1487{
1488 SDL_VideoDisplay *display = SDL_GetVideoDisplay(displayID);
1489
1490 CHECK_DISPLAY_MAGIC(display, NULL);
1491
1492 // Make sure our mode list is updated
1493 SDL_UpdateFullscreenDisplayModes(display);
1494
1495 return display->current_mode;
1496}
1497
1498bool SDL_SetDisplayModeForDisplay(SDL_VideoDisplay *display, SDL_DisplayMode *mode)
1499{
1500 /* Mode switching is being emulated per-window; nothing to do and cannot fail,
1501 * except for XWayland, which still needs the actual mode setting call since
1502 * it's emulated via the XRandR interface.
1503 */
1504 if (SDL_ModeSwitchingEmulated(_this) && SDL_strcmp(_this->name, "x11") != 0) {
1505 return true;
1506 }
1507
1508 if (!mode) {
1509 mode = &display->desktop_mode;
1510 }
1511
1512 if (mode == display->current_mode) {
1513 return true;
1514 }
1515
1516 // Actually change the display mode
1517 if (_this->SetDisplayMode) {
1518 bool result;
1519
1520 _this->setting_display_mode = true;
1521 result = _this->SetDisplayMode(_this, display, mode);
1522 _this->setting_display_mode = false;
1523 if (!result) {
1524 return false;
1525 }
1526 }
1527
1528 SDL_SetCurrentDisplayMode(display, mode);
1529
1530 return true;
1531}
1532
1533/**
1534 * If x, y are outside of rect, snaps them to the closest point inside rect
1535 * (between rect->x, rect->y, inclusive, and rect->x + w, rect->y + h, exclusive)
1536 */
1537static void SDL_GetClosestPointOnRect(const SDL_Rect *rect, SDL_Point *point)
1538{
1539 const int right = rect->x + rect->w - 1;
1540 const int bottom = rect->y + rect->h - 1;
1541
1542 if (point->x < rect->x) {
1543 point->x = rect->x;
1544 } else if (point->x > right) {
1545 point->x = right;
1546 }
1547
1548 if (point->y < rect->y) {
1549 point->y = rect->y;
1550 } else if (point->y > bottom) {
1551 point->y = bottom;
1552 }
1553}
1554
1555static SDL_DisplayID GetDisplayForRect(int x, int y, int w, int h)
1556{
1557 int i, dist;
1558 SDL_DisplayID closest = 0;
1559 int closest_dist = 0x7FFFFFFF;
1560 SDL_Point closest_point_on_display;
1561 SDL_Point delta;
1562 SDL_Point center;
1563 center.x = x + w / 2;
1564 center.y = y + h / 2;
1565
1566 if (_this) {
1567 for (i = 0; i < _this->num_displays; ++i) {
1568 SDL_VideoDisplay *display = _this->displays[i];
1569 SDL_Rect display_rect;
1570 SDL_GetDisplayBounds(display->id, &display_rect);
1571
1572 // Check if the window is fully enclosed
1573 if (SDL_GetRectEnclosingPoints(&center, 1, &display_rect, NULL)) {
1574 return display->id;
1575 }
1576
1577 // Snap window center to the display rect
1578 closest_point_on_display = center;
1579 SDL_GetClosestPointOnRect(&display_rect, &closest_point_on_display);
1580
1581 delta.x = center.x - closest_point_on_display.x;
1582 delta.y = center.y - closest_point_on_display.y;
1583 dist = (delta.x * delta.x + delta.y * delta.y);
1584 if (dist < closest_dist) {
1585 closest = display->id;
1586 closest_dist = dist;
1587 }
1588 }
1589 }
1590
1591 if (closest == 0) {
1592 SDL_SetError("Couldn't find any displays");
1593 }
1594
1595 return closest;
1596}
1597
1598void SDL_RelativeToGlobalForWindow(SDL_Window *window, int rel_x, int rel_y, int *abs_x, int *abs_y)
1599{
1600 SDL_Window *w;
1601
1602 if (SDL_WINDOW_IS_POPUP(window)) {
1603 // Calculate the total offset of the popup from the parents
1604 for (w = window->parent; w; w = w->parent) {
1605 rel_x += w->x;
1606 rel_y += w->y;
1607
1608 if (!SDL_WINDOW_IS_POPUP(w)) {
1609 break;
1610 }
1611 }
1612 }
1613
1614 if (abs_x) {
1615 *abs_x = rel_x;
1616 }
1617 if (abs_y) {
1618 *abs_y = rel_y;
1619 }
1620}
1621
1622void SDL_GlobalToRelativeForWindow(SDL_Window *window, int abs_x, int abs_y, int *rel_x, int *rel_y)
1623{
1624 SDL_Window *w;
1625
1626 if (SDL_WINDOW_IS_POPUP(window)) {
1627 // Convert absolute window coordinates to relative for a popup
1628 for (w = window->parent; w; w = w->parent) {
1629 abs_x -= w->x;
1630 abs_y -= w->y;
1631
1632 if (!SDL_WINDOW_IS_POPUP(w)) {
1633 break;
1634 }
1635 }
1636 }
1637
1638 if (rel_x) {
1639 *rel_x = abs_x;
1640 }
1641 if (rel_y) {
1642 *rel_y = abs_y;
1643 }
1644}
1645
1646SDL_DisplayID SDL_GetDisplayForPoint(const SDL_Point *point)
1647{
1648 if (!point) {
1649 SDL_InvalidParamError("point");
1650 return 0;
1651 }
1652
1653 return GetDisplayForRect(point->x, point->y, 1, 1);
1654}
1655
1656SDL_DisplayID SDL_GetDisplayForRect(const SDL_Rect *rect)
1657{
1658 if (!rect) {
1659 SDL_InvalidParamError("rect");
1660 return 0;
1661 }
1662
1663 return GetDisplayForRect(rect->x, rect->y, rect->w, rect->h);
1664}
1665
1666SDL_DisplayID SDL_GetDisplayForWindowPosition(SDL_Window *window)
1667{
1668 int x, y;
1669 SDL_DisplayID displayID = 0;
1670
1671 CHECK_WINDOW_MAGIC(window, 0);
1672
1673 if (_this->GetDisplayForWindow) {
1674 displayID = _this->GetDisplayForWindow(_this, window);
1675 }
1676
1677 /* A backend implementation may fail to get a display for the window
1678 * (for example if the window is off-screen), but other code may expect it
1679 * to succeed in that situation, so we fall back to a generic position-
1680 * based implementation in that case. */
1681 SDL_RelativeToGlobalForWindow(window, window->x, window->y, &x, &y);
1682
1683 if (!displayID) {
1684 /* Fullscreen windows may be larger than the display if they were moved between differently sized
1685 * displays and the new position was received before the new size or vice versa. Using the center
1686 * of the window rect in this case can report the wrong display, so use the origin.
1687 */
1688 if (window->flags & SDL_WINDOW_FULLSCREEN) {
1689 displayID = GetDisplayForRect(x, y, 1, 1);
1690 } else {
1691 displayID = GetDisplayForRect(x, y, window->w, window->h);
1692 }
1693 }
1694 if (!displayID) {
1695 // Use the primary display for a window if we can't find it anywhere else
1696 displayID = SDL_GetPrimaryDisplay();
1697 }
1698 return displayID;
1699}
1700
1701SDL_VideoDisplay *SDL_GetVideoDisplayForFullscreenWindow(SDL_Window *window)
1702{
1703 SDL_DisplayID displayID = 0;
1704
1705 CHECK_WINDOW_MAGIC(window, 0);
1706
1707 // An explicit fullscreen display overrides all
1708 if (window->current_fullscreen_mode.displayID) {
1709 displayID = window->current_fullscreen_mode.displayID;
1710 }
1711
1712 /* This is used to handle the very common pattern of SDL_SetWindowPosition()
1713 * followed immediately by SDL_SetWindowFullscreen() to make the window fullscreen
1714 * desktop on a specific display. If the backend doesn't support changing the
1715 * window position, or an async window manager hasn't yet actually moved the window,
1716 * the current position won't be updated at the time of the fullscreen call.
1717 */
1718 if (!displayID) {
1719 // Use the pending position and dimensions, if available, otherwise, use the current.
1720 const int x = window->last_position_pending ? window->pending.x : window->x;
1721 const int y = window->last_position_pending ? window->pending.y : window->y;
1722 const int w = window->last_size_pending ? window->pending.w : window->w;
1723 const int h = window->last_size_pending ? window->pending.h : window->h;
1724
1725 displayID = GetDisplayForRect(x, y, w, h);
1726 }
1727 if (!displayID) {
1728 // Use the primary display for a window if we can't find it anywhere else
1729 displayID = SDL_GetPrimaryDisplay();
1730 }
1731 return SDL_GetVideoDisplay(displayID);
1732}
1733
1734SDL_DisplayID SDL_GetDisplayForWindow(SDL_Window *window)
1735{
1736 SDL_DisplayID displayID = 0;
1737
1738 CHECK_WINDOW_MAGIC(window, 0);
1739
1740 // An explicit fullscreen display overrides all
1741 if (window->flags & SDL_WINDOW_FULLSCREEN) {
1742 displayID = window->current_fullscreen_mode.displayID;
1743 }
1744
1745 if (!displayID) {
1746 displayID = SDL_GetDisplayForWindowPosition(window);
1747 }
1748 return displayID;
1749}
1750
1751static void SDL_CheckWindowDisplayChanged(SDL_Window *window)
1752{
1753 if (SDL_SendsDisplayChanges(_this)) {
1754 return;
1755 }
1756
1757 SDL_DisplayID displayID = SDL_GetDisplayForWindowPosition(window);
1758
1759 if (displayID != window->last_displayID) {
1760 int i, display_index;
1761
1762 // Sanity check our fullscreen windows
1763 display_index = SDL_GetDisplayIndex(displayID);
1764 for (i = 0; i < _this->num_displays; ++i) {
1765 SDL_VideoDisplay *display = _this->displays[i];
1766
1767 if (display->fullscreen_window == window) {
1768 if (display_index != i) {
1769 if (display_index < 0) {
1770 display_index = i;
1771 } else {
1772 SDL_VideoDisplay *new_display = _this->displays[display_index];
1773
1774 // The window was moved to a different display
1775 if (new_display->fullscreen_window &&
1776 new_display->fullscreen_window != window) {
1777 // Uh oh, there's already a fullscreen window here; minimize it
1778 SDL_MinimizeWindow(new_display->fullscreen_window);
1779 }
1780 new_display->fullscreen_window = window;
1781 display->fullscreen_window = NULL;
1782 }
1783 }
1784 break;
1785 }
1786 }
1787
1788 SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_DISPLAY_CHANGED, (int)displayID, 0);
1789 }
1790}
1791
1792float SDL_GetWindowPixelDensity(SDL_Window *window)
1793{
1794 int window_w, window_h, pixel_w, pixel_h;
1795 float pixel_density = 1.0f;
1796
1797 CHECK_WINDOW_MAGIC(window, 0.0f);
1798
1799 if (SDL_GetWindowSize(window, &window_w, &window_h) &&
1800 SDL_GetWindowSizeInPixels(window, &pixel_w, &pixel_h)) {
1801 pixel_density = (float)pixel_w / window_w;
1802 }
1803 return pixel_density;
1804}
1805
1806float SDL_GetWindowDisplayScale(SDL_Window *window)
1807{
1808 CHECK_WINDOW_MAGIC(window, 0.0f);
1809
1810 return window->display_scale;
1811}
1812
1813static void SDL_CheckWindowDisplayScaleChanged(SDL_Window *window)
1814{
1815 float display_scale;
1816
1817 if (_this->GetWindowContentScale) {
1818 display_scale = _this->GetWindowContentScale(_this, window);
1819 } else {
1820 const float pixel_density = SDL_GetWindowPixelDensity(window);
1821 const float content_scale = SDL_GetDisplayContentScale(SDL_GetDisplayForWindowPosition(window));
1822
1823 display_scale = pixel_density * content_scale;
1824 }
1825
1826 if (display_scale != window->display_scale) {
1827 window->display_scale = display_scale;
1828 SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_DISPLAY_SCALE_CHANGED, 0, 0);
1829 }
1830}
1831
1832static void SDL_RestoreMousePosition(SDL_Window *window)
1833{
1834 float x, y;
1835 SDL_Mouse *mouse = SDL_GetMouse();
1836
1837 if (window == SDL_GetMouseFocus()) {
1838 const bool prev_warp_val = mouse->warp_emulation_prohibited;
1839 SDL_GetMouseState(&x, &y);
1840
1841 // Disable the warp emulation so it isn't accidentally activated on a fullscreen transitions.
1842 mouse->warp_emulation_prohibited = true;
1843 SDL_WarpMouseInWindow(window, x, y);
1844 mouse->warp_emulation_prohibited = prev_warp_val;
1845 }
1846}
1847
1848bool SDL_UpdateFullscreenMode(SDL_Window *window, SDL_FullscreenOp fullscreen, bool commit)
1849{
1850 SDL_VideoDisplay *display = NULL;
1851 SDL_DisplayMode *mode = NULL;
1852 int i;
1853
1854 CHECK_WINDOW_MAGIC(window, false);
1855
1856 window->fullscreen_exclusive = false;
1857 window->update_fullscreen_on_display_changed = false;
1858
1859 // If we are in the process of hiding don't go back to fullscreen
1860 if (window->is_destroying || window->is_hiding) {
1861 fullscreen = SDL_FULLSCREEN_OP_LEAVE;
1862 }
1863
1864 // Get the correct display for this operation
1865 if (fullscreen) {
1866 display = SDL_GetVideoDisplayForFullscreenWindow(window);
1867 if (!display) {
1868 // This should never happen, but it did...
1869 goto done;
1870 }
1871 } else {
1872 for (i = 0; i < _this->num_displays; ++i) {
1873 display = _this->displays[i];
1874 if (display->fullscreen_window == window) {
1875 break;
1876 }
1877 }
1878 if (!display || i == _this->num_displays) {
1879 // Already not fullscreen on any display
1880 display = NULL;
1881 }
1882 }
1883
1884 if (fullscreen) {
1885 mode = (SDL_DisplayMode *)SDL_GetWindowFullscreenMode(window);
1886 if (mode) {
1887 window->fullscreen_exclusive = true;
1888 } else {
1889 // Make sure the current mode is zeroed for fullscreen desktop.
1890 SDL_zero(window->current_fullscreen_mode);
1891 }
1892 }
1893
1894#if defined(SDL_PLATFORM_MACOS) && defined(SDL_VIDEO_DRIVER_COCOA)
1895 /* if the window is going away and no resolution change is necessary,
1896 do nothing, or else we may trigger an ugly double-transition
1897 */
1898 if (SDL_strcmp(_this->name, "cocoa") == 0) { // don't do this for X11, etc
1899 if (window->is_destroying && !window->last_fullscreen_exclusive_display) {
1900 window->fullscreen_exclusive = false;
1901 if (display) {
1902 display->fullscreen_window = NULL;
1903 }
1904 goto done;
1905 }
1906 if (commit) {
1907 // If we're switching between a fullscreen Space and exclusive fullscreen, we need to get back to normal first.
1908 if (fullscreen && Cocoa_IsWindowInFullscreenSpace(window) && !window->last_fullscreen_exclusive_display && window->fullscreen_exclusive) {
1909 if (!Cocoa_SetWindowFullscreenSpace(window, false, true)) {
1910 goto error;
1911 }
1912 } else if (fullscreen && window->last_fullscreen_exclusive_display && !window->fullscreen_exclusive) {
1913 for (i = 0; i < _this->num_displays; ++i) {
1914 SDL_VideoDisplay *last_display = _this->displays[i];
1915 if (last_display->fullscreen_window == window) {
1916 SDL_SetDisplayModeForDisplay(last_display, NULL);
1917 if (_this->SetWindowFullscreen) {
1918 _this->SetWindowFullscreen(_this, window, last_display, false);
1919 }
1920 last_display->fullscreen_window = NULL;
1921 }
1922 }
1923 }
1924
1925 if (Cocoa_SetWindowFullscreenSpace(window, !!fullscreen, syncHint)) {
1926 goto done;
1927 }
1928 }
1929 }
1930#endif
1931
1932 if (display) {
1933 // Restore the video mode on other displays if needed
1934 for (i = 0; i < _this->num_displays; ++i) {
1935 SDL_VideoDisplay *other = _this->displays[i];
1936 if (other != display && other->fullscreen_window == window) {
1937 SDL_SetDisplayModeForDisplay(other, NULL);
1938 other->fullscreen_window = NULL;
1939 }
1940 }
1941 }
1942
1943 if (fullscreen) {
1944 int mode_w = 0, mode_h = 0;
1945 bool resized = false;
1946
1947 // Hide any other fullscreen window on this display
1948 if (display->fullscreen_window &&
1949 display->fullscreen_window != window) {
1950 SDL_MinimizeWindow(display->fullscreen_window);
1951 }
1952
1953 display->fullscreen_active = window->fullscreen_exclusive;
1954
1955 if (!SDL_SetDisplayModeForDisplay(display, mode)) {
1956 goto error;
1957 }
1958 if (commit) {
1959 SDL_FullscreenResult ret = SDL_FULLSCREEN_SUCCEEDED;
1960 if (_this->SetWindowFullscreen) {
1961 ret = _this->SetWindowFullscreen(_this, window, display, fullscreen);
1962 } else {
1963 resized = true;
1964 }
1965
1966 if (ret == SDL_FULLSCREEN_SUCCEEDED) {
1967 // Window is fullscreen immediately upon return. If the driver hasn't already sent the event, do so now.
1968 if (!(window->flags & SDL_WINDOW_FULLSCREEN)) {
1969 SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_ENTER_FULLSCREEN, 0, 0);
1970 }
1971 } else if (ret == SDL_FULLSCREEN_FAILED) {
1972 display->fullscreen_active = false;
1973 goto error;
1974 }
1975 }
1976
1977 if (window->flags & SDL_WINDOW_FULLSCREEN) {
1978 display->fullscreen_window = window;
1979
1980 /* Android may not resize the window to exactly what our fullscreen mode is,
1981 * especially on windowed Android environments like the Chromebook or Samsung DeX.
1982 * Given this, we shouldn't use the mode size. Android's SetWindowFullscreen
1983 * will generate the window event for us with the proper final size.
1984 *
1985 * This is also unnecessary on Cocoa, Wayland, Win32, and X11 (will send SDL_EVENT_WINDOW_RESIZED).
1986 */
1987 if (!SDL_SendsFullscreenDimensions(_this)) {
1988 SDL_Rect displayRect;
1989
1990 if (mode) {
1991 mode_w = mode->w;
1992 mode_h = mode->h;
1993 SDL_GetDisplayBounds(mode->displayID, &displayRect);
1994 } else {
1995 mode_w = display->desktop_mode.w;
1996 mode_h = display->desktop_mode.h;
1997 SDL_GetDisplayBounds(display->id, &displayRect);
1998 }
1999
2000 if (window->w != mode_w || window->h != mode_h) {
2001 resized = true;
2002 }
2003
2004 SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_MOVED, displayRect.x, displayRect.y);
2005
2006 if (resized) {
2007 SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_RESIZED, mode_w, mode_h);
2008 } else {
2009 SDL_OnWindowResized(window);
2010 }
2011 }
2012
2013 // Restore the cursor position
2014 if (!SDL_DisableMouseWarpOnFullscreenTransitions(_this)) {
2015 SDL_RestoreMousePosition(window);
2016 }
2017 }
2018 } else {
2019 bool resized = false;
2020
2021 // Restore the desktop mode
2022 if (display) {
2023 display->fullscreen_active = false;
2024
2025 SDL_SetDisplayModeForDisplay(display, NULL);
2026 }
2027 if (commit) {
2028 SDL_FullscreenResult ret = SDL_FULLSCREEN_SUCCEEDED;
2029 if (_this->SetWindowFullscreen) {
2030 SDL_VideoDisplay *full_screen_display = display ? display : SDL_GetVideoDisplayForFullscreenWindow(window);
2031 if (full_screen_display) {
2032 ret = _this->SetWindowFullscreen(_this, window, full_screen_display, SDL_FULLSCREEN_OP_LEAVE);
2033 }
2034 } else {
2035 resized = true;
2036 }
2037
2038 if (ret == SDL_FULLSCREEN_SUCCEEDED) {
2039 // Window left fullscreen immediately upon return. If the driver hasn't already sent the event, do so now.
2040 if (window->flags & SDL_WINDOW_FULLSCREEN) {
2041 SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_LEAVE_FULLSCREEN, 0, 0);
2042 }
2043 } else if (ret == SDL_FULLSCREEN_FAILED) {
2044 goto error;
2045 }
2046 }
2047
2048 if (!(window->flags & SDL_WINDOW_FULLSCREEN)) {
2049 if (display) {
2050 display->fullscreen_window = NULL;
2051 }
2052
2053 if (!SDL_SendsFullscreenDimensions(_this)) {
2054 SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_MOVED, window->windowed.x, window->windowed.y);
2055 if (resized) {
2056 SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_RESIZED, window->windowed.w, window->windowed.h);
2057 } else {
2058 SDL_OnWindowResized(window);
2059 }
2060 }
2061
2062 // Restore the cursor position if we've exited fullscreen on a display
2063 if (display && !SDL_DisableMouseWarpOnFullscreenTransitions(_this)) {
2064 SDL_RestoreMousePosition(window);
2065 }
2066 }
2067 }
2068
2069done:
2070 window->last_fullscreen_exclusive_display = display && (window->flags & SDL_WINDOW_FULLSCREEN) && window->fullscreen_exclusive ? display->id : 0;
2071 return true;
2072
2073error:
2074 if (fullscreen) {
2075 // Something went wrong and the window is no longer fullscreen.
2076 SDL_UpdateFullscreenMode(window, SDL_FULLSCREEN_OP_LEAVE, commit);
2077 }
2078 return false;
2079}
2080
2081bool SDL_SetWindowFullscreenMode(SDL_Window *window, const SDL_DisplayMode *mode)
2082{
2083 CHECK_WINDOW_MAGIC(window, false);
2084 CHECK_WINDOW_NOT_POPUP(window, false);
2085
2086 if (mode) {
2087 if (!SDL_GetFullscreenModeMatch(mode)) {
2088 return SDL_SetError("Invalid fullscreen display mode");
2089 }
2090
2091 // Save the mode so we can look up the closest match later
2092 SDL_copyp(&window->requested_fullscreen_mode, mode);
2093 } else {
2094 SDL_zero(window->requested_fullscreen_mode);
2095 }
2096
2097 /* Copy to the current mode now, in case an asynchronous fullscreen window request
2098 * is in progress. It will be overwritten if a new request is made.
2099 */
2100 SDL_copyp(&window->current_fullscreen_mode, &window->requested_fullscreen_mode);
2101 if (SDL_WINDOW_FULLSCREEN_VISIBLE(window)) {
2102 SDL_UpdateFullscreenMode(window, SDL_FULLSCREEN_OP_UPDATE, true);
2103 SDL_SyncIfRequired(window);
2104 }
2105
2106 return true;
2107}
2108
2109const SDL_DisplayMode *SDL_GetWindowFullscreenMode(SDL_Window *window)
2110{
2111 CHECK_WINDOW_MAGIC(window, NULL);
2112 CHECK_WINDOW_NOT_POPUP(window, NULL);
2113
2114 if (window->flags & SDL_WINDOW_FULLSCREEN) {
2115 return SDL_GetFullscreenModeMatch(&window->current_fullscreen_mode);
2116 } else {
2117 return SDL_GetFullscreenModeMatch(&window->requested_fullscreen_mode);
2118 }
2119}
2120
2121void *SDL_GetWindowICCProfile(SDL_Window *window, size_t *size)
2122{
2123 if (!_this->GetWindowICCProfile) {
2124 SDL_Unsupported();
2125 return NULL;
2126 }
2127 return _this->GetWindowICCProfile(_this, window, size);
2128}
2129
2130SDL_PixelFormat SDL_GetWindowPixelFormat(SDL_Window *window)
2131{
2132 SDL_DisplayID displayID;
2133 const SDL_DisplayMode *mode;
2134
2135 CHECK_WINDOW_MAGIC(window, SDL_PIXELFORMAT_UNKNOWN);
2136
2137 displayID = SDL_GetDisplayForWindow(window);
2138 mode = SDL_GetCurrentDisplayMode(displayID);
2139 if (mode) {
2140 return mode->format;
2141 } else {
2142 return SDL_PIXELFORMAT_UNKNOWN;
2143 }
2144}
2145
2146#define CREATE_FLAGS \
2147 (SDL_WINDOW_OPENGL | SDL_WINDOW_BORDERLESS | SDL_WINDOW_RESIZABLE | SDL_WINDOW_HIGH_PIXEL_DENSITY | SDL_WINDOW_ALWAYS_ON_TOP | SDL_WINDOW_POPUP_MENU | SDL_WINDOW_UTILITY | SDL_WINDOW_TOOLTIP | SDL_WINDOW_VULKAN | SDL_WINDOW_MINIMIZED | SDL_WINDOW_METAL | SDL_WINDOW_TRANSPARENT | SDL_WINDOW_NOT_FOCUSABLE)
2148
2149static SDL_INLINE bool IsAcceptingDragAndDrop(void)
2150{
2151 if (SDL_EventEnabled(SDL_EVENT_DROP_FILE) || SDL_EventEnabled(SDL_EVENT_DROP_TEXT)) {
2152 return true;
2153 }
2154 return false;
2155}
2156
2157// prepare a newly-created window
2158static SDL_INLINE void PrepareDragAndDropSupport(SDL_Window *window)
2159{
2160 if (_this->AcceptDragAndDrop) {
2161 _this->AcceptDragAndDrop(window, IsAcceptingDragAndDrop());
2162 }
2163}
2164
2165// toggle d'n'd for all existing windows.
2166void SDL_ToggleDragAndDropSupport(void)
2167{
2168 if (_this && _this->AcceptDragAndDrop) {
2169 const bool enable = IsAcceptingDragAndDrop();
2170 SDL_Window *window;
2171 for (window = _this->windows; window; window = window->next) {
2172 _this->AcceptDragAndDrop(window, enable);
2173 }
2174 }
2175}
2176
2177SDL_Window ** SDLCALL SDL_GetWindows(int *count)
2178{
2179 if (count) {
2180 *count = 0;
2181 }
2182
2183 if (!_this) {
2184 SDL_UninitializedVideo();
2185 return NULL;
2186 }
2187
2188 SDL_Window *window;
2189 int num_added = 0;
2190 int num_windows = 0;
2191 for (window = _this->windows; window; window = window->next) {
2192 ++num_windows;
2193 }
2194
2195 SDL_Window **windows = (SDL_Window **)SDL_malloc((num_windows + 1) * sizeof(*windows));
2196 if (!windows) {
2197 return NULL;
2198 }
2199
2200 for (window = _this->windows; window; window = window->next) {
2201 windows[num_added++] = window;
2202 if (num_added == num_windows) {
2203 // Race condition? Multi-threading not supported, ignore it
2204 break;
2205 }
2206 }
2207 windows[num_added] = NULL;
2208
2209 if (count) {
2210 *count = num_added;
2211 }
2212 return windows;
2213}
2214
2215static void ApplyWindowFlags(SDL_Window *window, SDL_WindowFlags flags)
2216{
2217 if (!SDL_WINDOW_IS_POPUP(window)) {
2218 if (!(flags & (SDL_WINDOW_MINIMIZED | SDL_WINDOW_MAXIMIZED))) {
2219 SDL_RestoreWindow(window);
2220 }
2221 if (flags & SDL_WINDOW_MAXIMIZED) {
2222 SDL_MaximizeWindow(window);
2223 }
2224
2225 SDL_SetWindowFullscreen(window, (flags & SDL_WINDOW_FULLSCREEN) != 0);
2226
2227 if (flags & SDL_WINDOW_MINIMIZED) {
2228 SDL_MinimizeWindow(window);
2229 }
2230
2231 if (flags & SDL_WINDOW_MODAL) {
2232 SDL_SetWindowModal(window, true);
2233 }
2234
2235 if (flags & SDL_WINDOW_MOUSE_GRABBED) {
2236 SDL_SetWindowMouseGrab(window, true);
2237 }
2238 if (flags & SDL_WINDOW_KEYBOARD_GRABBED) {
2239 SDL_SetWindowKeyboardGrab(window, true);
2240 }
2241 }
2242}
2243
2244static void SDL_FinishWindowCreation(SDL_Window *window, SDL_WindowFlags flags)
2245{
2246 PrepareDragAndDropSupport(window);
2247
2248 if (window->flags & SDL_WINDOW_EXTERNAL) {
2249 // Whoever has created the window has already applied whatever flags are needed
2250 } else {
2251 ApplyWindowFlags(window, flags);
2252 if (!(flags & SDL_WINDOW_HIDDEN)) {
2253 SDL_ShowWindow(window);
2254 }
2255 }
2256}
2257
2258static bool SDL_ContextNotSupported(const char *name)
2259{
2260 return SDL_SetError("%s support is either not configured in SDL "
2261 "or not available in current SDL video driver "
2262 "(%s) or platform",
2263 name,
2264 _this->name);
2265}
2266
2267static bool SDL_DllNotSupported(const char *name)
2268{
2269 return SDL_SetError("No dynamic %s support in current SDL video driver (%s)", name, _this->name);
2270}
2271
2272static struct {
2273 const char *property_name;
2274 SDL_WindowFlags flag;
2275 bool invert_value;
2276} SDL_WindowFlagProperties[] = {
2277 { SDL_PROP_WINDOW_CREATE_ALWAYS_ON_TOP_BOOLEAN, SDL_WINDOW_ALWAYS_ON_TOP, false },
2278 { SDL_PROP_WINDOW_CREATE_BORDERLESS_BOOLEAN, SDL_WINDOW_BORDERLESS, false },
2279 { SDL_PROP_WINDOW_CREATE_FOCUSABLE_BOOLEAN, SDL_WINDOW_NOT_FOCUSABLE, true },
2280 { SDL_PROP_WINDOW_CREATE_FULLSCREEN_BOOLEAN, SDL_WINDOW_FULLSCREEN, false },
2281 { SDL_PROP_WINDOW_CREATE_HIDDEN_BOOLEAN, SDL_WINDOW_HIDDEN, false },
2282 { SDL_PROP_WINDOW_CREATE_HIGH_PIXEL_DENSITY_BOOLEAN, SDL_WINDOW_HIGH_PIXEL_DENSITY, false },
2283 { SDL_PROP_WINDOW_CREATE_MAXIMIZED_BOOLEAN, SDL_WINDOW_MAXIMIZED, false },
2284 { SDL_PROP_WINDOW_CREATE_MENU_BOOLEAN, SDL_WINDOW_POPUP_MENU, false },
2285 { SDL_PROP_WINDOW_CREATE_METAL_BOOLEAN, SDL_WINDOW_METAL, false },
2286 { SDL_PROP_WINDOW_CREATE_MINIMIZED_BOOLEAN, SDL_WINDOW_MINIMIZED, false },
2287 { SDL_PROP_WINDOW_CREATE_MODAL_BOOLEAN, SDL_WINDOW_MODAL, false },
2288 { SDL_PROP_WINDOW_CREATE_MOUSE_GRABBED_BOOLEAN, SDL_WINDOW_MOUSE_GRABBED, false },
2289 { SDL_PROP_WINDOW_CREATE_OPENGL_BOOLEAN, SDL_WINDOW_OPENGL, false },
2290 { SDL_PROP_WINDOW_CREATE_RESIZABLE_BOOLEAN, SDL_WINDOW_RESIZABLE, false },
2291 { SDL_PROP_WINDOW_CREATE_TRANSPARENT_BOOLEAN, SDL_WINDOW_TRANSPARENT, false },
2292 { SDL_PROP_WINDOW_CREATE_TOOLTIP_BOOLEAN, SDL_WINDOW_TOOLTIP, false },
2293 { SDL_PROP_WINDOW_CREATE_UTILITY_BOOLEAN, SDL_WINDOW_UTILITY, false },
2294 { SDL_PROP_WINDOW_CREATE_VULKAN_BOOLEAN, SDL_WINDOW_VULKAN, false }
2295};
2296
2297static SDL_WindowFlags SDL_GetWindowFlagProperties(SDL_PropertiesID props)
2298{
2299 unsigned i;
2300 SDL_WindowFlags flags = (SDL_WindowFlags)SDL_GetNumberProperty(props, SDL_PROP_WINDOW_CREATE_FLAGS_NUMBER, 0);
2301
2302 for (i = 0; i < SDL_arraysize(SDL_WindowFlagProperties); ++i) {
2303 if (SDL_WindowFlagProperties[i].invert_value) {
2304 if (!SDL_GetBooleanProperty(props, SDL_WindowFlagProperties[i].property_name, true)) {
2305 flags |= SDL_WindowFlagProperties[i].flag;
2306 }
2307 } else {
2308 if (SDL_GetBooleanProperty(props, SDL_WindowFlagProperties[i].property_name, false)) {
2309 flags |= SDL_WindowFlagProperties[i].flag;
2310 }
2311 }
2312 }
2313 return flags;
2314}
2315
2316SDL_Window *SDL_CreateWindowWithProperties(SDL_PropertiesID props)
2317{
2318 SDL_Window *window;
2319 const char *title = SDL_GetStringProperty(props, SDL_PROP_WINDOW_CREATE_TITLE_STRING, NULL);
2320 int x = (int)SDL_GetNumberProperty(props, SDL_PROP_WINDOW_CREATE_X_NUMBER, SDL_WINDOWPOS_UNDEFINED);
2321 int y = (int)SDL_GetNumberProperty(props, SDL_PROP_WINDOW_CREATE_Y_NUMBER, SDL_WINDOWPOS_UNDEFINED);
2322 int w = (int)SDL_GetNumberProperty(props, SDL_PROP_WINDOW_CREATE_WIDTH_NUMBER, 0);
2323 int h = (int)SDL_GetNumberProperty(props, SDL_PROP_WINDOW_CREATE_HEIGHT_NUMBER, 0);
2324 SDL_Window *parent = (SDL_Window *)SDL_GetPointerProperty(props, SDL_PROP_WINDOW_CREATE_PARENT_POINTER, NULL);
2325 SDL_WindowFlags flags = SDL_GetWindowFlagProperties(props);
2326 SDL_WindowFlags type_flags, graphics_flags;
2327 bool undefined_x = false;
2328 bool undefined_y = false;
2329 bool external_graphics_context = SDL_GetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_EXTERNAL_GRAPHICS_CONTEXT_BOOLEAN, false);
2330
2331 if (!_this) {
2332 // Initialize the video system if needed
2333 if (!SDL_Init(SDL_INIT_VIDEO)) {
2334 return NULL;
2335 }
2336
2337 // Make clang-tidy happy
2338 if (!_this) {
2339 return NULL;
2340 }
2341 }
2342
2343 if ((flags & SDL_WINDOW_MODAL) && !SDL_ObjectValid(parent, SDL_OBJECT_TYPE_WINDOW)) {
2344 SDL_SetError("Modal windows must specify a parent window");
2345 return NULL;
2346 }
2347
2348 if ((flags & (SDL_WINDOW_TOOLTIP | SDL_WINDOW_POPUP_MENU)) != 0) {
2349 if (!(_this->device_caps & VIDEO_DEVICE_CAPS_HAS_POPUP_WINDOW_SUPPORT)) {
2350 SDL_Unsupported();
2351 return NULL;
2352 }
2353
2354 // Tooltip and popup menu window must specify a parent window
2355 if (!SDL_ObjectValid(parent, SDL_OBJECT_TYPE_WINDOW)) {
2356 SDL_SetError("Tooltip and popup menu windows must specify a parent window");
2357 return NULL;
2358 }
2359
2360 // Remove invalid flags
2361 flags &= ~(SDL_WINDOW_MINIMIZED | SDL_WINDOW_MAXIMIZED | SDL_WINDOW_FULLSCREEN | SDL_WINDOW_BORDERLESS);
2362 }
2363
2364 // Ensure no more than one of these flags is set
2365 type_flags = flags & (SDL_WINDOW_UTILITY | SDL_WINDOW_TOOLTIP | SDL_WINDOW_POPUP_MENU | SDL_WINDOW_MODAL);
2366 if (type_flags & (type_flags - 1)) {
2367 SDL_SetError("Conflicting window type flags specified: 0x%.8x", (unsigned int)type_flags);
2368 return NULL;
2369 }
2370
2371 // Make sure the display list is up to date for window placement
2372 if (_this->RefreshDisplays) {
2373 _this->RefreshDisplays(_this);
2374 }
2375
2376 // Some platforms can't create zero-sized windows
2377 if (w < 1) {
2378 w = 1;
2379 }
2380 if (h < 1) {
2381 h = 1;
2382 }
2383
2384 if (SDL_WINDOWPOS_ISUNDEFINED(x) || SDL_WINDOWPOS_ISUNDEFINED(y) ||
2385 SDL_WINDOWPOS_ISCENTERED(x) || SDL_WINDOWPOS_ISCENTERED(y)) {
2386 SDL_DisplayID displayID = 0;
2387 SDL_Rect bounds;
2388
2389 if ((SDL_WINDOWPOS_ISUNDEFINED(x) || SDL_WINDOWPOS_ISCENTERED(x)) && (x & 0xFFFF)) {
2390 displayID = (x & 0xFFFF);
2391 } else if ((SDL_WINDOWPOS_ISUNDEFINED(y) || SDL_WINDOWPOS_ISCENTERED(y)) && (y & 0xFFFF)) {
2392 displayID = (y & 0xFFFF);
2393 }
2394 if (displayID == 0 || SDL_GetDisplayIndex(displayID) < 0) {
2395 displayID = SDL_GetPrimaryDisplay();
2396 }
2397
2398 SDL_zero(bounds);
2399 SDL_GetDisplayUsableBounds(displayID, &bounds);
2400 if (w > bounds.w || h > bounds.h) {
2401 // This window is larger than the usable bounds, just center on the display
2402 SDL_GetDisplayBounds(displayID, &bounds);
2403 }
2404 if (SDL_WINDOWPOS_ISCENTERED(x) || SDL_WINDOWPOS_ISUNDEFINED(x)) {
2405 if (SDL_WINDOWPOS_ISUNDEFINED(x)) {
2406 undefined_x = true;
2407 }
2408 x = bounds.x + (bounds.w - w) / 2;
2409 }
2410 if (SDL_WINDOWPOS_ISCENTERED(y) || SDL_WINDOWPOS_ISUNDEFINED(y)) {
2411 if (SDL_WINDOWPOS_ISUNDEFINED(y)) {
2412 undefined_y = true;
2413 }
2414 y = bounds.y + (bounds.h - h) / 2;
2415 }
2416 }
2417
2418 // ensure no more than one of these flags is set
2419 graphics_flags = flags & (SDL_WINDOW_OPENGL | SDL_WINDOW_METAL | SDL_WINDOW_VULKAN);
2420 if (graphics_flags & (graphics_flags - 1)) {
2421 SDL_SetError("Conflicting window graphics flags specified: 0x%.8x", (unsigned int)graphics_flags);
2422 return NULL;
2423 }
2424
2425 // Some platforms have certain graphics backends enabled by default
2426 if (!graphics_flags && !external_graphics_context) {
2427 flags |= SDL_DefaultGraphicsBackends(_this);
2428 }
2429
2430 if (flags & SDL_WINDOW_OPENGL) {
2431 if (!_this->GL_CreateContext) {
2432 SDL_ContextNotSupported("OpenGL");
2433 return NULL;
2434 }
2435 if (!SDL_GL_LoadLibrary(NULL)) {
2436 return NULL;
2437 }
2438 }
2439
2440 if (flags & SDL_WINDOW_VULKAN) {
2441 if (!_this->Vulkan_CreateSurface) {
2442 SDL_ContextNotSupported("Vulkan");
2443 return NULL;
2444 }
2445 if (!SDL_Vulkan_LoadLibrary(NULL)) {
2446 return NULL;
2447 }
2448 }
2449
2450 if (flags & SDL_WINDOW_METAL) {
2451 if (!_this->Metal_CreateView) {
2452 SDL_ContextNotSupported("Metal");
2453 return NULL;
2454 }
2455 }
2456
2457 window = (SDL_Window *)SDL_calloc(1, sizeof(*window));
2458 if (!window) {
2459 return NULL;
2460 }
2461 SDL_SetObjectValid(window, SDL_OBJECT_TYPE_WINDOW, true);
2462 window->id = SDL_GetNextObjectID();
2463 window->floating.x = window->windowed.x = window->x = x;
2464 window->floating.y = window->windowed.y = window->y = y;
2465 window->floating.w = window->windowed.w = window->w = w;
2466 window->floating.h = window->windowed.h = window->h = h;
2467 window->undefined_x = undefined_x;
2468 window->undefined_y = undefined_y;
2469
2470 SDL_VideoDisplay *display = SDL_GetVideoDisplayForWindow(window);
2471 if (display) {
2472 SDL_SetWindowHDRProperties(window, &display->HDR, false);
2473 }
2474
2475 if (flags & SDL_WINDOW_FULLSCREEN || IsFullscreenOnly(_this)) {
2476 SDL_Rect bounds;
2477
2478 SDL_GetDisplayBounds(display ? display->id : SDL_GetPrimaryDisplay(), &bounds);
2479 window->x = bounds.x;
2480 window->y = bounds.y;
2481 window->w = bounds.w;
2482 window->h = bounds.h;
2483 window->pending_flags |= SDL_WINDOW_FULLSCREEN;
2484 flags |= SDL_WINDOW_FULLSCREEN;
2485 }
2486
2487 window->flags = ((flags & CREATE_FLAGS) | SDL_WINDOW_HIDDEN);
2488 window->display_scale = 1.0f;
2489 window->opacity = 1.0f;
2490 window->next = _this->windows;
2491 window->is_destroying = false;
2492 window->last_displayID = SDL_GetDisplayForWindow(window);
2493 window->external_graphics_context = external_graphics_context;
2494
2495 if (_this->windows) {
2496 _this->windows->prev = window;
2497 }
2498 _this->windows = window;
2499
2500 // Set the parent before creation.
2501 SDL_UpdateWindowHierarchy(window, parent);
2502
2503 if (_this->CreateSDLWindow && !_this->CreateSDLWindow(_this, window, props)) {
2504 SDL_DestroyWindow(window);
2505 return NULL;
2506 }
2507
2508 /* Clear minimized if not on windows, only windows handles it at create rather than FinishWindowCreation,
2509 * but it's important or window focus will get broken on windows!
2510 */
2511#if !defined(SDL_PLATFORM_WINDOWS)
2512 if (window->flags & SDL_WINDOW_MINIMIZED) {
2513 window->flags &= ~SDL_WINDOW_MINIMIZED;
2514 }
2515#endif
2516
2517 if (title) {
2518 SDL_SetWindowTitle(window, title);
2519 }
2520 SDL_FinishWindowCreation(window, flags);
2521
2522 // Make sure window pixel size is up to date
2523 SDL_CheckWindowPixelSizeChanged(window);
2524
2525#ifdef SDL_VIDEO_DRIVER_UIKIT
2526 SDL_UpdateLifecycleObserver();
2527#endif
2528
2529 SDL_ClearError();
2530
2531 return window;
2532}
2533
2534SDL_Window *SDL_CreateWindow(const char *title, int w, int h, SDL_WindowFlags flags)
2535{
2536 SDL_Window *window;
2537 SDL_PropertiesID props = SDL_CreateProperties();
2538 if (title && *title) {
2539 SDL_SetStringProperty(props, SDL_PROP_WINDOW_CREATE_TITLE_STRING, title);
2540 }
2541 SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_WIDTH_NUMBER, w);
2542 SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_HEIGHT_NUMBER, h);
2543 SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_FLAGS_NUMBER, flags);
2544 window = SDL_CreateWindowWithProperties(props);
2545 SDL_DestroyProperties(props);
2546 return window;
2547}
2548
2549SDL_Window *SDL_CreatePopupWindow(SDL_Window *parent, int offset_x, int offset_y, int w, int h, SDL_WindowFlags flags)
2550{
2551 SDL_Window *window;
2552 SDL_PropertiesID props = SDL_CreateProperties();
2553
2554 // Popups must specify either the tooltip or popup menu window flags
2555 if (!(flags & (SDL_WINDOW_TOOLTIP | SDL_WINDOW_POPUP_MENU))) {
2556 SDL_SetError("Popup windows must specify either the 'SDL_WINDOW_TOOLTIP' or the 'SDL_WINDOW_POPUP_MENU' flag");
2557 return NULL;
2558 }
2559
2560 SDL_SetPointerProperty(props, SDL_PROP_WINDOW_CREATE_PARENT_POINTER, parent);
2561 SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_X_NUMBER, offset_x);
2562 SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_Y_NUMBER, offset_y);
2563 SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_WIDTH_NUMBER, w);
2564 SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_HEIGHT_NUMBER, h);
2565 SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_FLAGS_NUMBER, flags);
2566 window = SDL_CreateWindowWithProperties(props);
2567 SDL_DestroyProperties(props);
2568 return window;
2569}
2570
2571bool SDL_RecreateWindow(SDL_Window *window, SDL_WindowFlags flags)
2572{
2573 bool loaded_opengl = false;
2574 bool need_gl_unload = false;
2575 bool need_gl_load = false;
2576 bool loaded_vulkan = false;
2577 bool need_vulkan_unload = false;
2578 bool need_vulkan_load = false;
2579 SDL_WindowFlags graphics_flags;
2580
2581 // ensure no more than one of these flags is set
2582 graphics_flags = flags & (SDL_WINDOW_OPENGL | SDL_WINDOW_METAL | SDL_WINDOW_VULKAN);
2583 if (graphics_flags & (graphics_flags - 1)) {
2584 return SDL_SetError("Conflicting window flags specified");
2585 }
2586
2587 if ((flags & SDL_WINDOW_OPENGL) && !_this->GL_CreateContext) {
2588 return SDL_ContextNotSupported("OpenGL");
2589 }
2590 if ((flags & SDL_WINDOW_VULKAN) && !_this->Vulkan_CreateSurface) {
2591 return SDL_ContextNotSupported("Vulkan");
2592 }
2593 if ((flags & SDL_WINDOW_METAL) && !_this->Metal_CreateView) {
2594 return SDL_ContextNotSupported("Metal");
2595 }
2596
2597 if (window->flags & SDL_WINDOW_EXTERNAL) {
2598 // Can't destroy and re-create external windows, hrm
2599 flags |= SDL_WINDOW_EXTERNAL;
2600 } else {
2601 flags &= ~SDL_WINDOW_EXTERNAL;
2602 }
2603
2604 // If this is a modal dialog, clear the modal status.
2605 if (window->flags & SDL_WINDOW_MODAL) {
2606 SDL_SetWindowModal(window, false);
2607 }
2608
2609 // Restore video mode, etc.
2610 if (!(window->flags & SDL_WINDOW_EXTERNAL)) {
2611 const bool restore_on_show = window->restore_on_show;
2612 SDL_HideWindow(window);
2613 window->restore_on_show = restore_on_show;
2614 }
2615
2616 // Tear down the old native window
2617 SDL_DestroyWindowSurface(window);
2618
2619 if ((window->flags & SDL_WINDOW_OPENGL) != (flags & SDL_WINDOW_OPENGL)) {
2620 if (flags & SDL_WINDOW_OPENGL) {
2621 need_gl_load = true;
2622 } else {
2623 need_gl_unload = true;
2624 }
2625 } else if (window->flags & SDL_WINDOW_OPENGL) {
2626 need_gl_unload = true;
2627 need_gl_load = true;
2628 }
2629
2630 if ((window->flags & SDL_WINDOW_VULKAN) != (flags & SDL_WINDOW_VULKAN)) {
2631 if (flags & SDL_WINDOW_VULKAN) {
2632 need_vulkan_load = true;
2633 } else {
2634 need_vulkan_unload = true;
2635 }
2636 } else if (window->flags & SDL_WINDOW_VULKAN) {
2637 need_vulkan_unload = true;
2638 need_vulkan_load = true;
2639 }
2640
2641 if (need_gl_unload) {
2642 SDL_GL_UnloadLibrary();
2643 }
2644
2645 if (need_vulkan_unload) {
2646 SDL_Vulkan_UnloadLibrary();
2647 }
2648
2649 if (_this->DestroyWindow && !(flags & SDL_WINDOW_EXTERNAL)) {
2650 _this->DestroyWindow(_this, window);
2651 }
2652
2653 if (need_gl_load) {
2654 if (!SDL_GL_LoadLibrary(NULL)) {
2655 return false;
2656 }
2657 loaded_opengl = true;
2658 }
2659
2660 if (need_vulkan_load) {
2661 if (!SDL_Vulkan_LoadLibrary(NULL)) {
2662 return false;
2663 }
2664 loaded_vulkan = true;
2665 }
2666
2667 window->flags = ((flags & CREATE_FLAGS) | SDL_WINDOW_HIDDEN);
2668 window->is_destroying = false;
2669
2670 if (_this->CreateSDLWindow && !(flags & SDL_WINDOW_EXTERNAL)) {
2671 /* Reset the window size to the original floating value, so the
2672 * recreated window has the proper base size.
2673 */
2674 window->x = window->windowed.x = window->floating.x;
2675 window->y = window->windowed.y = window->floating.y;
2676 window->w = window->windowed.w = window->floating.w;
2677 window->h = window->windowed.h = window->floating.h;
2678
2679 if (!_this->CreateSDLWindow(_this, window, 0)) {
2680 if (loaded_opengl) {
2681 SDL_GL_UnloadLibrary();
2682 window->flags &= ~SDL_WINDOW_OPENGL;
2683 }
2684 if (loaded_vulkan) {
2685 SDL_Vulkan_UnloadLibrary();
2686 window->flags &= ~SDL_WINDOW_VULKAN;
2687 }
2688 return false;
2689 }
2690 }
2691
2692 if (flags & SDL_WINDOW_EXTERNAL) {
2693 window->flags |= SDL_WINDOW_EXTERNAL;
2694 }
2695
2696 if (_this->SetWindowTitle && window->title) {
2697 _this->SetWindowTitle(_this, window);
2698 }
2699
2700 if (_this->SetWindowIcon && window->icon) {
2701 _this->SetWindowIcon(_this, window, window->icon);
2702 }
2703
2704 if (_this->SetWindowMinimumSize && (window->min_w || window->min_h)) {
2705 _this->SetWindowMinimumSize(_this, window);
2706 }
2707
2708 if (_this->SetWindowMaximumSize && (window->max_w || window->max_h)) {
2709 _this->SetWindowMaximumSize(_this, window);
2710 }
2711
2712 if (_this->SetWindowAspectRatio && (window->min_aspect > 0.0f || window->max_aspect > 0.0f)) {
2713 _this->SetWindowAspectRatio(_this, window);
2714 }
2715
2716 if (window->hit_test) {
2717 _this->SetWindowHitTest(window, true);
2718 }
2719
2720 SDL_FinishWindowCreation(window, flags);
2721
2722 return true;
2723}
2724
2725bool SDL_HasWindows(void)
2726{
2727 return _this && _this->windows;
2728}
2729
2730SDL_WindowID SDL_GetWindowID(SDL_Window *window)
2731{
2732 CHECK_WINDOW_MAGIC(window, 0);
2733
2734 return window->id;
2735}
2736
2737SDL_Window *SDL_GetWindowFromID(SDL_WindowID id)
2738{
2739 SDL_Window *window;
2740
2741 if (!_this) {
2742 SDL_UninitializedVideo();
2743 return NULL;
2744 }
2745 if (id) {
2746 for (window = _this->windows; window; window = window->next) {
2747 if (window->id == id) {
2748 return window;
2749 }
2750 }
2751 }
2752 SDL_SetError("Invalid window ID"); \
2753 return NULL;
2754}
2755
2756SDL_Window *SDL_GetWindowParent(SDL_Window *window)
2757{
2758 CHECK_WINDOW_MAGIC(window, NULL);
2759
2760 return window->parent;
2761}
2762
2763SDL_PropertiesID SDL_GetWindowProperties(SDL_Window *window)
2764{
2765 CHECK_WINDOW_MAGIC(window, 0);
2766
2767 if (window->props == 0) {
2768 window->props = SDL_CreateProperties();
2769 }
2770 return window->props;
2771}
2772
2773SDL_WindowFlags SDL_GetWindowFlags(SDL_Window *window)
2774{
2775 CHECK_WINDOW_MAGIC(window, 0);
2776
2777 return window->flags | window->pending_flags;
2778}
2779
2780bool SDL_SetWindowTitle(SDL_Window *window, const char *title)
2781{
2782 CHECK_WINDOW_MAGIC(window, false);
2783 CHECK_WINDOW_NOT_POPUP(window, false);
2784
2785 if (title == window->title) {
2786 return true;
2787 }
2788 if (!title) {
2789 title = "";
2790 }
2791 if (window->title && SDL_strcmp(title, window->title) == 0) {
2792 return true;
2793 }
2794
2795 SDL_free(window->title);
2796
2797 window->title = SDL_strdup(title);
2798
2799 if (_this->SetWindowTitle) {
2800 _this->SetWindowTitle(_this, window);
2801 }
2802 return true;
2803}
2804
2805const char *SDL_GetWindowTitle(SDL_Window *window)
2806{
2807 CHECK_WINDOW_MAGIC(window, "");
2808
2809 return window->title ? window->title : "";
2810}
2811
2812bool SDL_SetWindowIcon(SDL_Window *window, SDL_Surface *icon)
2813{
2814 CHECK_WINDOW_MAGIC(window, false);
2815
2816 if (!icon) {
2817 return SDL_InvalidParamError("icon");
2818 }
2819
2820 SDL_DestroySurface(window->icon);
2821
2822 // Convert the icon into ARGB8888
2823 window->icon = SDL_ConvertSurface(icon, SDL_PIXELFORMAT_ARGB8888);
2824 if (!window->icon) {
2825 return false;
2826 }
2827
2828 if (!_this->SetWindowIcon) {
2829 return SDL_Unsupported();
2830 }
2831
2832 return _this->SetWindowIcon(_this, window, window->icon);
2833}
2834
2835bool SDL_SetWindowPosition(SDL_Window *window, int x, int y)
2836{
2837 SDL_DisplayID original_displayID;
2838
2839 CHECK_WINDOW_MAGIC(window, false);
2840
2841 const int w = window->last_size_pending ? window->pending.w : window->windowed.w;
2842 const int h = window->last_size_pending ? window->pending.h : window->windowed.h;
2843
2844 original_displayID = SDL_GetDisplayForWindow(window);
2845
2846 if (SDL_WINDOWPOS_ISUNDEFINED(x)) {
2847 x = window->windowed.x;
2848 }
2849 if (SDL_WINDOWPOS_ISUNDEFINED(y)) {
2850 y = window->windowed.y;
2851 }
2852 if (SDL_WINDOWPOS_ISCENTERED(x) || SDL_WINDOWPOS_ISCENTERED(y)) {
2853 SDL_DisplayID displayID = original_displayID;
2854 SDL_Rect bounds;
2855
2856 if (SDL_WINDOWPOS_ISCENTERED(x) && (x & 0xFFFF)) {
2857 displayID = (x & 0xFFFF);
2858 } else if (SDL_WINDOWPOS_ISCENTERED(y) && (y & 0xFFFF)) {
2859 displayID = (y & 0xFFFF);
2860 }
2861 if (displayID == 0 || SDL_GetDisplayIndex(displayID) < 0) {
2862 displayID = SDL_GetPrimaryDisplay();
2863 }
2864
2865 SDL_zero(bounds);
2866 if (!SDL_GetDisplayUsableBounds(displayID, &bounds) || w > bounds.w || h > bounds.h) {
2867 if (!SDL_GetDisplayBounds(displayID, &bounds)) {
2868 return false;
2869 }
2870 }
2871 if (SDL_WINDOWPOS_ISCENTERED(x)) {
2872 x = bounds.x + (bounds.w - w) / 2;
2873 }
2874 if (SDL_WINDOWPOS_ISCENTERED(y)) {
2875 y = bounds.y + (bounds.h - h) / 2;
2876 }
2877 }
2878
2879 window->pending.x = x;
2880 window->pending.y = y;
2881 window->undefined_x = false;
2882 window->undefined_y = false;
2883 window->last_position_pending = true;
2884
2885 if (_this->SetWindowPosition) {
2886 const bool result = _this->SetWindowPosition(_this, window);
2887 if (result) {
2888 SDL_SyncIfRequired(window);
2889 }
2890 return result;
2891 }
2892
2893 return SDL_Unsupported();
2894}
2895
2896bool SDL_GetWindowPosition(SDL_Window *window, int *x, int *y)
2897{
2898 CHECK_WINDOW_MAGIC(window, false);
2899
2900 // Fullscreen windows are always at their display's origin
2901 if (window->flags & SDL_WINDOW_FULLSCREEN) {
2902 SDL_DisplayID displayID;
2903
2904 if (x) {
2905 *x = 0;
2906 }
2907 if (y) {
2908 *y = 0;
2909 }
2910
2911 /* Find the window's monitor and update to the
2912 monitor offset. */
2913 displayID = SDL_GetDisplayForWindow(window);
2914 if (displayID != 0) {
2915 SDL_Rect bounds;
2916
2917 SDL_zero(bounds);
2918
2919 SDL_GetDisplayBounds(displayID, &bounds);
2920 if (x) {
2921 *x = bounds.x;
2922 }
2923 if (y) {
2924 *y = bounds.y;
2925 }
2926 }
2927 } else {
2928 const bool use_current = !(window->flags & SDL_WINDOW_HIDDEN) && !window->last_position_pending;
2929 if (x) {
2930 *x = use_current ? window->x : window->pending.x;
2931 }
2932 if (y) {
2933 *y = use_current ? window->y : window->pending.y;
2934 }
2935 }
2936 return true;
2937}
2938
2939bool SDL_SetWindowBordered(SDL_Window *window, bool bordered)
2940{
2941 CHECK_WINDOW_MAGIC(window, false);
2942 CHECK_WINDOW_NOT_POPUP(window, false);
2943
2944 const bool want = (bordered != false); // normalize the flag.
2945 const bool have = !(window->flags & SDL_WINDOW_BORDERLESS);
2946 if ((want != have) && (_this->SetWindowBordered)) {
2947 if (want) {
2948 window->flags &= ~SDL_WINDOW_BORDERLESS;
2949 } else {
2950 window->flags |= SDL_WINDOW_BORDERLESS;
2951 }
2952 _this->SetWindowBordered(_this, window, want);
2953 }
2954
2955 return true;
2956}
2957
2958bool SDL_SetWindowResizable(SDL_Window *window, bool resizable)
2959{
2960 CHECK_WINDOW_MAGIC(window, false);
2961 CHECK_WINDOW_NOT_POPUP(window, false);
2962
2963 const bool want = (resizable != false); // normalize the flag.
2964 const bool have = ((window->flags & SDL_WINDOW_RESIZABLE) != 0);
2965 if ((want != have) && (_this->SetWindowResizable)) {
2966 if (want) {
2967 window->flags |= SDL_WINDOW_RESIZABLE;
2968 } else {
2969 window->flags &= ~SDL_WINDOW_RESIZABLE;
2970 SDL_copyp(&window->windowed, &window->floating);
2971 }
2972 _this->SetWindowResizable(_this, window, want);
2973 }
2974
2975 return true;
2976}
2977
2978bool SDL_SetWindowAlwaysOnTop(SDL_Window *window, bool on_top)
2979{
2980 CHECK_WINDOW_MAGIC(window, false);
2981 CHECK_WINDOW_NOT_POPUP(window, false);
2982
2983 const bool want = (on_top != false); // normalize the flag.
2984 const bool have = ((window->flags & SDL_WINDOW_ALWAYS_ON_TOP) != 0);
2985 if ((want != have) && (_this->SetWindowAlwaysOnTop)) {
2986 if (want) {
2987 window->flags |= SDL_WINDOW_ALWAYS_ON_TOP;
2988 } else {
2989 window->flags &= ~SDL_WINDOW_ALWAYS_ON_TOP;
2990 }
2991 _this->SetWindowAlwaysOnTop(_this, window, want);
2992 }
2993
2994 return true;
2995}
2996
2997bool SDL_SetWindowSize(SDL_Window *window, int w, int h)
2998{
2999 CHECK_WINDOW_MAGIC(window, false);
3000
3001 if (w <= 0) {
3002 return SDL_InvalidParamError("w");
3003 }
3004 if (h <= 0) {
3005 return SDL_InvalidParamError("h");
3006 }
3007
3008 // It is possible for the aspect ratio constraints to not satisfy the size constraints.
3009 // The size constraints will override the aspect ratio constraints so we will apply the
3010 // the aspect ratio constraints first
3011 float new_aspect = w / (float)h;
3012 if (window->max_aspect > 0.0f && new_aspect > window->max_aspect) {
3013 w = (int)SDL_roundf(h * window->max_aspect);
3014 } else if (window->min_aspect > 0.0f && new_aspect < window->min_aspect) {
3015 h = (int)SDL_roundf(w / window->min_aspect);
3016 }
3017
3018 // Make sure we don't exceed any window size limits
3019 if (window->min_w && w < window->min_w) {
3020 w = window->min_w;
3021 }
3022 if (window->max_w && w > window->max_w) {
3023 w = window->max_w;
3024 }
3025 if (window->min_h && h < window->min_h) {
3026 h = window->min_h;
3027 }
3028 if (window->max_h && h > window->max_h) {
3029 h = window->max_h;
3030 }
3031
3032 window->last_size_pending = true;
3033 window->pending.w = w;
3034 window->pending.h = h;
3035
3036 if (_this->SetWindowSize) {
3037 _this->SetWindowSize(_this, window);
3038 SDL_SyncIfRequired(window);
3039 } else {
3040 return SDL_Unsupported();
3041 }
3042 return true;
3043}
3044
3045bool SDL_GetWindowSize(SDL_Window *window, int *w, int *h)
3046{
3047 CHECK_WINDOW_MAGIC(window, false);
3048 if (w) {
3049 *w = window->w;
3050 }
3051 if (h) {
3052 *h = window->h;
3053 }
3054 return true;
3055}
3056
3057bool SDL_SetWindowAspectRatio(SDL_Window *window, float min_aspect, float max_aspect)
3058{
3059 CHECK_WINDOW_MAGIC(window, false);
3060
3061 window->min_aspect = min_aspect;
3062 window->max_aspect = max_aspect;
3063 if (_this->SetWindowAspectRatio) {
3064 _this->SetWindowAspectRatio(_this, window);
3065 }
3066 return SDL_SetWindowSize(window, window->floating.w, window->floating.h);
3067}
3068
3069bool SDL_GetWindowAspectRatio(SDL_Window *window, float *min_aspect, float *max_aspect)
3070{
3071 CHECK_WINDOW_MAGIC(window, false);
3072
3073 if (min_aspect) {
3074 *min_aspect = window->min_aspect;
3075 }
3076 if (max_aspect) {
3077 *max_aspect = window->max_aspect;
3078 }
3079 return true;
3080}
3081
3082bool SDL_GetWindowBordersSize(SDL_Window *window, int *top, int *left, int *bottom, int *right)
3083{
3084 int dummy = 0;
3085
3086 if (!top) {
3087 top = &dummy;
3088 }
3089 if (!left) {
3090 left = &dummy;
3091 }
3092 if (!right) {
3093 right = &dummy;
3094 }
3095 if (!bottom) {
3096 bottom = &dummy;
3097 }
3098
3099 // Always initialize, so applications don't have to care
3100 *top = *left = *bottom = *right = 0;
3101
3102 CHECK_WINDOW_MAGIC(window, false);
3103
3104 if (!_this->GetWindowBordersSize) {
3105 return SDL_Unsupported();
3106 }
3107
3108 return _this->GetWindowBordersSize(_this, window, top, left, bottom, right);
3109}
3110
3111bool SDL_GetWindowSizeInPixels(SDL_Window *window, int *w, int *h)
3112{
3113 int filter;
3114
3115 CHECK_WINDOW_MAGIC(window, false);
3116
3117 if (!w) {
3118 w = &filter;
3119 }
3120
3121 if (!h) {
3122 h = &filter;
3123 }
3124
3125 if (_this->GetWindowSizeInPixels) {
3126 _this->GetWindowSizeInPixels(_this, window, w, h);
3127 } else {
3128 SDL_DisplayID displayID = SDL_GetDisplayForWindow(window);
3129 const SDL_DisplayMode *mode;
3130
3131 SDL_GetWindowSize(window, w, h);
3132
3133 if ((window->flags & SDL_WINDOW_FULLSCREEN) && SDL_GetWindowFullscreenMode(window)) {
3134 mode = SDL_GetCurrentDisplayMode(displayID);
3135 } else {
3136 mode = SDL_GetDesktopDisplayMode(displayID);
3137 }
3138 if (mode) {
3139 *w = (int)SDL_ceilf(*w * mode->pixel_density);
3140 *h = (int)SDL_ceilf(*h * mode->pixel_density);
3141 }
3142 }
3143 return true;
3144}
3145
3146bool SDL_SetWindowMinimumSize(SDL_Window *window, int min_w, int min_h)
3147{
3148 CHECK_WINDOW_MAGIC(window, false);
3149 if (min_w < 0) {
3150 return SDL_InvalidParamError("min_w");
3151 }
3152 if (min_h < 0) {
3153 return SDL_InvalidParamError("min_h");
3154 }
3155
3156 if ((window->max_w && min_w > window->max_w) ||
3157 (window->max_h && min_h > window->max_h)) {
3158 return SDL_SetError("SDL_SetWindowMinimumSize(): Tried to set minimum size larger than maximum size");
3159 }
3160
3161 window->min_w = min_w;
3162 window->min_h = min_h;
3163
3164 if (_this->SetWindowMinimumSize) {
3165 _this->SetWindowMinimumSize(_this, window);
3166 }
3167
3168 // Ensure that window is not smaller than minimal size
3169 int w = window->last_size_pending ? window->pending.w : window->floating.w;
3170 int h = window->last_size_pending ? window->pending.h : window->floating.h;
3171 w = window->min_w ? SDL_max(w, window->min_w) : w;
3172 h = window->min_h ? SDL_max(h, window->min_h) : h;
3173 return SDL_SetWindowSize(window, w, h);
3174}
3175
3176bool SDL_GetWindowMinimumSize(SDL_Window *window, int *min_w, int *min_h)
3177{
3178 CHECK_WINDOW_MAGIC(window, false);
3179 if (min_w) {
3180 *min_w = window->min_w;
3181 }
3182 if (min_h) {
3183 *min_h = window->min_h;
3184 }
3185 return true;
3186}
3187
3188bool SDL_SetWindowMaximumSize(SDL_Window *window, int max_w, int max_h)
3189{
3190 CHECK_WINDOW_MAGIC(window, false);
3191 if (max_w < 0) {
3192 return SDL_InvalidParamError("max_w");
3193 }
3194 if (max_h < 0) {
3195 return SDL_InvalidParamError("max_h");
3196 }
3197
3198 if ((max_w && max_w < window->min_w) ||
3199 (max_h && max_h < window->min_h)) {
3200 return SDL_SetError("SDL_SetWindowMaximumSize(): Tried to set maximum size smaller than minimum size");
3201 }
3202
3203 window->max_w = max_w;
3204 window->max_h = max_h;
3205
3206 if (_this->SetWindowMaximumSize) {
3207 _this->SetWindowMaximumSize(_this, window);
3208 }
3209
3210 // Ensure that window is not larger than maximal size
3211 int w = window->last_size_pending ? window->pending.w : window->floating.w;
3212 int h = window->last_size_pending ? window->pending.h : window->floating.h;
3213 w = window->max_w ? SDL_min(w, window->max_w) : w;
3214 h = window->max_h ? SDL_min(h, window->max_h) : h;
3215 return SDL_SetWindowSize(window, w, h);
3216}
3217
3218bool SDL_GetWindowMaximumSize(SDL_Window *window, int *max_w, int *max_h)
3219{
3220 CHECK_WINDOW_MAGIC(window, false);
3221 if (max_w) {
3222 *max_w = window->max_w;
3223 }
3224 if (max_h) {
3225 *max_h = window->max_h;
3226 }
3227 return true;
3228}
3229
3230bool SDL_ShowWindow(SDL_Window *window)
3231{
3232 SDL_Window *child;
3233 CHECK_WINDOW_MAGIC(window, false);
3234
3235 if (!(window->flags & SDL_WINDOW_HIDDEN)) {
3236 return true;
3237 }
3238
3239 // If the parent is hidden, set the flag to restore this when the parent is shown
3240 if (window->parent && (window->parent->flags & SDL_WINDOW_HIDDEN)) {
3241 window->restore_on_show = true;
3242 return true;
3243 }
3244
3245 if (_this->ShowWindow) {
3246 _this->ShowWindow(_this, window);
3247 } else {
3248 SDL_SetMouseFocus(window);
3249 SDL_SetKeyboardFocus(window);
3250 }
3251 SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_SHOWN, 0, 0);
3252
3253 // Restore child windows
3254 for (child = window->first_child; child; child = child->next_sibling) {
3255 if (!child->restore_on_show && (child->flags & SDL_WINDOW_HIDDEN)) {
3256 break;
3257 }
3258 SDL_ShowWindow(child);
3259 child->restore_on_show = false;
3260 }
3261 return true;
3262}
3263
3264bool SDL_HideWindow(SDL_Window *window)
3265{
3266 SDL_Window *child;
3267 CHECK_WINDOW_MAGIC(window, false);
3268
3269 if (window->flags & SDL_WINDOW_HIDDEN) {
3270 window->restore_on_show = false;
3271 return true;
3272 }
3273
3274 // Hide all child windows
3275 for (child = window->first_child; child; child = child->next_sibling) {
3276 if (child->flags & SDL_WINDOW_HIDDEN) {
3277 break;
3278 }
3279 SDL_HideWindow(child);
3280 child->restore_on_show = true;
3281 }
3282
3283 // Store the flags for restoration later.
3284 const SDL_WindowFlags pending_mask = (SDL_WINDOW_MAXIMIZED | SDL_WINDOW_MINIMIZED | SDL_WINDOW_FULLSCREEN | SDL_WINDOW_KEYBOARD_GRABBED | SDL_WINDOW_MOUSE_GRABBED);
3285 window->pending_flags = (window->flags & pending_mask);
3286
3287 window->is_hiding = true;
3288 if (_this->HideWindow) {
3289 _this->HideWindow(_this, window);
3290 } else {
3291 SDL_SetMouseFocus(NULL);
3292 SDL_SetKeyboardFocus(NULL);
3293 }
3294 window->is_hiding = false;
3295 SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_HIDDEN, 0, 0);
3296 return true;
3297}
3298
3299bool SDL_RaiseWindow(SDL_Window *window)
3300{
3301 CHECK_WINDOW_MAGIC(window, false);
3302
3303 if (window->flags & SDL_WINDOW_HIDDEN) {
3304 return true;
3305 }
3306 if (_this->RaiseWindow) {
3307 _this->RaiseWindow(_this, window);
3308 }
3309 return true;
3310}
3311
3312bool SDL_MaximizeWindow(SDL_Window *window)
3313{
3314 CHECK_WINDOW_MAGIC(window, false);
3315 CHECK_WINDOW_NOT_POPUP(window, false);
3316
3317 if (!_this->MaximizeWindow) {
3318 return SDL_Unsupported();
3319 }
3320
3321 if (!(window->flags & SDL_WINDOW_RESIZABLE)) {
3322 return SDL_SetError("A window without the 'SDL_WINDOW_RESIZABLE' flag can't be maximized");
3323 }
3324
3325 if (window->flags & SDL_WINDOW_HIDDEN) {
3326 window->pending_flags |= SDL_WINDOW_MAXIMIZED;
3327 return true;
3328 }
3329
3330 _this->MaximizeWindow(_this, window);
3331 SDL_SyncIfRequired(window);
3332 return true;
3333}
3334
3335bool SDL_MinimizeWindow(SDL_Window *window)
3336{
3337 CHECK_WINDOW_MAGIC(window, false);
3338 CHECK_WINDOW_NOT_POPUP(window, false);
3339
3340 if (!_this->MinimizeWindow) {
3341 return SDL_Unsupported();
3342 }
3343
3344 if (window->flags & SDL_WINDOW_HIDDEN) {
3345 window->pending_flags |= SDL_WINDOW_MINIMIZED;
3346 return true;
3347 }
3348
3349 _this->MinimizeWindow(_this, window);
3350 SDL_SyncIfRequired(window);
3351 return true;
3352}
3353
3354bool SDL_RestoreWindow(SDL_Window *window)
3355{
3356 CHECK_WINDOW_MAGIC(window, false);
3357 CHECK_WINDOW_NOT_POPUP(window, false);
3358
3359 if (!_this->RestoreWindow) {
3360 return SDL_Unsupported();
3361 }
3362
3363 if (window->flags & SDL_WINDOW_HIDDEN) {
3364 window->pending_flags &= ~(SDL_WINDOW_MAXIMIZED | SDL_WINDOW_MINIMIZED);
3365 return true;
3366 }
3367
3368 _this->RestoreWindow(_this, window);
3369 SDL_SyncIfRequired(window);
3370 return true;
3371}
3372
3373bool SDL_SetWindowFullscreen(SDL_Window *window, bool fullscreen)
3374{
3375 bool result;
3376
3377 CHECK_WINDOW_MAGIC(window, false);
3378 CHECK_WINDOW_NOT_POPUP(window, false);
3379
3380 if (window->flags & SDL_WINDOW_HIDDEN) {
3381 if (fullscreen) {
3382 window->pending_flags |= SDL_WINDOW_FULLSCREEN;
3383 } else {
3384 window->pending_flags &= ~SDL_WINDOW_FULLSCREEN;
3385 }
3386 return true;
3387 }
3388
3389 if (fullscreen) {
3390 // Set the current fullscreen mode to the desired mode
3391 SDL_copyp(&window->current_fullscreen_mode, &window->requested_fullscreen_mode);
3392 }
3393
3394 result = SDL_UpdateFullscreenMode(window, fullscreen ? SDL_FULLSCREEN_OP_ENTER : SDL_FULLSCREEN_OP_LEAVE, true);
3395
3396 if (!fullscreen || !result) {
3397 // Clear the current fullscreen mode.
3398 SDL_zero(window->current_fullscreen_mode);
3399 }
3400
3401 if (result) {
3402 SDL_SyncIfRequired(window);
3403 }
3404
3405 return result;
3406}
3407
3408bool SDL_SyncWindow(SDL_Window *window)
3409{
3410 CHECK_WINDOW_MAGIC(window, false)
3411
3412 if (_this->SyncWindow) {
3413 return _this->SyncWindow(_this, window);
3414 } else {
3415 return true;
3416 }
3417}
3418
3419static bool ShouldAttemptTextureFramebuffer(void)
3420{
3421 const char *hint;
3422 bool attempt_texture_framebuffer = true;
3423
3424 // The dummy driver never has GPU support, of course.
3425 if (_this->is_dummy) {
3426 return false;
3427 }
3428
3429 // See if there's a hint override
3430 hint = SDL_GetHint(SDL_HINT_FRAMEBUFFER_ACCELERATION);
3431 if (hint && *hint) {
3432 if (*hint == '0' || SDL_strcasecmp(hint, "false") == 0 || SDL_strcasecmp(hint, SDL_SOFTWARE_RENDERER) == 0) {
3433 attempt_texture_framebuffer = false;
3434 } else {
3435 attempt_texture_framebuffer = true;
3436 }
3437 } else {
3438 // Check for platform specific defaults
3439#ifdef SDL_PLATFORM_LINUX
3440 // On WSL, direct X11 is faster than using OpenGL for window framebuffers, so try to detect WSL and avoid texture framebuffer.
3441 if ((_this->CreateWindowFramebuffer) && (SDL_strcmp(_this->name, "x11") == 0)) {
3442 struct stat sb;
3443 if ((stat("/proc/sys/fs/binfmt_misc/WSLInterop", &sb) == 0) || (stat("/run/WSL", &sb) == 0)) { // if either of these exist, we're on WSL.
3444 attempt_texture_framebuffer = false;
3445 }
3446 }
3447#endif
3448#if defined(SDL_PLATFORM_WIN32) || defined(SDL_PLATFORM_WINGDK) // GDI BitBlt() is way faster than Direct3D dynamic textures right now. (!!! FIXME: is this still true?)
3449 if (_this->CreateWindowFramebuffer && (SDL_strcmp(_this->name, "windows") == 0)) {
3450 attempt_texture_framebuffer = false;
3451 }
3452#endif
3453#ifdef SDL_PLATFORM_EMSCRIPTEN
3454 attempt_texture_framebuffer = false;
3455#endif
3456 }
3457 return attempt_texture_framebuffer;
3458}
3459
3460static SDL_Surface *SDL_CreateWindowFramebuffer(SDL_Window *window)
3461{
3462 SDL_PixelFormat format = SDL_PIXELFORMAT_UNKNOWN;
3463 void *pixels = NULL;
3464 int pitch = 0;
3465 bool created_framebuffer = false;
3466 int w, h;
3467
3468 SDL_GetWindowSizeInPixels(window, &w, &h);
3469
3470 /* This will switch the video backend from using a software surface to
3471 using a GPU texture through the 2D render API, if we think this would
3472 be more efficient. This only checks once, on demand. */
3473 if (!_this->checked_texture_framebuffer) {
3474 if (ShouldAttemptTextureFramebuffer()) {
3475 if (!SDL_CreateWindowTexture(_this, window, &format, &pixels, &pitch)) {
3476 /* !!! FIXME: if this failed halfway (made renderer, failed to make texture, etc),
3477 !!! FIXME: we probably need to clean this up so it doesn't interfere with
3478 !!! FIXME: a software fallback at the system level (can we blit to an
3479 !!! FIXME: OpenGL window? etc). */
3480 } else {
3481 // future attempts will just try to use a texture framebuffer.
3482 /* !!! FIXME: maybe we shouldn't override these but check if we used a texture
3483 !!! FIXME: framebuffer at the right places; is it feasible we could have an
3484 !!! FIXME: accelerated OpenGL window and a second ends up in software? */
3485 _this->CreateWindowFramebuffer = SDL_CreateWindowTexture;
3486 _this->SetWindowFramebufferVSync = SDL_SetWindowTextureVSync;
3487 _this->GetWindowFramebufferVSync = SDL_GetWindowTextureVSync;
3488 _this->UpdateWindowFramebuffer = SDL_UpdateWindowTexture;
3489 _this->DestroyWindowFramebuffer = SDL_DestroyWindowTexture;
3490 created_framebuffer = true;
3491 }
3492 }
3493
3494 _this->checked_texture_framebuffer = true; // don't check this again.
3495 }
3496
3497 if (!created_framebuffer) {
3498 if (!_this->CreateWindowFramebuffer || !_this->UpdateWindowFramebuffer) {
3499 SDL_SetError("Window framebuffer support not available");
3500 return NULL;
3501 }
3502
3503 if (!_this->CreateWindowFramebuffer(_this, window, &format, &pixels, &pitch)) {
3504 return NULL;
3505 }
3506 }
3507
3508 if (window->surface) {
3509 // We may have gone recursive and already created the surface
3510 return window->surface;
3511 }
3512
3513 return SDL_CreateSurfaceFrom(w, h, format, pixels, pitch);
3514}
3515
3516bool SDL_WindowHasSurface(SDL_Window *window)
3517{
3518 CHECK_WINDOW_MAGIC(window, false);
3519
3520 return window->surface ? true : false;
3521}
3522
3523SDL_Surface *SDL_GetWindowSurface(SDL_Window *window)
3524{
3525 CHECK_WINDOW_MAGIC(window, NULL);
3526
3527 if (!window->surface_valid) {
3528 if (window->surface) {
3529 window->surface->internal_flags &= ~SDL_INTERNAL_SURFACE_DONTFREE;
3530 SDL_DestroySurface(window->surface);
3531 window->surface = NULL;
3532 }
3533
3534 window->surface = SDL_CreateWindowFramebuffer(window);
3535 if (window->surface) {
3536 window->surface_valid = true;
3537 window->surface->internal_flags |= SDL_INTERNAL_SURFACE_DONTFREE;
3538 }
3539 }
3540 return window->surface;
3541}
3542
3543bool SDL_SetWindowSurfaceVSync(SDL_Window *window, int vsync)
3544{
3545 CHECK_WINDOW_MAGIC(window, false);
3546
3547 if (!_this->SetWindowFramebufferVSync) {
3548 return SDL_Unsupported();
3549 }
3550 return _this->SetWindowFramebufferVSync(_this, window, vsync);
3551}
3552
3553bool SDL_GetWindowSurfaceVSync(SDL_Window *window, int *vsync)
3554{
3555 CHECK_WINDOW_MAGIC(window, false);
3556
3557 if (!_this->GetWindowFramebufferVSync) {
3558 return SDL_Unsupported();
3559 }
3560 return _this->GetWindowFramebufferVSync(_this, window, vsync);
3561}
3562
3563bool SDL_UpdateWindowSurface(SDL_Window *window)
3564{
3565 SDL_Rect full_rect;
3566
3567 CHECK_WINDOW_MAGIC(window, false);
3568
3569 full_rect.x = 0;
3570 full_rect.y = 0;
3571 SDL_GetWindowSizeInPixels(window, &full_rect.w, &full_rect.h);
3572
3573 return SDL_UpdateWindowSurfaceRects(window, &full_rect, 1);
3574}
3575
3576bool SDL_UpdateWindowSurfaceRects(SDL_Window *window, const SDL_Rect *rects,
3577 int numrects)
3578{
3579 CHECK_WINDOW_MAGIC(window, false);
3580
3581 if (!window->surface_valid) {
3582 return SDL_SetError("Window surface is invalid, please call SDL_GetWindowSurface() to get a new surface");
3583 }
3584
3585 SDL_assert(_this->checked_texture_framebuffer); // we should have done this before we had a valid surface.
3586
3587 return _this->UpdateWindowFramebuffer(_this, window, rects, numrects);
3588}
3589
3590bool SDL_DestroyWindowSurface(SDL_Window *window)
3591{
3592 CHECK_WINDOW_MAGIC(window, false);
3593
3594 if (window->surface) {
3595 window->surface->internal_flags &= ~SDL_INTERNAL_SURFACE_DONTFREE;
3596 SDL_DestroySurface(window->surface);
3597 window->surface = NULL;
3598 window->surface_valid = false;
3599 }
3600
3601 if (_this->checked_texture_framebuffer) { // never checked? No framebuffer to destroy. Don't risk calling the wrong implementation.
3602 if (_this->DestroyWindowFramebuffer) {
3603 _this->DestroyWindowFramebuffer(_this, window);
3604 }
3605 }
3606 return true;
3607}
3608
3609bool SDL_SetWindowOpacity(SDL_Window *window, float opacity)
3610{
3611 bool result;
3612
3613 CHECK_WINDOW_MAGIC(window, false);
3614
3615 if (!_this->SetWindowOpacity) {
3616 return SDL_Unsupported();
3617 }
3618
3619 if (opacity < 0.0f) {
3620 opacity = 0.0f;
3621 } else if (opacity > 1.0f) {
3622 opacity = 1.0f;
3623 }
3624
3625 result = _this->SetWindowOpacity(_this, window, opacity);
3626 if (result) {
3627 window->opacity = opacity;
3628 }
3629
3630 return result;
3631}
3632
3633float SDL_GetWindowOpacity(SDL_Window *window)
3634{
3635 CHECK_WINDOW_MAGIC(window, -1.0f);
3636
3637 return window->opacity;
3638}
3639
3640bool SDL_SetWindowParent(SDL_Window *window, SDL_Window *parent)
3641{
3642 CHECK_WINDOW_MAGIC(window, false);
3643 CHECK_WINDOW_NOT_POPUP(window, false);
3644
3645 if (parent) {
3646 CHECK_WINDOW_MAGIC(parent, false);
3647 CHECK_WINDOW_NOT_POPUP(parent, false);
3648 }
3649
3650 if (!_this->SetWindowParent) {
3651 return SDL_Unsupported();
3652 }
3653
3654 if (window->flags & SDL_WINDOW_MODAL) {
3655 return SDL_SetError("Modal windows cannot change parents; call SDL_SetWindowModal() to clear modal status first.");
3656 }
3657
3658 if (window->parent == parent) {
3659 return true;
3660 }
3661
3662 const bool ret = _this->SetWindowParent(_this, window, parent);
3663 SDL_UpdateWindowHierarchy(window, ret ? parent : NULL);
3664
3665 return ret;
3666}
3667
3668bool SDL_SetWindowModal(SDL_Window *window, bool modal)
3669{
3670 CHECK_WINDOW_MAGIC(window, false);
3671 CHECK_WINDOW_NOT_POPUP(window, false);
3672
3673 if (!_this->SetWindowModal) {
3674 return SDL_Unsupported();
3675 }
3676
3677 if (modal) {
3678 if (!window->parent) {
3679 return SDL_SetError("Window must have a parent to enable the modal state; use SDL_SetWindowParent() to set the parent first.");
3680 }
3681 window->flags |= SDL_WINDOW_MODAL;
3682 } else if (window->flags & SDL_WINDOW_MODAL) {
3683 window->flags &= ~SDL_WINDOW_MODAL;
3684 } else {
3685 return true; // Already not modal, so nothing to do.
3686 }
3687
3688 if (window->flags & SDL_WINDOW_HIDDEN) {
3689 return true;
3690 }
3691
3692 return _this->SetWindowModal(_this, window, modal);
3693}
3694
3695bool SDL_SetWindowFocusable(SDL_Window *window, bool focusable)
3696{
3697 CHECK_WINDOW_MAGIC(window, false);
3698
3699 const bool want = (focusable != false); // normalize the flag.
3700 const bool have = !(window->flags & SDL_WINDOW_NOT_FOCUSABLE);
3701 if ((want != have) && (_this->SetWindowFocusable)) {
3702 if (want) {
3703 window->flags &= ~SDL_WINDOW_NOT_FOCUSABLE;
3704 } else {
3705 window->flags |= SDL_WINDOW_NOT_FOCUSABLE;
3706 }
3707 if (!_this->SetWindowFocusable(_this, window, want)) {
3708 return false;
3709 }
3710 }
3711
3712 return true;
3713}
3714
3715void SDL_UpdateWindowGrab(SDL_Window *window)
3716{
3717 bool keyboard_grabbed, mouse_grabbed;
3718
3719 if (window->flags & SDL_WINDOW_INPUT_FOCUS) {
3720 if (SDL_GetMouse()->relative_mode || (window->flags & SDL_WINDOW_MOUSE_GRABBED)) {
3721 mouse_grabbed = true;
3722 } else {
3723 mouse_grabbed = false;
3724 }
3725
3726 if (window->flags & SDL_WINDOW_KEYBOARD_GRABBED) {
3727 keyboard_grabbed = true;
3728 } else {
3729 keyboard_grabbed = false;
3730 }
3731 } else {
3732 mouse_grabbed = false;
3733 keyboard_grabbed = false;
3734 }
3735
3736 if (mouse_grabbed || keyboard_grabbed) {
3737 if (_this->grabbed_window && (_this->grabbed_window != window)) {
3738 // stealing a grab from another window!
3739 _this->grabbed_window->flags &= ~(SDL_WINDOW_MOUSE_GRABBED | SDL_WINDOW_KEYBOARD_GRABBED);
3740 if (_this->SetWindowMouseGrab) {
3741 _this->SetWindowMouseGrab(_this, _this->grabbed_window, false);
3742 }
3743 if (_this->SetWindowKeyboardGrab) {
3744 _this->SetWindowKeyboardGrab(_this, _this->grabbed_window, false);
3745 }
3746 }
3747 _this->grabbed_window = window;
3748 } else if (_this->grabbed_window == window) {
3749 _this->grabbed_window = NULL; // ungrabbing input.
3750 }
3751
3752 if (_this->SetWindowMouseGrab) {
3753 if (!_this->SetWindowMouseGrab(_this, window, mouse_grabbed)) {
3754 window->flags &= ~SDL_WINDOW_MOUSE_GRABBED;
3755 }
3756 }
3757 if (_this->SetWindowKeyboardGrab) {
3758 if (!_this->SetWindowKeyboardGrab(_this, window, keyboard_grabbed)) {
3759 window->flags &= ~SDL_WINDOW_KEYBOARD_GRABBED;
3760 }
3761 }
3762
3763 if (_this->grabbed_window && !(_this->grabbed_window->flags & (SDL_WINDOW_MOUSE_GRABBED | SDL_WINDOW_KEYBOARD_GRABBED))) {
3764 _this->grabbed_window = NULL;
3765 }
3766}
3767
3768bool SDL_SetWindowKeyboardGrab(SDL_Window *window, bool grabbed)
3769{
3770 CHECK_WINDOW_MAGIC(window, false);
3771 CHECK_WINDOW_NOT_POPUP(window, false);
3772
3773 if (window->flags & SDL_WINDOW_HIDDEN) {
3774 if (grabbed) {
3775 window->pending_flags |= SDL_WINDOW_KEYBOARD_GRABBED;
3776 } else {
3777 window->pending_flags &= ~SDL_WINDOW_KEYBOARD_GRABBED;
3778 }
3779 return true;
3780 }
3781
3782 if (!!grabbed == !!(window->flags & SDL_WINDOW_KEYBOARD_GRABBED)) {
3783 return true;
3784 }
3785 if (grabbed) {
3786 window->flags |= SDL_WINDOW_KEYBOARD_GRABBED;
3787 } else {
3788 window->flags &= ~SDL_WINDOW_KEYBOARD_GRABBED;
3789 }
3790 SDL_UpdateWindowGrab(window);
3791
3792 if (grabbed && !(window->flags & SDL_WINDOW_KEYBOARD_GRABBED)) {
3793 return false;
3794 }
3795 return true;
3796}
3797
3798bool SDL_SetWindowMouseGrab(SDL_Window *window, bool grabbed)
3799{
3800 CHECK_WINDOW_MAGIC(window, false);
3801 CHECK_WINDOW_NOT_POPUP(window, false);
3802
3803 if (window->flags & SDL_WINDOW_HIDDEN) {
3804 if (grabbed) {
3805 window->pending_flags |= SDL_WINDOW_MOUSE_GRABBED;
3806 } else {
3807 window->pending_flags &= ~SDL_WINDOW_MOUSE_GRABBED;
3808 }
3809 return true;
3810 }
3811
3812 if (!!grabbed == !!(window->flags & SDL_WINDOW_MOUSE_GRABBED)) {
3813 return true;
3814 }
3815 if (grabbed) {
3816 window->flags |= SDL_WINDOW_MOUSE_GRABBED;
3817 } else {
3818 window->flags &= ~SDL_WINDOW_MOUSE_GRABBED;
3819 }
3820 SDL_UpdateWindowGrab(window);
3821
3822 if (grabbed && !(window->flags & SDL_WINDOW_MOUSE_GRABBED)) {
3823 return false;
3824 }
3825 return true;
3826}
3827
3828bool SDL_GetWindowKeyboardGrab(SDL_Window *window)
3829{
3830 CHECK_WINDOW_MAGIC(window, false);
3831 return window == _this->grabbed_window && (_this->grabbed_window->flags & SDL_WINDOW_KEYBOARD_GRABBED);
3832}
3833
3834bool SDL_GetWindowMouseGrab(SDL_Window *window)
3835{
3836 CHECK_WINDOW_MAGIC(window, false);
3837 return window == _this->grabbed_window && (_this->grabbed_window->flags & SDL_WINDOW_MOUSE_GRABBED);
3838}
3839
3840SDL_Window *SDL_GetGrabbedWindow(void)
3841{
3842 if (_this->grabbed_window &&
3843 (_this->grabbed_window->flags & (SDL_WINDOW_MOUSE_GRABBED | SDL_WINDOW_KEYBOARD_GRABBED)) != 0) {
3844 return _this->grabbed_window;
3845 } else {
3846 return NULL;
3847 }
3848}
3849
3850bool SDL_SetWindowMouseRect(SDL_Window *window, const SDL_Rect *rect)
3851{
3852 CHECK_WINDOW_MAGIC(window, false);
3853
3854 if (rect) {
3855 SDL_memcpy(&window->mouse_rect, rect, sizeof(*rect));
3856 } else {
3857 SDL_zero(window->mouse_rect);
3858 }
3859
3860 if (_this->SetWindowMouseRect) {
3861 return _this->SetWindowMouseRect(_this, window);
3862 }
3863 return true;
3864}
3865
3866const SDL_Rect *SDL_GetWindowMouseRect(SDL_Window *window)
3867{
3868 CHECK_WINDOW_MAGIC(window, NULL);
3869
3870 if (SDL_RectEmpty(&window->mouse_rect)) {
3871 return NULL;
3872 } else {
3873 return &window->mouse_rect;
3874 }
3875}
3876
3877bool SDL_SetWindowRelativeMouseMode(SDL_Window *window, bool enabled)
3878{
3879 CHECK_WINDOW_MAGIC(window, false);
3880
3881 /* If the app toggles relative mode directly, it probably shouldn't
3882 * also be emulating it using repeated mouse warps, so disable
3883 * mouse warp emulation by default.
3884 */
3885 SDL_DisableMouseWarpEmulation();
3886
3887 if (enabled == SDL_GetWindowRelativeMouseMode(window)) {
3888 return true;
3889 }
3890
3891 if (enabled) {
3892 window->flags |= SDL_WINDOW_MOUSE_RELATIVE_MODE;
3893 } else {
3894 window->flags &= ~SDL_WINDOW_MOUSE_RELATIVE_MODE;
3895 }
3896 SDL_UpdateRelativeMouseMode();
3897
3898 return true;
3899}
3900
3901bool SDL_GetWindowRelativeMouseMode(SDL_Window *window)
3902{
3903 CHECK_WINDOW_MAGIC(window, false);
3904
3905 if (window->flags & SDL_WINDOW_MOUSE_RELATIVE_MODE) {
3906 return true;
3907 } else {
3908 return false;
3909 }
3910}
3911
3912bool SDL_FlashWindow(SDL_Window *window, SDL_FlashOperation operation)
3913{
3914 CHECK_WINDOW_MAGIC(window, false);
3915 CHECK_WINDOW_NOT_POPUP(window, false);
3916
3917 if (_this->FlashWindow) {
3918 return _this->FlashWindow(_this, window, operation);
3919 }
3920
3921 return SDL_Unsupported();
3922}
3923
3924void SDL_OnWindowShown(SDL_Window *window)
3925{
3926 // Set window state if we have pending window flags cached
3927 ApplyWindowFlags(window, window->pending_flags);
3928 window->pending_flags = 0;
3929}
3930
3931void SDL_OnWindowHidden(SDL_Window *window)
3932{
3933 /* Store the maximized and fullscreen flags for restoration later, in case
3934 * this was initiated by the window manager due to the window being unmapped
3935 * when minimized.
3936 */
3937 window->pending_flags |= (window->flags & (SDL_WINDOW_FULLSCREEN | SDL_WINDOW_MAXIMIZED));
3938
3939 // The window is already hidden at this point, so just change the mode back if necessary.
3940 SDL_UpdateFullscreenMode(window, SDL_FULLSCREEN_OP_LEAVE, false);
3941}
3942
3943void SDL_OnWindowDisplayChanged(SDL_Window *window)
3944{
3945 // Don't run this if a fullscreen change was made in an event watcher callback in response to a display changed event.
3946 if (window->update_fullscreen_on_display_changed && (window->flags & SDL_WINDOW_FULLSCREEN)) {
3947 const bool auto_mode_switch = SDL_GetHintBoolean(SDL_HINT_VIDEO_MATCH_EXCLUSIVE_MODE_ON_MOVE, true);
3948
3949 if (auto_mode_switch && (window->requested_fullscreen_mode.w != 0 || window->requested_fullscreen_mode.h != 0)) {
3950 SDL_DisplayID displayID = SDL_GetDisplayForWindowPosition(window);
3951 bool include_high_density_modes = false;
3952
3953 if (window->requested_fullscreen_mode.pixel_density > 1.0f) {
3954 include_high_density_modes = true;
3955 }
3956 const bool found_match = SDL_GetClosestFullscreenDisplayMode(displayID, window->requested_fullscreen_mode.w, window->requested_fullscreen_mode.h,
3957 window->requested_fullscreen_mode.refresh_rate, include_high_density_modes, &window->current_fullscreen_mode);
3958
3959 // If a mode without matching dimensions was not found, just go to fullscreen desktop.
3960 if (!found_match ||
3961 window->requested_fullscreen_mode.w != window->current_fullscreen_mode.w ||
3962 window->requested_fullscreen_mode.h != window->current_fullscreen_mode.h) {
3963 SDL_zero(window->current_fullscreen_mode);
3964 }
3965 } else {
3966 SDL_zero(window->current_fullscreen_mode);
3967 }
3968
3969 if (SDL_WINDOW_FULLSCREEN_VISIBLE(window)) {
3970 SDL_UpdateFullscreenMode(window, SDL_FULLSCREEN_OP_UPDATE, true);
3971 }
3972 }
3973
3974 SDL_CheckWindowPixelSizeChanged(window);
3975}
3976
3977void SDL_OnWindowMoved(SDL_Window *window)
3978{
3979 SDL_CheckWindowDisplayChanged(window);
3980}
3981
3982void SDL_OnWindowResized(SDL_Window *window)
3983{
3984 SDL_CheckWindowDisplayChanged(window);
3985 SDL_CheckWindowPixelSizeChanged(window);
3986 SDL_CheckWindowSafeAreaChanged(window);
3987
3988 if ((window->flags & SDL_WINDOW_TRANSPARENT) && _this->UpdateWindowShape) {
3989 SDL_Surface *surface = (SDL_Surface *)SDL_GetPointerProperty(window->props, SDL_PROP_WINDOW_SHAPE_POINTER, NULL);
3990 if (surface) {
3991 _this->UpdateWindowShape(_this, window, surface);
3992 }
3993 }
3994}
3995
3996void SDL_CheckWindowPixelSizeChanged(SDL_Window *window)
3997{
3998 int pixel_w = 0, pixel_h = 0;
3999
4000 SDL_GetWindowSizeInPixels(window, &pixel_w, &pixel_h);
4001 SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED, pixel_w, pixel_h);
4002
4003 SDL_CheckWindowDisplayScaleChanged(window);
4004}
4005
4006void SDL_OnWindowPixelSizeChanged(SDL_Window *window)
4007{
4008 window->surface_valid = false;
4009}
4010
4011void SDL_OnWindowLiveResizeUpdate(SDL_Window *window)
4012{
4013 if (SDL_HasMainCallbacks()) {
4014 SDL_IterateMainCallbacks(false);
4015 } else {
4016 // Send an expose event so the application can redraw
4017 SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_EXPOSED, 0, 0);
4018 }
4019
4020 SDL_PumpEventMaintenance();
4021}
4022
4023static void SDL_CheckWindowSafeAreaChanged(SDL_Window *window)
4024{
4025 SDL_Rect rect;
4026
4027 rect.x = window->safe_inset_left;
4028 rect.y = window->safe_inset_top;
4029 rect.w = window->w - (window->safe_inset_right + window->safe_inset_left);
4030 rect.h = window->h - (window->safe_inset_top + window->safe_inset_bottom);
4031 if (SDL_memcmp(&rect, &window->safe_rect, sizeof(rect)) != 0) {
4032 SDL_copyp(&window->safe_rect, &rect);
4033 SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_SAFE_AREA_CHANGED, 0, 0);
4034 }
4035}
4036
4037void SDL_SetWindowSafeAreaInsets(SDL_Window *window, int left, int right, int top, int bottom)
4038{
4039 window->safe_inset_left = left;
4040 window->safe_inset_right = right;
4041 window->safe_inset_top = top;
4042 window->safe_inset_bottom = bottom;
4043 SDL_CheckWindowSafeAreaChanged(window);
4044}
4045
4046bool SDL_GetWindowSafeArea(SDL_Window *window, SDL_Rect *rect)
4047{
4048 if (rect) {
4049 SDL_zerop(rect);
4050 }
4051
4052 CHECK_WINDOW_MAGIC(window, false);
4053
4054 if (rect) {
4055 if (SDL_RectEmpty(&window->safe_rect)) {
4056 rect->w = window->w;
4057 rect->h = window->h;
4058 } else {
4059 SDL_copyp(rect, &window->safe_rect);
4060 }
4061 }
4062 return true;
4063}
4064
4065void SDL_OnWindowMinimized(SDL_Window *window)
4066{
4067 if (window->flags & SDL_WINDOW_FULLSCREEN) {
4068 SDL_UpdateFullscreenMode(window, SDL_FULLSCREEN_OP_LEAVE, false);
4069 }
4070}
4071
4072void SDL_OnWindowMaximized(SDL_Window *window)
4073{
4074}
4075
4076void SDL_OnWindowRestored(SDL_Window *window)
4077{
4078 /*
4079 * FIXME: Is this fine to just remove this, or should it be preserved just
4080 * for the fullscreen case? In principle it seems like just hiding/showing
4081 * windows shouldn't affect the stacking order; maybe the right fix is to
4082 * re-decouple OnWindowShown and OnWindowRestored.
4083 */
4084 // SDL_RaiseWindow(window);
4085
4086 if (window->flags & SDL_WINDOW_FULLSCREEN) {
4087 SDL_UpdateFullscreenMode(window, SDL_FULLSCREEN_OP_ENTER, false);
4088 }
4089}
4090
4091void SDL_OnWindowEnter(SDL_Window *window)
4092{
4093 if (_this->OnWindowEnter) {
4094 _this->OnWindowEnter(_this, window);
4095 }
4096}
4097
4098void SDL_OnWindowLeave(SDL_Window *window)
4099{
4100}
4101
4102void SDL_OnWindowFocusGained(SDL_Window *window)
4103{
4104 SDL_Mouse *mouse = SDL_GetMouse();
4105
4106 if (mouse && mouse->relative_mode) {
4107 SDL_SetMouseFocus(window);
4108 }
4109
4110 SDL_UpdateWindowGrab(window);
4111}
4112
4113static bool SDL_ShouldMinimizeOnFocusLoss(SDL_Window *window)
4114{
4115 const char *hint;
4116
4117 if (!(window->flags & SDL_WINDOW_FULLSCREEN) || window->is_destroying) {
4118 return false;
4119 }
4120
4121#if defined(SDL_PLATFORM_MACOS) && defined(SDL_VIDEO_DRIVER_COCOA)
4122 if (SDL_strcmp(_this->name, "cocoa") == 0) { // don't do this for X11, etc
4123 if (Cocoa_IsWindowInFullscreenSpace(window)) {
4124 return false;
4125 }
4126 }
4127#endif
4128
4129#ifdef SDL_PLATFORM_ANDROID
4130 {
4131 extern bool Android_JNI_ShouldMinimizeOnFocusLoss(void);
4132 if (!Android_JNI_ShouldMinimizeOnFocusLoss()) {
4133 return false;
4134 }
4135 }
4136#endif
4137
4138 // Real fullscreen windows should minimize on focus loss so the desktop video mode is restored
4139 hint = SDL_GetHint(SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS);
4140 if (!hint || !*hint || SDL_strcasecmp(hint, "auto") == 0) {
4141 if (window->fullscreen_exclusive && !SDL_ModeSwitchingEmulated(_this)) {
4142 return true;
4143 } else {
4144 return false;
4145 }
4146 }
4147 return SDL_GetHintBoolean(SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS, false);
4148}
4149
4150void SDL_OnWindowFocusLost(SDL_Window *window)
4151{
4152 SDL_UpdateWindowGrab(window);
4153
4154 if (SDL_ShouldMinimizeOnFocusLoss(window)) {
4155 SDL_MinimizeWindow(window);
4156 }
4157}
4158
4159SDL_Window *SDL_GetToplevelForKeyboardFocus(void)
4160{
4161 SDL_Window *focus = SDL_GetKeyboardFocus();
4162
4163 if (focus) {
4164 // Get the toplevel parent window.
4165 while (focus->parent) {
4166 focus = focus->parent;
4167 }
4168 }
4169
4170 return focus;
4171}
4172
4173bool SDL_AddWindowRenderer(SDL_Window *window, SDL_Renderer *renderer)
4174{
4175 SDL_Renderer **renderers = (SDL_Renderer **)SDL_realloc(window->renderers, (window->num_renderers + 1) * sizeof(*renderers));
4176 if (!renderers) {
4177 return false;
4178 }
4179
4180 window->renderers = renderers;
4181 window->renderers[window->num_renderers++] = renderer;
4182 return true;
4183}
4184
4185void SDL_RemoveWindowRenderer(SDL_Window *window, SDL_Renderer *renderer)
4186{
4187 for (int i = 0; i < window->num_renderers; ++i) {
4188 if (window->renderers[i] == renderer) {
4189 if (i < (window->num_renderers - 1)) {
4190 SDL_memmove(&window->renderers[i], &window->renderers[i + 1], (window->num_renderers - i - 1) * sizeof(window->renderers[i]));
4191 }
4192 --window->num_renderers;
4193 break;
4194 }
4195 }
4196}
4197
4198void SDL_DestroyWindow(SDL_Window *window)
4199{
4200 CHECK_WINDOW_MAGIC(window,);
4201
4202 window->is_destroying = true;
4203
4204 // Destroy any child windows of this window
4205 while (window->first_child) {
4206 SDL_DestroyWindow(window->first_child);
4207 }
4208
4209 SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_DESTROYED, 0, 0);
4210
4211 SDL_Renderer *renderer = SDL_GetRenderer(window);
4212 if (renderer) {
4213 SDL_DestroyRendererWithoutFreeing(renderer);
4214 }
4215
4216 // Restore video mode, etc.
4217 SDL_UpdateFullscreenMode(window, SDL_FULLSCREEN_OP_LEAVE, true);
4218 if (!(window->flags & SDL_WINDOW_EXTERNAL)) {
4219 SDL_HideWindow(window);
4220 }
4221
4222 SDL_DestroyProperties(window->text_input_props);
4223 SDL_DestroyProperties(window->props);
4224
4225 /* Clear the modal status, but don't unset the parent just yet, as it
4226 * may be needed later in the destruction process if a backend needs
4227 * to update the input focus.
4228 */
4229 if (_this->SetWindowModal && (window->flags & SDL_WINDOW_MODAL)) {
4230 _this->SetWindowModal(_this, window, false);
4231 }
4232
4233 // Make sure the destroyed window isn't referenced by any display as a fullscreen window.
4234 for (int i = 0; i < _this->num_displays; ++i) {
4235 if (_this->displays[i]->fullscreen_window == window) {
4236 _this->displays[i]->fullscreen_window = NULL;
4237 }
4238 }
4239
4240 // Make sure this window no longer has focus
4241 if (SDL_GetKeyboardFocus() == window) {
4242 SDL_SetKeyboardFocus(NULL);
4243 }
4244 if ((window->flags & SDL_WINDOW_MOUSE_CAPTURE)) {
4245 SDL_UpdateMouseCapture(true);
4246 }
4247 if (SDL_GetMouseFocus() == window) {
4248 SDL_SetMouseFocus(NULL);
4249 }
4250
4251 SDL_DestroyWindowSurface(window);
4252
4253 // Make no context current if this is the current context window
4254 if (window->flags & SDL_WINDOW_OPENGL) {
4255 if (_this->current_glwin == window) {
4256 SDL_GL_MakeCurrent(window, NULL);
4257 }
4258 }
4259
4260 if (_this->DestroyWindow) {
4261 _this->DestroyWindow(_this, window);
4262 }
4263
4264 // Unload the graphics libraries after the window is destroyed, which may clean up EGL surfaces
4265 if (window->flags & SDL_WINDOW_OPENGL) {
4266 SDL_GL_UnloadLibrary();
4267 }
4268 if (window->flags & SDL_WINDOW_VULKAN) {
4269 SDL_Vulkan_UnloadLibrary();
4270 }
4271
4272 if (_this->grabbed_window == window) {
4273 _this->grabbed_window = NULL; // ungrabbing input.
4274 }
4275
4276 if (_this->current_glwin == window) {
4277 _this->current_glwin = NULL;
4278 }
4279
4280 if (_this->wakeup_window == window) {
4281 _this->wakeup_window = NULL;
4282 }
4283
4284 // Now invalidate magic
4285 SDL_SetObjectValid(window, SDL_OBJECT_TYPE_WINDOW, false);
4286
4287 // Free memory associated with the window
4288 SDL_free(window->title);
4289 SDL_DestroySurface(window->icon);
4290
4291 // Unlink the window from its siblings.
4292 SDL_UpdateWindowHierarchy(window, NULL);
4293
4294 // Unlink the window from the global window list
4295 if (window->next) {
4296 window->next->prev = window->prev;
4297 }
4298 if (window->prev) {
4299 window->prev->next = window->next;
4300 } else {
4301 _this->windows = window->next;
4302 }
4303
4304 SDL_free(window->renderers);
4305 SDL_free(window);
4306
4307#ifdef SDL_VIDEO_DRIVER_UIKIT
4308 SDL_UpdateLifecycleObserver();
4309#endif
4310}
4311
4312bool SDL_ScreenSaverEnabled(void)
4313{
4314 if (!_this) {
4315 return true;
4316 }
4317 return !_this->suspend_screensaver;
4318}
4319
4320bool SDL_EnableScreenSaver(void)
4321{
4322 if (!_this) {
4323 return SDL_UninitializedVideo();
4324 }
4325 if (!_this->suspend_screensaver) {
4326 return true;
4327 }
4328 _this->suspend_screensaver = false;
4329 if (_this->SuspendScreenSaver) {
4330 return _this->SuspendScreenSaver(_this);
4331 }
4332
4333 return SDL_Unsupported();
4334}
4335
4336bool SDL_DisableScreenSaver(void)
4337{
4338 if (!_this) {
4339 return SDL_UninitializedVideo();
4340 }
4341 if (_this->suspend_screensaver) {
4342 return true;
4343 }
4344 _this->suspend_screensaver = true;
4345 if (_this->SuspendScreenSaver) {
4346 return _this->SuspendScreenSaver(_this);
4347 }
4348
4349 return SDL_Unsupported();
4350}
4351
4352void SDL_VideoQuit(void)
4353{
4354 int i;
4355
4356 if (!_this) {
4357 return;
4358 }
4359
4360 // Halt event processing before doing anything else
4361#if 0 // This was moved to the end to fix a memory leak
4362 SDL_QuitPen();
4363#endif
4364 SDL_QuitTouch();
4365 SDL_QuitMouse();
4366 SDL_QuitKeyboard();
4367 SDL_QuitSubSystem(SDL_INIT_EVENTS);
4368
4369 SDL_EnableScreenSaver();
4370
4371 // Clean up the system video
4372 while (_this->windows) {
4373 SDL_DestroyWindow(_this->windows);
4374 }
4375 _this->VideoQuit(_this);
4376
4377 for (i = _this->num_displays; i--; ) {
4378 SDL_VideoDisplay *display = _this->displays[i];
4379 SDL_DelVideoDisplay(display->id, false);
4380 }
4381
4382 SDL_assert(_this->num_displays == 0);
4383 SDL_free(_this->displays);
4384 _this->displays = NULL;
4385
4386 SDL_CancelClipboardData(0);
4387
4388 if (_this->primary_selection_text) {
4389 SDL_free(_this->primary_selection_text);
4390 _this->primary_selection_text = NULL;
4391 }
4392 _this->free(_this);
4393 _this = NULL;
4394
4395 // This needs to happen after the video subsystem has removed pen data
4396 SDL_QuitPen();
4397}
4398
4399bool SDL_GL_LoadLibrary(const char *path)
4400{
4401 bool result;
4402
4403 if (!_this) {
4404 return SDL_UninitializedVideo();
4405 }
4406 if (_this->gl_config.driver_loaded) {
4407 if (path && SDL_strcmp(path, _this->gl_config.driver_path) != 0) {
4408 return SDL_SetError("OpenGL library already loaded");
4409 }
4410 result = true;
4411 } else {
4412 if (!_this->GL_LoadLibrary) {
4413 return SDL_DllNotSupported("OpenGL");
4414 }
4415 result = _this->GL_LoadLibrary(_this, path);
4416 }
4417 if (result) {
4418 ++_this->gl_config.driver_loaded;
4419 } else {
4420 if (_this->GL_UnloadLibrary) {
4421 _this->GL_UnloadLibrary(_this);
4422 }
4423 }
4424 return result;
4425}
4426
4427SDL_FunctionPointer SDL_GL_GetProcAddress(const char *proc)
4428{
4429 SDL_FunctionPointer func;
4430
4431 if (!_this) {
4432 SDL_UninitializedVideo();
4433 return NULL;
4434 }
4435 func = NULL;
4436 if (_this->GL_GetProcAddress) {
4437 if (_this->gl_config.driver_loaded) {
4438 func = _this->GL_GetProcAddress(_this, proc);
4439 } else {
4440 SDL_SetError("No GL driver has been loaded");
4441 }
4442 } else {
4443 SDL_SetError("No dynamic GL support in current SDL video driver (%s)", _this->name);
4444 }
4445 return func;
4446}
4447
4448SDL_FunctionPointer SDL_EGL_GetProcAddress(const char *proc)
4449{
4450#ifdef SDL_VIDEO_OPENGL_EGL
4451 SDL_FunctionPointer func;
4452
4453 if (!_this) {
4454 SDL_UninitializedVideo();
4455 return NULL;
4456 }
4457 func = NULL;
4458
4459 if (_this->egl_data) {
4460 func = SDL_EGL_GetProcAddressInternal(_this, proc);
4461 } else {
4462 SDL_SetError("No EGL library has been loaded");
4463 }
4464
4465 return func;
4466#else
4467 SDL_SetError("SDL was not built with EGL support");
4468 return NULL;
4469#endif
4470}
4471
4472void SDL_GL_UnloadLibrary(void)
4473{
4474 if (!_this) {
4475 SDL_UninitializedVideo();
4476 return;
4477 }
4478 if (_this->gl_config.driver_loaded > 0) {
4479 if (--_this->gl_config.driver_loaded > 0) {
4480 return;
4481 }
4482 if (_this->GL_UnloadLibrary) {
4483 _this->GL_UnloadLibrary(_this);
4484 }
4485 }
4486}
4487
4488#if defined(SDL_VIDEO_OPENGL) || defined(SDL_VIDEO_OPENGL_ES) || defined(SDL_VIDEO_OPENGL_ES2)
4489typedef GLenum (APIENTRY* PFNGLGETERRORPROC) (void);
4490typedef void (APIENTRY* PFNGLGETINTEGERVPROC) (GLenum pname, GLint *params);
4491typedef const GLubyte *(APIENTRY* PFNGLGETSTRINGPROC) (GLenum name);
4492#ifndef SDL_VIDEO_OPENGL
4493typedef const GLubyte *(APIENTRY* PFNGLGETSTRINGIPROC) (GLenum name, GLuint index);
4494#endif
4495
4496static SDL_INLINE bool isAtLeastGL3(const char *verstr)
4497{
4498 return verstr && (SDL_atoi(verstr) >= 3);
4499}
4500#endif // SDL_VIDEO_OPENGL || SDL_VIDEO_OPENGL_ES || SDL_VIDEO_OPENGL_ES2
4501
4502bool SDL_GL_ExtensionSupported(const char *extension)
4503{
4504#if defined(SDL_VIDEO_OPENGL) || defined(SDL_VIDEO_OPENGL_ES) || defined(SDL_VIDEO_OPENGL_ES2)
4505 PFNGLGETSTRINGPROC glGetStringFunc;
4506 const char *extensions;
4507 const char *start;
4508 const char *where, *terminator;
4509
4510 // Extension names should not have spaces.
4511 where = SDL_strchr(extension, ' ');
4512 if (where || *extension == '\0') {
4513 return false;
4514 }
4515 // See if there's a hint or environment variable override
4516 start = SDL_GetHint(extension);
4517 if (start && *start == '0') {
4518 return false;
4519 }
4520
4521 // Lookup the available extensions
4522
4523 glGetStringFunc = (PFNGLGETSTRINGPROC)SDL_GL_GetProcAddress("glGetString");
4524 if (!glGetStringFunc) {
4525 return false;
4526 }
4527
4528 if (isAtLeastGL3((const char *)glGetStringFunc(GL_VERSION))) {
4529 PFNGLGETSTRINGIPROC glGetStringiFunc;
4530 PFNGLGETINTEGERVPROC glGetIntegervFunc;
4531 GLint num_exts = 0;
4532 GLint i;
4533
4534 glGetStringiFunc = (PFNGLGETSTRINGIPROC)SDL_GL_GetProcAddress("glGetStringi");
4535 glGetIntegervFunc = (PFNGLGETINTEGERVPROC)SDL_GL_GetProcAddress("glGetIntegerv");
4536 if ((!glGetStringiFunc) || (!glGetIntegervFunc)) {
4537 return false;
4538 }
4539
4540#ifndef GL_NUM_EXTENSIONS
4541#define GL_NUM_EXTENSIONS 0x821D
4542#endif
4543 glGetIntegervFunc(GL_NUM_EXTENSIONS, &num_exts);
4544 for (i = 0; i < num_exts; i++) {
4545 const char *thisext = (const char *)glGetStringiFunc(GL_EXTENSIONS, i);
4546 if (SDL_strcmp(thisext, extension) == 0) {
4547 return true;
4548 }
4549 }
4550
4551 return false;
4552 }
4553
4554 // Try the old way with glGetString(GL_EXTENSIONS) ...
4555
4556 extensions = (const char *)glGetStringFunc(GL_EXTENSIONS);
4557 if (!extensions) {
4558 return false;
4559 }
4560 /*
4561 * It takes a bit of care to be fool-proof about parsing the OpenGL
4562 * extensions string. Don't be fooled by sub-strings, etc.
4563 */
4564
4565 start = extensions;
4566
4567 for (;;) {
4568 where = SDL_strstr(start, extension);
4569 if (!where) {
4570 break;
4571 }
4572
4573 terminator = where + SDL_strlen(extension);
4574 if (where == extensions || *(where - 1) == ' ') {
4575 if (*terminator == ' ' || *terminator == '\0') {
4576 return true;
4577 }
4578 }
4579
4580 start = terminator;
4581 }
4582 return false;
4583#else
4584 return false;
4585#endif
4586}
4587
4588/* Deduce supported ES profile versions from the supported
4589 ARB_ES*_compatibility extensions. There is no direct query.
4590
4591 This is normally only called when the OpenGL driver supports
4592 {GLX,WGL}_EXT_create_context_es2_profile.
4593 */
4594void SDL_GL_DeduceMaxSupportedESProfile(int *major, int *minor)
4595{
4596// THIS REQUIRES AN EXISTING GL CONTEXT THAT HAS BEEN MADE CURRENT.
4597// Please refer to https://bugzilla.libsdl.org/show_bug.cgi?id=3725 for discussion.
4598#if defined(SDL_VIDEO_OPENGL) || defined(SDL_VIDEO_OPENGL_ES) || defined(SDL_VIDEO_OPENGL_ES2)
4599 /* XXX This is fragile; it will break in the event of release of
4600 * new versions of OpenGL ES.
4601 */
4602 if (SDL_GL_ExtensionSupported("GL_ARB_ES3_2_compatibility")) {
4603 *major = 3;
4604 *minor = 2;
4605 } else if (SDL_GL_ExtensionSupported("GL_ARB_ES3_1_compatibility")) {
4606 *major = 3;
4607 *minor = 1;
4608 } else if (SDL_GL_ExtensionSupported("GL_ARB_ES3_compatibility")) {
4609 *major = 3;
4610 *minor = 0;
4611 } else {
4612 *major = 2;
4613 *minor = 0;
4614 }
4615#endif
4616}
4617
4618void SDL_EGL_SetAttributeCallbacks(SDL_EGLAttribArrayCallback platformAttribCallback,
4619 SDL_EGLIntArrayCallback surfaceAttribCallback,
4620 SDL_EGLIntArrayCallback contextAttribCallback,
4621 void *userdata)
4622{
4623 if (!_this) {
4624 return;
4625 }
4626 _this->egl_platformattrib_callback = platformAttribCallback;
4627 _this->egl_surfaceattrib_callback = surfaceAttribCallback;
4628 _this->egl_contextattrib_callback = contextAttribCallback;
4629 _this->egl_attrib_callback_userdata = userdata;
4630}
4631
4632void SDL_GL_ResetAttributes(void)
4633{
4634 if (!_this) {
4635 return;
4636 }
4637
4638 _this->egl_platformattrib_callback = NULL;
4639 _this->egl_surfaceattrib_callback = NULL;
4640 _this->egl_contextattrib_callback = NULL;
4641 _this->egl_attrib_callback_userdata = NULL;
4642
4643 _this->gl_config.red_size = 8;
4644 _this->gl_config.green_size = 8;
4645 _this->gl_config.blue_size = 8;
4646 _this->gl_config.alpha_size = 8;
4647 _this->gl_config.buffer_size = 0;
4648 _this->gl_config.depth_size = 16;
4649 _this->gl_config.stencil_size = 0;
4650 _this->gl_config.double_buffer = 1;
4651 _this->gl_config.accum_red_size = 0;
4652 _this->gl_config.accum_green_size = 0;
4653 _this->gl_config.accum_blue_size = 0;
4654 _this->gl_config.accum_alpha_size = 0;
4655 _this->gl_config.stereo = 0;
4656 _this->gl_config.multisamplebuffers = 0;
4657 _this->gl_config.multisamplesamples = 0;
4658 _this->gl_config.floatbuffers = 0;
4659 _this->gl_config.retained_backing = 1;
4660 _this->gl_config.accelerated = -1; // accelerated or not, both are fine
4661
4662#ifdef SDL_VIDEO_OPENGL
4663 _this->gl_config.major_version = 2;
4664 _this->gl_config.minor_version = 1;
4665 _this->gl_config.profile_mask = 0;
4666#elif defined(SDL_VIDEO_OPENGL_ES2)
4667 _this->gl_config.major_version = 2;
4668 _this->gl_config.minor_version = 0;
4669 _this->gl_config.profile_mask = SDL_GL_CONTEXT_PROFILE_ES;
4670#elif defined(SDL_VIDEO_OPENGL_ES)
4671 _this->gl_config.major_version = 1;
4672 _this->gl_config.minor_version = 1;
4673 _this->gl_config.profile_mask = SDL_GL_CONTEXT_PROFILE_ES;
4674#endif
4675
4676 if (_this->GL_DefaultProfileConfig) {
4677 _this->GL_DefaultProfileConfig(_this, &_this->gl_config.profile_mask,
4678 &_this->gl_config.major_version,
4679 &_this->gl_config.minor_version);
4680 }
4681
4682 _this->gl_config.flags = 0;
4683 _this->gl_config.framebuffer_srgb_capable = 0;
4684 _this->gl_config.no_error = 0;
4685 _this->gl_config.release_behavior = SDL_GL_CONTEXT_RELEASE_BEHAVIOR_FLUSH;
4686 _this->gl_config.reset_notification = SDL_GL_CONTEXT_RESET_NO_NOTIFICATION;
4687
4688 _this->gl_config.share_with_current_context = 0;
4689
4690 _this->gl_config.egl_platform = 0;
4691}
4692
4693bool SDL_GL_SetAttribute(SDL_GLAttr attr, int value)
4694{
4695#if defined(SDL_VIDEO_OPENGL) || defined(SDL_VIDEO_OPENGL_ES) || defined(SDL_VIDEO_OPENGL_ES2)
4696 bool result;
4697
4698 if (!_this) {
4699 return SDL_UninitializedVideo();
4700 }
4701 result = true;
4702 switch (attr) {
4703 case SDL_GL_RED_SIZE:
4704 _this->gl_config.red_size = value;
4705 break;
4706 case SDL_GL_GREEN_SIZE:
4707 _this->gl_config.green_size = value;
4708 break;
4709 case SDL_GL_BLUE_SIZE:
4710 _this->gl_config.blue_size = value;
4711 break;
4712 case SDL_GL_ALPHA_SIZE:
4713 _this->gl_config.alpha_size = value;
4714 break;
4715 case SDL_GL_DOUBLEBUFFER:
4716 _this->gl_config.double_buffer = value;
4717 break;
4718 case SDL_GL_BUFFER_SIZE:
4719 _this->gl_config.buffer_size = value;
4720 break;
4721 case SDL_GL_DEPTH_SIZE:
4722 _this->gl_config.depth_size = value;
4723 break;
4724 case SDL_GL_STENCIL_SIZE:
4725 _this->gl_config.stencil_size = value;
4726 break;
4727 case SDL_GL_ACCUM_RED_SIZE:
4728 _this->gl_config.accum_red_size = value;
4729 break;
4730 case SDL_GL_ACCUM_GREEN_SIZE:
4731 _this->gl_config.accum_green_size = value;
4732 break;
4733 case SDL_GL_ACCUM_BLUE_SIZE:
4734 _this->gl_config.accum_blue_size = value;
4735 break;
4736 case SDL_GL_ACCUM_ALPHA_SIZE:
4737 _this->gl_config.accum_alpha_size = value;
4738 break;
4739 case SDL_GL_STEREO:
4740 _this->gl_config.stereo = value;
4741 break;
4742 case SDL_GL_MULTISAMPLEBUFFERS:
4743 _this->gl_config.multisamplebuffers = value;
4744 break;
4745 case SDL_GL_MULTISAMPLESAMPLES:
4746 _this->gl_config.multisamplesamples = value;
4747 break;
4748 case SDL_GL_FLOATBUFFERS:
4749 _this->gl_config.floatbuffers = value;
4750 break;
4751 case SDL_GL_ACCELERATED_VISUAL:
4752 _this->gl_config.accelerated = value;
4753 break;
4754 case SDL_GL_RETAINED_BACKING:
4755 _this->gl_config.retained_backing = value;
4756 break;
4757 case SDL_GL_CONTEXT_MAJOR_VERSION:
4758 _this->gl_config.major_version = value;
4759 break;
4760 case SDL_GL_CONTEXT_MINOR_VERSION:
4761 _this->gl_config.minor_version = value;
4762 break;
4763 case SDL_GL_CONTEXT_FLAGS:
4764 if (value & ~(SDL_GL_CONTEXT_DEBUG_FLAG |
4765 SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG |
4766 SDL_GL_CONTEXT_ROBUST_ACCESS_FLAG |
4767 SDL_GL_CONTEXT_RESET_ISOLATION_FLAG)) {
4768 result = SDL_SetError("Unknown OpenGL context flag %d", value);
4769 break;
4770 }
4771 _this->gl_config.flags = value;
4772 break;
4773 case SDL_GL_CONTEXT_PROFILE_MASK:
4774 if (value != 0 &&
4775 value != SDL_GL_CONTEXT_PROFILE_CORE &&
4776 value != SDL_GL_CONTEXT_PROFILE_COMPATIBILITY &&
4777 value != SDL_GL_CONTEXT_PROFILE_ES) {
4778 result = SDL_SetError("Unknown OpenGL context profile %d", value);
4779 break;
4780 }
4781 _this->gl_config.profile_mask = value;
4782 break;
4783 case SDL_GL_SHARE_WITH_CURRENT_CONTEXT:
4784 _this->gl_config.share_with_current_context = value;
4785 break;
4786 case SDL_GL_FRAMEBUFFER_SRGB_CAPABLE:
4787 _this->gl_config.framebuffer_srgb_capable = value;
4788 break;
4789 case SDL_GL_CONTEXT_RELEASE_BEHAVIOR:
4790 _this->gl_config.release_behavior = value;
4791 break;
4792 case SDL_GL_CONTEXT_RESET_NOTIFICATION:
4793 _this->gl_config.reset_notification = value;
4794 break;
4795 case SDL_GL_CONTEXT_NO_ERROR:
4796 _this->gl_config.no_error = value;
4797 break;
4798 case SDL_GL_EGL_PLATFORM:
4799 _this->gl_config.egl_platform = value;
4800 break;
4801 default:
4802 result = SDL_SetError("Unknown OpenGL attribute");
4803 break;
4804 }
4805 return result;
4806#else
4807 return SDL_Unsupported();
4808#endif // SDL_VIDEO_OPENGL
4809}
4810
4811bool SDL_GL_GetAttribute(SDL_GLAttr attr, int *value)
4812{
4813#if defined(SDL_VIDEO_OPENGL) || defined(SDL_VIDEO_OPENGL_ES) || defined(SDL_VIDEO_OPENGL_ES2)
4814 PFNGLGETERRORPROC glGetErrorFunc;
4815 GLenum attrib = 0;
4816 GLenum error = 0;
4817
4818 /*
4819 * Some queries in Core Profile desktop OpenGL 3+ contexts require
4820 * glGetFramebufferAttachmentParameteriv instead of glGetIntegerv. Note that
4821 * the enums we use for the former function don't exist in OpenGL ES 2, and
4822 * the function itself doesn't exist prior to OpenGL 3 and OpenGL ES 2.
4823 */
4824#ifdef SDL_VIDEO_OPENGL
4825 PFNGLGETSTRINGPROC glGetStringFunc;
4826 PFNGLGETFRAMEBUFFERATTACHMENTPARAMETERIVPROC glGetFramebufferAttachmentParameterivFunc;
4827 GLenum attachment = GL_BACK_LEFT;
4828 GLenum attachmentattrib = 0;
4829#endif
4830
4831 if (!value) {
4832 return SDL_InvalidParamError("value");
4833 }
4834
4835 // Clear value in any case
4836 *value = 0;
4837
4838 if (!_this) {
4839 return SDL_UninitializedVideo();
4840 }
4841
4842 switch (attr) {
4843 case SDL_GL_RED_SIZE:
4844#ifdef SDL_VIDEO_OPENGL
4845 attachmentattrib = GL_FRAMEBUFFER_ATTACHMENT_RED_SIZE;
4846#endif
4847 attrib = GL_RED_BITS;
4848 break;
4849 case SDL_GL_BLUE_SIZE:
4850#ifdef SDL_VIDEO_OPENGL
4851 attachmentattrib = GL_FRAMEBUFFER_ATTACHMENT_BLUE_SIZE;
4852#endif
4853 attrib = GL_BLUE_BITS;
4854 break;
4855 case SDL_GL_GREEN_SIZE:
4856#ifdef SDL_VIDEO_OPENGL
4857 attachmentattrib = GL_FRAMEBUFFER_ATTACHMENT_GREEN_SIZE;
4858#endif
4859 attrib = GL_GREEN_BITS;
4860 break;
4861 case SDL_GL_ALPHA_SIZE:
4862#ifdef SDL_VIDEO_OPENGL
4863 attachmentattrib = GL_FRAMEBUFFER_ATTACHMENT_ALPHA_SIZE;
4864#endif
4865 attrib = GL_ALPHA_BITS;
4866 break;
4867 case SDL_GL_DOUBLEBUFFER:
4868#ifdef SDL_VIDEO_OPENGL
4869 attrib = GL_DOUBLEBUFFER;
4870 break;
4871#else
4872 // OpenGL ES 1.0 and above specifications have EGL_SINGLE_BUFFER
4873 // parameter which switches double buffer to single buffer. OpenGL ES
4874 // SDL driver must set proper value after initialization
4875 *value = _this->gl_config.double_buffer;
4876 return true;
4877#endif
4878 case SDL_GL_DEPTH_SIZE:
4879#ifdef SDL_VIDEO_OPENGL
4880 attachment = GL_DEPTH;
4881 attachmentattrib = GL_FRAMEBUFFER_ATTACHMENT_DEPTH_SIZE;
4882#endif
4883 attrib = GL_DEPTH_BITS;
4884 break;
4885 case SDL_GL_STENCIL_SIZE:
4886#ifdef SDL_VIDEO_OPENGL
4887 attachment = GL_STENCIL;
4888 attachmentattrib = GL_FRAMEBUFFER_ATTACHMENT_STENCIL_SIZE;
4889#endif
4890 attrib = GL_STENCIL_BITS;
4891 break;
4892#ifdef SDL_VIDEO_OPENGL
4893 case SDL_GL_ACCUM_RED_SIZE:
4894 attrib = GL_ACCUM_RED_BITS;
4895 break;
4896 case SDL_GL_ACCUM_GREEN_SIZE:
4897 attrib = GL_ACCUM_GREEN_BITS;
4898 break;
4899 case SDL_GL_ACCUM_BLUE_SIZE:
4900 attrib = GL_ACCUM_BLUE_BITS;
4901 break;
4902 case SDL_GL_ACCUM_ALPHA_SIZE:
4903 attrib = GL_ACCUM_ALPHA_BITS;
4904 break;
4905 case SDL_GL_STEREO:
4906 attrib = GL_STEREO;
4907 break;
4908#else
4909 case SDL_GL_ACCUM_RED_SIZE:
4910 case SDL_GL_ACCUM_GREEN_SIZE:
4911 case SDL_GL_ACCUM_BLUE_SIZE:
4912 case SDL_GL_ACCUM_ALPHA_SIZE:
4913 case SDL_GL_STEREO:
4914 // none of these are supported in OpenGL ES
4915 *value = 0;
4916 return true;
4917#endif
4918 case SDL_GL_MULTISAMPLEBUFFERS:
4919 attrib = GL_SAMPLE_BUFFERS;
4920 break;
4921 case SDL_GL_MULTISAMPLESAMPLES:
4922 attrib = GL_SAMPLES;
4923 break;
4924 case SDL_GL_CONTEXT_RELEASE_BEHAVIOR:
4925 attrib = GL_CONTEXT_RELEASE_BEHAVIOR;
4926 break;
4927 case SDL_GL_BUFFER_SIZE:
4928 {
4929 int rsize = 0, gsize = 0, bsize = 0, asize = 0;
4930
4931 // There doesn't seem to be a single flag in OpenGL for this!
4932 if (!SDL_GL_GetAttribute(SDL_GL_RED_SIZE, &rsize)) {
4933 return false;
4934 }
4935 if (!SDL_GL_GetAttribute(SDL_GL_GREEN_SIZE, &gsize)) {
4936 return false;
4937 }
4938 if (!SDL_GL_GetAttribute(SDL_GL_BLUE_SIZE, &bsize)) {
4939 return false;
4940 }
4941 if (!SDL_GL_GetAttribute(SDL_GL_ALPHA_SIZE, &asize)) {
4942 return false;
4943 }
4944
4945 *value = rsize + gsize + bsize + asize;
4946 return true;
4947 }
4948 case SDL_GL_ACCELERATED_VISUAL:
4949 {
4950 // FIXME: How do we get this information?
4951 *value = (_this->gl_config.accelerated != 0);
4952 return true;
4953 }
4954 case SDL_GL_RETAINED_BACKING:
4955 {
4956 *value = _this->gl_config.retained_backing;
4957 return true;
4958 }
4959 case SDL_GL_CONTEXT_MAJOR_VERSION:
4960 {
4961 *value = _this->gl_config.major_version;
4962 return true;
4963 }
4964 case SDL_GL_CONTEXT_MINOR_VERSION:
4965 {
4966 *value = _this->gl_config.minor_version;
4967 return true;
4968 }
4969 case SDL_GL_CONTEXT_FLAGS:
4970 {
4971 *value = _this->gl_config.flags;
4972 return true;
4973 }
4974 case SDL_GL_CONTEXT_PROFILE_MASK:
4975 {
4976 *value = _this->gl_config.profile_mask;
4977 return true;
4978 }
4979 case SDL_GL_SHARE_WITH_CURRENT_CONTEXT:
4980 {
4981 *value = _this->gl_config.share_with_current_context;
4982 return true;
4983 }
4984 case SDL_GL_FRAMEBUFFER_SRGB_CAPABLE:
4985 {
4986 *value = _this->gl_config.framebuffer_srgb_capable;
4987 return true;
4988 }
4989 case SDL_GL_CONTEXT_NO_ERROR:
4990 {
4991 *value = _this->gl_config.no_error;
4992 return true;
4993 }
4994 case SDL_GL_EGL_PLATFORM:
4995 {
4996 *value = _this->gl_config.egl_platform;
4997 return true;
4998 }
4999 default:
5000 return SDL_SetError("Unknown OpenGL attribute");
5001 }
5002
5003#ifdef SDL_VIDEO_OPENGL
5004 glGetStringFunc = (PFNGLGETSTRINGPROC)SDL_GL_GetProcAddress("glGetString");
5005 if (!glGetStringFunc) {
5006 return false;
5007 }
5008
5009 if (attachmentattrib && isAtLeastGL3((const char *)glGetStringFunc(GL_VERSION))) {
5010 // glGetFramebufferAttachmentParameteriv needs to operate on the window framebuffer for this, so bind FBO 0 if necessary.
5011 GLint current_fbo = 0;
5012 PFNGLGETINTEGERVPROC glGetIntegervFunc = (PFNGLGETINTEGERVPROC) SDL_GL_GetProcAddress("glGetIntegerv");
5013 PFNGLBINDFRAMEBUFFERPROC glBindFramebufferFunc = (PFNGLBINDFRAMEBUFFERPROC)SDL_GL_GetProcAddress("glBindFramebuffer");
5014 if (glGetIntegervFunc && glBindFramebufferFunc) {
5015 glGetIntegervFunc(GL_DRAW_FRAMEBUFFER_BINDING, &current_fbo);
5016 }
5017
5018 glGetFramebufferAttachmentParameterivFunc = (PFNGLGETFRAMEBUFFERATTACHMENTPARAMETERIVPROC)SDL_GL_GetProcAddress("glGetFramebufferAttachmentParameteriv");
5019 if (glGetFramebufferAttachmentParameterivFunc) {
5020 if (glBindFramebufferFunc && (current_fbo != 0)) {
5021 glBindFramebufferFunc(GL_DRAW_FRAMEBUFFER, 0);
5022 }
5023 // glGetFramebufferAttachmentParameterivFunc may cause GL_INVALID_OPERATION when querying depth/stencil size if the
5024 // bits is 0. From the GL docs:
5025 // If the value of GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE is GL_NONE, then either no framebuffer is bound to target;
5026 // or a default framebuffer is queried, attachment is GL_DEPTH or GL_STENCIL, and the number of depth or stencil bits,
5027 // respectively, is zero. In this case querying pname GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME will return zero, and all
5028 // other queries will generate an error.
5029 GLint fbo_type = GL_FRAMEBUFFER_DEFAULT;
5030 if (attachment == GL_DEPTH || attachment == GL_STENCIL) {
5031 glGetFramebufferAttachmentParameterivFunc(GL_FRAMEBUFFER, attachment, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, &fbo_type);
5032 }
5033 if (fbo_type != GL_NONE) {
5034 glGetFramebufferAttachmentParameterivFunc(GL_FRAMEBUFFER, attachment, attachmentattrib, (GLint *)value);
5035 }
5036 else {
5037 *value = 0;
5038 }
5039 if (glBindFramebufferFunc && (current_fbo != 0)) {
5040 glBindFramebufferFunc(GL_DRAW_FRAMEBUFFER, current_fbo);
5041 }
5042 } else {
5043 return false;
5044 }
5045 } else
5046#endif
5047 {
5048 PFNGLGETINTEGERVPROC glGetIntegervFunc = (PFNGLGETINTEGERVPROC)SDL_GL_GetProcAddress("glGetIntegerv");
5049 if (glGetIntegervFunc) {
5050 glGetIntegervFunc(attrib, (GLint *)value);
5051 } else {
5052 return false;
5053 }
5054 }
5055
5056 glGetErrorFunc = (PFNGLGETERRORPROC)SDL_GL_GetProcAddress("glGetError");
5057 if (!glGetErrorFunc) {
5058 return false;
5059 }
5060
5061 error = glGetErrorFunc();
5062 if (error != GL_NO_ERROR) {
5063 if (error == GL_INVALID_ENUM) {
5064 return SDL_SetError("OpenGL error: GL_INVALID_ENUM");
5065 } else if (error == GL_INVALID_VALUE) {
5066 return SDL_SetError("OpenGL error: GL_INVALID_VALUE");
5067 }
5068 return SDL_SetError("OpenGL error: %08X", error);
5069 }
5070
5071 // convert GL_CONTEXT_RELEASE_BEHAVIOR values back to SDL_GL_CONTEXT_RELEASE_BEHAVIOR values
5072 if (attr == SDL_GL_CONTEXT_RELEASE_BEHAVIOR) {
5073 *value = (*value == GL_CONTEXT_RELEASE_BEHAVIOR_FLUSH) ? SDL_GL_CONTEXT_RELEASE_BEHAVIOR_FLUSH : SDL_GL_CONTEXT_RELEASE_BEHAVIOR_NONE;
5074 }
5075
5076 return true;
5077#else
5078 return SDL_Unsupported();
5079#endif // SDL_VIDEO_OPENGL
5080}
5081
5082#define NOT_AN_OPENGL_WINDOW "The specified window isn't an OpenGL window"
5083
5084SDL_GLContext SDL_GL_CreateContext(SDL_Window *window)
5085{
5086 SDL_GLContext ctx = NULL;
5087 CHECK_WINDOW_MAGIC(window, NULL);
5088
5089 if (!(window->flags & SDL_WINDOW_OPENGL)) {
5090 SDL_SetError(NOT_AN_OPENGL_WINDOW);
5091 return NULL;
5092 }
5093
5094 ctx = _this->GL_CreateContext(_this, window);
5095
5096 // Creating a context is assumed to make it current in the SDL driver.
5097 if (ctx) {
5098 _this->current_glwin = window;
5099 _this->current_glctx = ctx;
5100 SDL_SetTLS(&_this->current_glwin_tls, window, NULL);
5101 SDL_SetTLS(&_this->current_glctx_tls, ctx, NULL);
5102 }
5103 return ctx;
5104}
5105
5106bool SDL_GL_MakeCurrent(SDL_Window *window, SDL_GLContext context)
5107{
5108 bool result;
5109
5110 if (!_this) {
5111 return SDL_UninitializedVideo();
5112 }
5113
5114 if (window == SDL_GL_GetCurrentWindow() &&
5115 context == SDL_GL_GetCurrentContext()) {
5116 // We're already current.
5117 return true;
5118 }
5119
5120 if (!context) {
5121 window = NULL;
5122 } else if (window) {
5123 CHECK_WINDOW_MAGIC(window, false);
5124
5125 if (!(window->flags & SDL_WINDOW_OPENGL)) {
5126 return SDL_SetError(NOT_AN_OPENGL_WINDOW);
5127 }
5128 } else if (!_this->gl_allow_no_surface) {
5129 return SDL_SetError("Use of OpenGL without a window is not supported on this platform");
5130 }
5131
5132 result = _this->GL_MakeCurrent(_this, window, context);
5133 if (result) {
5134 _this->current_glwin = window;
5135 _this->current_glctx = context;
5136 SDL_SetTLS(&_this->current_glwin_tls, window, NULL);
5137 SDL_SetTLS(&_this->current_glctx_tls, context, NULL);
5138 }
5139 return result;
5140}
5141
5142SDL_Window *SDL_GL_GetCurrentWindow(void)
5143{
5144 if (!_this) {
5145 SDL_UninitializedVideo();
5146 return NULL;
5147 }
5148 return (SDL_Window *)SDL_GetTLS(&_this->current_glwin_tls);
5149}
5150
5151SDL_GLContext SDL_GL_GetCurrentContext(void)
5152{
5153 if (!_this) {
5154 SDL_UninitializedVideo();
5155 return NULL;
5156 }
5157 return (SDL_GLContext)SDL_GetTLS(&_this->current_glctx_tls);
5158}
5159
5160SDL_EGLDisplay SDL_EGL_GetCurrentDisplay(void)
5161{
5162#ifdef SDL_VIDEO_OPENGL_EGL
5163 if (!_this) {
5164 SDL_UninitializedVideo();
5165 return EGL_NO_DISPLAY;
5166 }
5167 if (!_this->egl_data) {
5168 SDL_SetError("There is no current EGL display");
5169 return EGL_NO_DISPLAY;
5170 }
5171 return _this->egl_data->egl_display;
5172#else
5173 SDL_SetError("SDL was not built with EGL support");
5174 return NULL;
5175#endif
5176}
5177
5178SDL_EGLConfig SDL_EGL_GetCurrentConfig(void)
5179{
5180#ifdef SDL_VIDEO_OPENGL_EGL
5181 if (!_this) {
5182 SDL_UninitializedVideo();
5183 return NULL;
5184 }
5185 if (!_this->egl_data) {
5186 SDL_SetError("There is no current EGL display");
5187 return NULL;
5188 }
5189 return _this->egl_data->egl_config;
5190#else
5191 SDL_SetError("SDL was not built with EGL support");
5192 return NULL;
5193#endif
5194}
5195
5196SDL_EGLConfig SDL_EGL_GetWindowSurface(SDL_Window *window)
5197{
5198#ifdef SDL_VIDEO_OPENGL_EGL
5199 if (!_this) {
5200 SDL_UninitializedVideo();
5201 return NULL;
5202 }
5203 if (!_this->egl_data) {
5204 SDL_SetError("There is no current EGL display");
5205 return NULL;
5206 }
5207 if (_this->GL_GetEGLSurface) {
5208 return _this->GL_GetEGLSurface(_this, window);
5209 }
5210 return NULL;
5211#else
5212 SDL_SetError("SDL was not built with EGL support");
5213 return NULL;
5214#endif
5215}
5216
5217bool SDL_GL_SetSwapInterval(int interval)
5218{
5219 if (!_this) {
5220 return SDL_UninitializedVideo();
5221 } else if (SDL_GL_GetCurrentContext() == NULL) {
5222 return SDL_SetError("No OpenGL context has been made current");
5223 } else if (_this->GL_SetSwapInterval) {
5224 return _this->GL_SetSwapInterval(_this, interval);
5225 } else {
5226 return SDL_SetError("Setting the swap interval is not supported");
5227 }
5228}
5229
5230bool SDL_GL_GetSwapInterval(int *interval)
5231{
5232 if (!interval) {
5233 return SDL_InvalidParamError("interval");
5234 }
5235
5236 *interval = 0;
5237
5238 if (!_this) {
5239 return SDL_SetError("no video driver");
5240 } else if (SDL_GL_GetCurrentContext() == NULL) {
5241 return SDL_SetError("no current context");
5242 } else if (_this->GL_GetSwapInterval) {
5243 return _this->GL_GetSwapInterval(_this, interval);
5244 } else {
5245 return SDL_SetError("not implemented");
5246 }
5247}
5248
5249bool SDL_GL_SwapWindow(SDL_Window *window)
5250{
5251 CHECK_WINDOW_MAGIC(window, false);
5252
5253 if (!(window->flags & SDL_WINDOW_OPENGL)) {
5254 return SDL_SetError(NOT_AN_OPENGL_WINDOW);
5255 }
5256
5257 if (SDL_GL_GetCurrentWindow() != window) {
5258 return SDL_SetError("The specified window has not been made current");
5259 }
5260
5261 return _this->GL_SwapWindow(_this, window);
5262}
5263
5264bool SDL_GL_DestroyContext(SDL_GLContext context)
5265{
5266 if (!_this) {
5267 return SDL_UninitializedVideo(); \
5268 }
5269 if (!context) {
5270 return SDL_InvalidParamError("context");
5271 }
5272
5273 if (SDL_GL_GetCurrentContext() == context) {
5274 SDL_GL_MakeCurrent(NULL, NULL);
5275 }
5276
5277 return _this->GL_DestroyContext(_this, context);
5278}
5279
5280#if 0 // FIXME
5281/*
5282 * Utility function used by SDL_WM_SetIcon(); flags & 1 for color key, flags
5283 * & 2 for alpha channel.
5284 */
5285static void CreateMaskFromColorKeyOrAlpha(SDL_Surface *icon, Uint8 *mask, int flags)
5286{
5287 int x, y;
5288 Uint32 colorkey;
5289#define SET_MASKBIT(icon, x, y, mask) \
5290 mask[(y * ((icon->w + 7) / 8)) + (x / 8)] &= ~(0x01 << (7 - (x % 8)))
5291
5292 colorkey = icon->format->colorkey;
5293 switch (SDL_BYTESPERPIXEL(icon->format)) {
5294 case 1:
5295 {
5296 Uint8 *pixels;
5297 for (y = 0; y < icon->h; ++y) {
5298 pixels = (Uint8 *) icon->pixels + y * icon->pitch;
5299 for (x = 0; x < icon->w; ++x) {
5300 if (*pixels++ == colorkey) {
5301 SET_MASKBIT(icon, x, y, mask);
5302 }
5303 }
5304 }
5305 }
5306 break;
5307
5308 case 2:
5309 {
5310 Uint16 *pixels;
5311 for (y = 0; y < icon->h; ++y) {
5312 pixels = (Uint16 *) icon->pixels + y * icon->pitch / 2;
5313 for (x = 0; x < icon->w; ++x) {
5314 if ((flags & 1) && *pixels == colorkey) {
5315 SET_MASKBIT(icon, x, y, mask);
5316 } else if ((flags & 2)
5317 && (*pixels & icon->format->Amask) == 0) {
5318 SET_MASKBIT(icon, x, y, mask);
5319 }
5320 pixels++;
5321 }
5322 }
5323 }
5324 break;
5325
5326 case 4:
5327 {
5328 Uint32 *pixels;
5329 for (y = 0; y < icon->h; ++y) {
5330 pixels = (Uint32 *) icon->pixels + y * icon->pitch / 4;
5331 for (x = 0; x < icon->w; ++x) {
5332 if ((flags & 1) && *pixels == colorkey) {
5333 SET_MASKBIT(icon, x, y, mask);
5334 } else if ((flags & 2)
5335 && (*pixels & icon->format->Amask) == 0) {
5336 SET_MASKBIT(icon, x, y, mask);
5337 }
5338 pixels++;
5339 }
5340 }
5341 }
5342 break;
5343 }
5344}
5345
5346/*
5347 * Sets the window manager icon for the display window.
5348 */
5349void SDL_WM_SetIcon(SDL_Surface *icon, Uint8 *mask)
5350{
5351 if (icon && _this->SetIcon) {
5352 // Generate a mask if necessary, and create the icon!
5353 if (mask == NULL) {
5354 int mask_len = icon->h * (icon->w + 7) / 8;
5355 int flags = 0;
5356 mask = (Uint8 *) SDL_malloc(mask_len);
5357 if (mask == NULL) {
5358 return;
5359 }
5360 SDL_memset(mask, ~0, mask_len);
5361 if (icon->flags & SDL_SRCCOLORKEY)
5362 flags |= 1;
5363 if (icon->flags & SDL_SRCALPHA)
5364 flags |= 2;
5365 if (flags) {
5366 CreateMaskFromColorKeyOrAlpha(icon, mask, flags);
5367 }
5368 _this->SetIcon(_this, icon, mask);
5369 SDL_free(mask);
5370 } else {
5371 _this->SetIcon(_this, icon, mask);
5372 }
5373 }
5374}
5375#endif
5376
5377SDL_TextInputType SDL_GetTextInputType(SDL_PropertiesID props)
5378{
5379 return (SDL_TextInputType)SDL_GetNumberProperty(props, SDL_PROP_TEXTINPUT_TYPE_NUMBER, SDL_TEXTINPUT_TYPE_TEXT);
5380}
5381
5382SDL_Capitalization SDL_GetTextInputCapitalization(SDL_PropertiesID props)
5383{
5384 if (SDL_HasProperty(props, SDL_PROP_TEXTINPUT_CAPITALIZATION_NUMBER)) {
5385 return (SDL_Capitalization)SDL_GetNumberProperty(props, SDL_PROP_TEXTINPUT_CAPITALIZATION_NUMBER, SDL_CAPITALIZE_NONE);
5386 }
5387
5388 switch (SDL_GetTextInputType(props)) {
5389 case SDL_TEXTINPUT_TYPE_TEXT:
5390 return SDL_CAPITALIZE_SENTENCES;
5391 case SDL_TEXTINPUT_TYPE_TEXT_NAME:
5392 return SDL_CAPITALIZE_WORDS;
5393 default:
5394 return SDL_CAPITALIZE_NONE;
5395 }
5396}
5397
5398bool SDL_GetTextInputAutocorrect(SDL_PropertiesID props)
5399{
5400 return SDL_GetBooleanProperty(props, SDL_PROP_TEXTINPUT_AUTOCORRECT_BOOLEAN, true);
5401}
5402
5403bool SDL_GetTextInputMultiline(SDL_PropertiesID props)
5404{
5405 if (SDL_HasProperty(props, SDL_PROP_TEXTINPUT_MULTILINE_BOOLEAN)) {
5406 return SDL_GetBooleanProperty(props, SDL_PROP_TEXTINPUT_MULTILINE_BOOLEAN, false);
5407 }
5408
5409 if (SDL_GetHintBoolean(SDL_HINT_RETURN_KEY_HIDES_IME, false)) {
5410 return false;
5411 } else {
5412 return true;
5413 }
5414}
5415
5416static bool AutoShowingScreenKeyboard(void)
5417{
5418 const char *hint = SDL_GetHint(SDL_HINT_ENABLE_SCREEN_KEYBOARD);
5419 if (((!hint || SDL_strcasecmp(hint, "auto") == 0) && !SDL_HasKeyboard()) ||
5420 SDL_GetStringBoolean(hint, false)) {
5421 return true;
5422 } else {
5423 return false;
5424 }
5425}
5426
5427bool SDL_StartTextInput(SDL_Window *window)
5428{
5429 return SDL_StartTextInputWithProperties(window, 0);
5430}
5431
5432bool SDL_StartTextInputWithProperties(SDL_Window *window, SDL_PropertiesID props)
5433{
5434 CHECK_WINDOW_MAGIC(window, false);
5435
5436 if (window->text_input_props) {
5437 SDL_DestroyProperties(window->text_input_props);
5438 window->text_input_props = 0;
5439 }
5440
5441 if (props) {
5442 window->text_input_props = SDL_CreateProperties();
5443 if (!window->text_input_props) {
5444 return false;
5445 }
5446 if (!SDL_CopyProperties(props, window->text_input_props)) {
5447 return false;
5448 }
5449 }
5450
5451 if (_this->SetTextInputProperties) {
5452 _this->SetTextInputProperties(_this, window, props);
5453 }
5454
5455 // Show the on-screen keyboard, if desired
5456 if (AutoShowingScreenKeyboard() && !SDL_ScreenKeyboardShown(window)) {
5457 if (_this->ShowScreenKeyboard) {
5458 _this->ShowScreenKeyboard(_this, window, props);
5459 }
5460 }
5461
5462 if (!window->text_input_active) {
5463 // Finally start the text input system
5464 if (_this->StartTextInput) {
5465 if (!_this->StartTextInput(_this, window, props)) {
5466 return false;
5467 }
5468 }
5469 window->text_input_active = true;
5470 }
5471 return true;
5472}
5473
5474bool SDL_TextInputActive(SDL_Window *window)
5475{
5476 CHECK_WINDOW_MAGIC(window, false);
5477
5478 return window->text_input_active;
5479}
5480
5481bool SDL_StopTextInput(SDL_Window *window)
5482{
5483 CHECK_WINDOW_MAGIC(window, false);
5484
5485 if (window->text_input_active) {
5486 // Stop the text input system
5487 if (_this->StopTextInput) {
5488 _this->StopTextInput(_this, window);
5489 }
5490 window->text_input_active = false;
5491 }
5492
5493 // Hide the on-screen keyboard, if desired
5494 if (AutoShowingScreenKeyboard() && SDL_ScreenKeyboardShown(window)) {
5495 if (_this->HideScreenKeyboard) {
5496 _this->HideScreenKeyboard(_this, window);
5497 }
5498 }
5499 return true;
5500}
5501
5502bool SDL_SetTextInputArea(SDL_Window *window, const SDL_Rect *rect, int cursor)
5503{
5504 CHECK_WINDOW_MAGIC(window, false);
5505
5506 if (rect) {
5507 SDL_copyp(&window->text_input_rect, rect);
5508 window->text_input_cursor = cursor;
5509 } else {
5510 SDL_zero(window->text_input_rect);
5511 window->text_input_cursor = 0;
5512 }
5513
5514 if (_this && _this->UpdateTextInputArea) {
5515 if (!_this->UpdateTextInputArea(_this, window)) {
5516 return false;
5517 }
5518 }
5519 return true;
5520}
5521
5522bool SDL_GetTextInputArea(SDL_Window *window, SDL_Rect *rect, int *cursor)
5523{
5524 CHECK_WINDOW_MAGIC(window, false);
5525
5526 if (rect) {
5527 SDL_copyp(rect, &window->text_input_rect);
5528 }
5529 if (cursor) {
5530 *cursor = window->text_input_cursor;
5531 }
5532 return true;
5533}
5534
5535bool SDL_ClearComposition(SDL_Window *window)
5536{
5537 CHECK_WINDOW_MAGIC(window, false);
5538
5539 if (_this->ClearComposition) {
5540 return _this->ClearComposition(_this, window);
5541 }
5542 return true;
5543}
5544
5545bool SDL_HasScreenKeyboardSupport(void)
5546{
5547 if (_this && _this->HasScreenKeyboardSupport) {
5548 return _this->HasScreenKeyboardSupport(_this);
5549 }
5550 return false;
5551}
5552
5553bool SDL_ScreenKeyboardShown(SDL_Window *window)
5554{
5555 CHECK_WINDOW_MAGIC(window, false);
5556
5557 if (_this->IsScreenKeyboardShown) {
5558 return _this->IsScreenKeyboardShown(_this, window);
5559 }
5560 return false;
5561}
5562
5563int SDL_GetMessageBoxCount(void)
5564{
5565 return SDL_GetAtomicInt(&SDL_messagebox_count);
5566}
5567
5568#ifdef SDL_VIDEO_DRIVER_ANDROID
5569#include "android/SDL_androidmessagebox.h"
5570#endif
5571#ifdef SDL_VIDEO_DRIVER_WINDOWS
5572#include "windows/SDL_windowsmessagebox.h"
5573#endif
5574#ifdef SDL_VIDEO_DRIVER_COCOA
5575#include "cocoa/SDL_cocoamessagebox.h"
5576#endif
5577#ifdef SDL_VIDEO_DRIVER_UIKIT
5578#include "uikit/SDL_uikitmessagebox.h"
5579#endif
5580#ifdef SDL_VIDEO_DRIVER_WAYLAND
5581#include "wayland/SDL_waylandmessagebox.h"
5582#endif
5583#ifdef SDL_VIDEO_DRIVER_X11
5584#include "x11/SDL_x11messagebox.h"
5585#endif
5586#ifdef SDL_VIDEO_DRIVER_HAIKU
5587#include "haiku/SDL_bmessagebox.h"
5588#endif
5589#ifdef SDL_VIDEO_DRIVER_RISCOS
5590#include "riscos/SDL_riscosmessagebox.h"
5591#endif
5592#ifdef SDL_VIDEO_DRIVER_VITA
5593#include "vita/SDL_vitamessagebox.h"
5594#endif
5595
5596bool SDL_ShowMessageBox(const SDL_MessageBoxData *messageboxdata, int *buttonID)
5597{
5598 int dummybutton;
5599 bool result = false;
5600 bool show_cursor_prev;
5601 SDL_Window *current_window;
5602 SDL_MessageBoxData mbdata;
5603
5604 if (!messageboxdata) {
5605 return SDL_InvalidParamError("messageboxdata");
5606 } else if (messageboxdata->numbuttons < 0) {
5607 return SDL_SetError("Invalid number of buttons");
5608 }
5609
5610 // in case either the title or message was a pointer from SDL_GetError(), make a copy
5611 // now, as we'll likely overwrite error state in here.
5612 bool titleisstack = false, msgisstack = false;
5613 char *titlecpy = NULL;
5614 char *msgcpy = NULL;
5615 if (messageboxdata->title) {
5616 const size_t slen = SDL_strlen(messageboxdata->title) + 1;
5617 titlecpy = SDL_small_alloc(char, slen, &titleisstack);
5618 if (!titlecpy) {
5619 return false;
5620 }
5621 SDL_memcpy(titlecpy, messageboxdata->title, slen);
5622 }
5623
5624 if (messageboxdata->message) {
5625 const size_t slen = SDL_strlen(messageboxdata->message) + 1;
5626 msgcpy = SDL_small_alloc(char, slen, &msgisstack);
5627 if (!msgcpy) {
5628 SDL_small_free(titlecpy, titleisstack);
5629 return false;
5630 }
5631 SDL_memcpy(msgcpy, messageboxdata->message, slen);
5632 }
5633
5634 (void)SDL_AtomicIncRef(&SDL_messagebox_count);
5635
5636 current_window = SDL_GetKeyboardFocus();
5637 SDL_UpdateMouseCapture(false);
5638 SDL_SetRelativeMouseMode(false);
5639 show_cursor_prev = SDL_CursorVisible();
5640 SDL_ShowCursor();
5641 SDL_ResetKeyboard();
5642
5643 if (!buttonID) {
5644 buttonID = &dummybutton;
5645 }
5646
5647 SDL_memcpy(&mbdata, messageboxdata, sizeof(*messageboxdata));
5648 mbdata.title = titlecpy;
5649 if (!mbdata.title) {
5650 mbdata.title = "";
5651 }
5652 mbdata.message = msgcpy;
5653 if (!mbdata.message) {
5654 mbdata.message = "";
5655 }
5656 messageboxdata = &mbdata;
5657
5658 SDL_ClearError();
5659
5660 if (_this && _this->ShowMessageBox) {
5661 result = _this->ShowMessageBox(_this, messageboxdata, buttonID);
5662 } else {
5663 // It's completely fine to call this function before video is initialized
5664 const char *driver_name = SDL_GetHint(SDL_HINT_VIDEO_DRIVER);
5665 if (driver_name && *driver_name != 0) {
5666 const char *driver_attempt = driver_name;
5667 while (driver_attempt && (*driver_attempt != 0) && !result) {
5668 const char *driver_attempt_end = SDL_strchr(driver_attempt, ',');
5669 size_t driver_attempt_len = (driver_attempt_end) ? (driver_attempt_end - driver_attempt)
5670 : SDL_strlen(driver_attempt);
5671 for (int i = 0; bootstrap[i]; ++i) {
5672 if (bootstrap[i]->ShowMessageBox && (driver_attempt_len == SDL_strlen(bootstrap[i]->name)) &&
5673 (SDL_strncasecmp(bootstrap[i]->name, driver_attempt, driver_attempt_len) == 0)) {
5674 if (bootstrap[i]->ShowMessageBox(messageboxdata, buttonID)) {
5675 result = true;
5676 }
5677 break;
5678 }
5679 }
5680
5681 driver_attempt = (driver_attempt_end) ? (driver_attempt_end + 1) : NULL;
5682 }
5683 } else {
5684 for (int i = 0; bootstrap[i]; ++i) {
5685 if (bootstrap[i]->ShowMessageBox && bootstrap[i]->ShowMessageBox(messageboxdata, buttonID)) {
5686 result = true;
5687 break;
5688 }
5689 }
5690 }
5691 }
5692
5693 if (!result) {
5694 const char *error = SDL_GetError();
5695
5696 if (!*error) {
5697 SDL_SetError("No message system available");
5698 }
5699 } else {
5700 SDL_ClearError();
5701 }
5702
5703 (void)SDL_AtomicDecRef(&SDL_messagebox_count);
5704
5705 if (current_window) {
5706 SDL_RaiseWindow(current_window);
5707 }
5708
5709 if (!show_cursor_prev) {
5710 SDL_HideCursor();
5711 }
5712 SDL_UpdateRelativeMouseMode();
5713 SDL_UpdateMouseCapture(false);
5714
5715 SDL_small_free(msgcpy, msgisstack);
5716 SDL_small_free(titlecpy, titleisstack);
5717
5718 return result;
5719}
5720
5721bool SDL_ShowSimpleMessageBox(SDL_MessageBoxFlags flags, const char *title, const char *message, SDL_Window *window)
5722{
5723#ifdef SDL_PLATFORM_EMSCRIPTEN
5724 // !!! FIXME: propose a browser API for this, get this #ifdef out of here?
5725 /* Web browsers don't (currently) have an API for a custom message box
5726 that can block, but for the most common case (SDL_ShowSimpleMessageBox),
5727 we can use the standard Javascript alert() function. */
5728 if (!title) {
5729 title = "";
5730 }
5731 if (!message) {
5732 message = "";
5733 }
5734 EM_ASM({
5735 alert(UTF8ToString($0) + "\n\n" + UTF8ToString($1));
5736 },
5737 title, message);
5738 return true;
5739#elif defined(SDL_PLATFORM_3DS)
5740 errorConf errCnf;
5741 bool hasGpuRight;
5742
5743 // If the video subsystem has not been initialised, set up graphics temporarily
5744 hasGpuRight = gspHasGpuRight();
5745 if (!hasGpuRight)
5746 gfxInitDefault();
5747
5748 errorInit(&errCnf, ERROR_TEXT_WORD_WRAP, CFG_LANGUAGE_EN);
5749 errorText(&errCnf, message);
5750 errorDisp(&errCnf);
5751
5752 if (!hasGpuRight)
5753 gfxExit();
5754
5755 return true;
5756#else
5757 SDL_MessageBoxData data;
5758 SDL_MessageBoxButtonData button;
5759
5760 SDL_zero(data);
5761 data.flags = flags;
5762 data.title = title;
5763 data.message = message;
5764 data.numbuttons = 1;
5765 data.buttons = &button;
5766 data.window = window;
5767
5768 SDL_zero(button);
5769 button.flags |= SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT;
5770 button.flags |= SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT;
5771 button.text = "OK";
5772
5773 return SDL_ShowMessageBox(&data, NULL);
5774#endif
5775}
5776
5777bool SDL_ShouldAllowTopmost(void)
5778{
5779 return SDL_GetHintBoolean(SDL_HINT_WINDOW_ALLOW_TOPMOST, true);
5780}
5781
5782bool SDL_ShowWindowSystemMenu(SDL_Window *window, int x, int y)
5783{
5784 CHECK_WINDOW_MAGIC(window, false)
5785 CHECK_WINDOW_NOT_POPUP(window, false)
5786
5787 if (_this->ShowWindowSystemMenu) {
5788 _this->ShowWindowSystemMenu(window, x, y);
5789 return true;
5790 }
5791
5792 return SDL_Unsupported();
5793}
5794
5795bool SDL_SetWindowHitTest(SDL_Window *window, SDL_HitTest callback, void *callback_data)
5796{
5797 CHECK_WINDOW_MAGIC(window, false);
5798
5799 if (!_this->SetWindowHitTest) {
5800 return SDL_Unsupported();
5801 }
5802
5803 window->hit_test = callback;
5804 window->hit_test_data = callback_data;
5805
5806 return _this->SetWindowHitTest(window, callback != NULL);
5807}
5808
5809bool SDL_SetWindowShape(SDL_Window *window, SDL_Surface *shape)
5810{
5811 SDL_PropertiesID props;
5812 SDL_Surface *surface;
5813
5814 CHECK_WINDOW_MAGIC(window, false);
5815
5816 if (!(window->flags & SDL_WINDOW_TRANSPARENT)) {
5817 return SDL_SetError("Window must be created with SDL_WINDOW_TRANSPARENT");
5818 }
5819
5820 props = SDL_GetWindowProperties(window);
5821 if (!props) {
5822 return false;
5823 }
5824
5825 surface = SDL_ConvertSurface(shape, SDL_PIXELFORMAT_ARGB32);
5826 if (!surface) {
5827 return false;
5828 }
5829
5830 if (!SDL_SetSurfaceProperty(props, SDL_PROP_WINDOW_SHAPE_POINTER, surface)) {
5831 return false;
5832 }
5833
5834 if (_this->UpdateWindowShape) {
5835 if (!_this->UpdateWindowShape(_this, window, surface)) {
5836 return false;
5837 }
5838 }
5839 return true;
5840}
5841
5842/*
5843 * Functions used by iOS application delegates
5844 */
5845void SDL_OnApplicationWillTerminate(void)
5846{
5847 SDL_SendAppEvent(SDL_EVENT_TERMINATING);
5848}
5849
5850void SDL_OnApplicationDidReceiveMemoryWarning(void)
5851{
5852 SDL_SendAppEvent(SDL_EVENT_LOW_MEMORY);
5853}
5854
5855void SDL_OnApplicationWillEnterBackground(void)
5856{
5857 if (_this) {
5858 SDL_Window *window;
5859 for (window = _this->windows; window; window = window->next) {
5860 SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_MINIMIZED, 0, 0);
5861 }
5862 SDL_SetKeyboardFocus(NULL);
5863 }
5864 SDL_SendAppEvent(SDL_EVENT_WILL_ENTER_BACKGROUND);
5865}
5866
5867void SDL_OnApplicationDidEnterBackground(void)
5868{
5869 SDL_SendAppEvent(SDL_EVENT_DID_ENTER_BACKGROUND);
5870}
5871
5872void SDL_OnApplicationWillEnterForeground(void)
5873{
5874 SDL_SendAppEvent(SDL_EVENT_WILL_ENTER_FOREGROUND);
5875}
5876
5877void SDL_OnApplicationDidEnterForeground(void)
5878{
5879 SDL_SendAppEvent(SDL_EVENT_DID_ENTER_FOREGROUND);
5880
5881 if (_this) {
5882 SDL_Window *window;
5883 for (window = _this->windows; window; window = window->next) {
5884 SDL_SetKeyboardFocus(window);
5885 SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_RESTORED, 0, 0);
5886 }
5887 }
5888}
5889
5890#define NOT_A_VULKAN_WINDOW "The specified window isn't a Vulkan window"
5891
5892bool SDL_Vulkan_LoadLibrary(const char *path)
5893{
5894 bool result;
5895
5896 if (!_this) {
5897 return SDL_UninitializedVideo();
5898 }
5899 if (_this->vulkan_config.loader_loaded) {
5900 if (path && SDL_strcmp(path, _this->vulkan_config.loader_path) != 0) {
5901 return SDL_SetError("Vulkan loader library already loaded");
5902 }
5903 result = true;
5904 } else {
5905 if (!_this->Vulkan_LoadLibrary) {
5906 return SDL_DllNotSupported("Vulkan");
5907 }
5908 result = _this->Vulkan_LoadLibrary(_this, path);
5909 }
5910 if (result) {
5911 _this->vulkan_config.loader_loaded++;
5912 }
5913 return result;
5914}
5915
5916SDL_FunctionPointer SDL_Vulkan_GetVkGetInstanceProcAddr(void)
5917{
5918 if (!_this) {
5919 SDL_UninitializedVideo();
5920 return NULL;
5921 }
5922 if (!_this->vulkan_config.loader_loaded) {
5923 SDL_SetError("No Vulkan loader has been loaded");
5924 return NULL;
5925 }
5926 return (SDL_FunctionPointer)_this->vulkan_config.vkGetInstanceProcAddr;
5927}
5928
5929void SDL_Vulkan_UnloadLibrary(void)
5930{
5931 if (!_this) {
5932 SDL_UninitializedVideo();
5933 return;
5934 }
5935 if (_this->vulkan_config.loader_loaded > 0) {
5936 if (--_this->vulkan_config.loader_loaded > 0) {
5937 return;
5938 }
5939 if (_this->Vulkan_UnloadLibrary) {
5940 _this->Vulkan_UnloadLibrary(_this);
5941 }
5942 }
5943}
5944
5945char const* const* SDL_Vulkan_GetInstanceExtensions(Uint32 *count)
5946{
5947 return _this->Vulkan_GetInstanceExtensions(_this, count);
5948}
5949
5950bool SDL_Vulkan_CreateSurface(SDL_Window *window,
5951 VkInstance instance,
5952 const struct VkAllocationCallbacks *allocator,
5953 VkSurfaceKHR *surface)
5954{
5955 CHECK_WINDOW_MAGIC(window, false);
5956
5957 if (!(window->flags & SDL_WINDOW_VULKAN)) {
5958 return SDL_SetError(NOT_A_VULKAN_WINDOW);
5959 }
5960
5961 if (!instance) {
5962 return SDL_InvalidParamError("instance");
5963 }
5964
5965 if (!surface) {
5966 return SDL_InvalidParamError("surface");
5967 }
5968
5969 return _this->Vulkan_CreateSurface(_this, window, instance, allocator, surface);
5970}
5971
5972void SDL_Vulkan_DestroySurface(VkInstance instance,
5973 VkSurfaceKHR surface,
5974 const struct VkAllocationCallbacks *allocator)
5975{
5976 if (_this && instance && surface && _this->Vulkan_DestroySurface) {
5977 _this->Vulkan_DestroySurface(_this, instance, surface, allocator);
5978 }
5979}
5980
5981bool SDL_Vulkan_GetPresentationSupport(VkInstance instance,
5982 VkPhysicalDevice physicalDevice,
5983 Uint32 queueFamilyIndex)
5984{
5985 if (!_this) {
5986 SDL_UninitializedVideo();
5987 return false;
5988 }
5989
5990 if (!instance) {
5991 SDL_InvalidParamError("instance");
5992 return false;
5993 }
5994
5995 if (!physicalDevice) {
5996 SDL_InvalidParamError("physicalDevice");
5997 return false;
5998 }
5999
6000 if (_this->Vulkan_GetPresentationSupport) {
6001 return _this->Vulkan_GetPresentationSupport(_this, instance, physicalDevice, queueFamilyIndex);
6002 }
6003
6004 /* If the backend does not have this function then it does not have a
6005 * WSI function to query it; in other words it's not necessary to check
6006 * as it is always supported.
6007 */
6008 return true;
6009}
6010
6011SDL_MetalView SDL_Metal_CreateView(SDL_Window *window)
6012{
6013 CHECK_WINDOW_MAGIC(window, NULL);
6014
6015 if (!_this->Metal_CreateView) {
6016 SDL_Unsupported();
6017 return NULL;
6018 }
6019
6020 if (!(window->flags & SDL_WINDOW_METAL)) {
6021 // No problem, we can convert to Metal
6022 if (window->flags & SDL_WINDOW_OPENGL) {
6023 window->flags &= ~SDL_WINDOW_OPENGL;
6024 SDL_GL_UnloadLibrary();
6025 }
6026 if (window->flags & SDL_WINDOW_VULKAN) {
6027 window->flags &= ~SDL_WINDOW_VULKAN;
6028 SDL_Vulkan_UnloadLibrary();
6029 }
6030 window->flags |= SDL_WINDOW_METAL;
6031 }
6032
6033 return _this->Metal_CreateView(_this, window);
6034}
6035
6036void SDL_Metal_DestroyView(SDL_MetalView view)
6037{
6038 if (_this && view && _this->Metal_DestroyView) {
6039 _this->Metal_DestroyView(_this, view);
6040 }
6041}
6042
6043void *SDL_Metal_GetLayer(SDL_MetalView view)
6044{
6045 if (_this && _this->Metal_GetLayer) {
6046 if (view) {
6047 return _this->Metal_GetLayer(_this, view);
6048 } else {
6049 SDL_InvalidParamError("view");
6050 return NULL;
6051 }
6052 } else {
6053 SDL_SetError("Metal is not supported.");
6054 return NULL;
6055 }
6056}
6057
6058#if defined(SDL_VIDEO_DRIVER_X11) || defined(SDL_VIDEO_DRIVER_WAYLAND) || defined(SDL_VIDEO_DRIVER_EMSCRIPTEN)
6059const char *SDL_GetCSSCursorName(SDL_SystemCursor id, const char **fallback_name)
6060{
6061 // Reference: https://www.w3.org/TR/css-ui-4/#cursor
6062 // Also in: https://www.freedesktop.org/wiki/Specifications/cursor-spec/
6063 switch (id) {
6064 case SDL_SYSTEM_CURSOR_DEFAULT:
6065 return "default";
6066
6067 case SDL_SYSTEM_CURSOR_TEXT:
6068 return "text";
6069
6070 case SDL_SYSTEM_CURSOR_WAIT:
6071 return "wait";
6072
6073 case SDL_SYSTEM_CURSOR_CROSSHAIR:
6074 return "crosshair";
6075
6076 case SDL_SYSTEM_CURSOR_PROGRESS:
6077 return "progress";
6078
6079 case SDL_SYSTEM_CURSOR_NWSE_RESIZE:
6080 if (fallback_name) {
6081 // only a single arrow
6082 *fallback_name = "nw-resize";
6083 }
6084 return "nwse-resize";
6085
6086 case SDL_SYSTEM_CURSOR_NESW_RESIZE:
6087 if (fallback_name) {
6088 // only a single arrow
6089 *fallback_name = "ne-resize";
6090 }
6091 return "nesw-resize";
6092
6093 case SDL_SYSTEM_CURSOR_EW_RESIZE:
6094 if (fallback_name) {
6095 *fallback_name = "col-resize";
6096 }
6097 return "ew-resize";
6098
6099 case SDL_SYSTEM_CURSOR_NS_RESIZE:
6100 if (fallback_name) {
6101 *fallback_name = "row-resize";
6102 }
6103 return "ns-resize";
6104
6105 case SDL_SYSTEM_CURSOR_MOVE:
6106 return "all-scroll";
6107
6108 case SDL_SYSTEM_CURSOR_NOT_ALLOWED:
6109 return "not-allowed";
6110
6111 case SDL_SYSTEM_CURSOR_POINTER:
6112 return "pointer";
6113
6114 case SDL_SYSTEM_CURSOR_NW_RESIZE:
6115 return "nw-resize";
6116
6117 case SDL_SYSTEM_CURSOR_N_RESIZE:
6118 return "n-resize";
6119
6120 case SDL_SYSTEM_CURSOR_NE_RESIZE:
6121 return "ne-resize";
6122
6123 case SDL_SYSTEM_CURSOR_E_RESIZE:
6124 return "e-resize";
6125
6126 case SDL_SYSTEM_CURSOR_SE_RESIZE:
6127 return "se-resize";
6128
6129 case SDL_SYSTEM_CURSOR_S_RESIZE:
6130 return "s-resize";
6131
6132 case SDL_SYSTEM_CURSOR_SW_RESIZE:
6133 return "sw-resize";
6134
6135 case SDL_SYSTEM_CURSOR_W_RESIZE:
6136 return "w-resize";
6137
6138 default:
6139 return "default";
6140 }
6141}
6142#endif
6143