1/**
2 * Copyright (c) 2006-2023 LOVE Development Team
3 *
4 * This software is provided 'as-is', without any express or implied
5 * warranty. In no event will the authors be held liable for any damages
6 * arising from the use of this software.
7 *
8 * Permission is granted to anyone to use this software for any purpose,
9 * including commercial applications, and to alter it and redistribute it
10 * freely, subject to the following restrictions:
11 *
12 * 1. The origin of this software must not be misrepresented; you must not
13 * claim that you wrote the original software. If you use this software
14 * in a product, an acknowledgment in the product documentation would be
15 * appreciated but is not required.
16 * 2. Altered source versions must be plainly marked as such, and must not be
17 * misrepresented as being the original software.
18 * 3. This notice may not be removed or altered from any source distribution.
19 **/
20
21// LOVE
22#include "common/config.h"
23#include "graphics/Graphics.h"
24#include "Window.h"
25
26#ifdef LOVE_ANDROID
27#include "common/android.h"
28#endif
29
30#ifdef LOVE_IOS
31#include "common/ios.h"
32#endif
33
34// C++
35#include <iostream>
36#include <vector>
37#include <algorithm>
38
39// C
40#include <cstdio>
41
42// SDL
43#include <SDL_syswm.h>
44
45#if defined(LOVE_WINDOWS)
46#include <windows.h>
47#include <dwmapi.h>
48#include <VersionHelpers.h>
49#elif defined(LOVE_MACOSX)
50#include "common/macosx.h"
51#endif
52
53#ifndef APIENTRY
54#define APIENTRY
55#endif
56
57namespace love
58{
59namespace window
60{
61namespace sdl
62{
63
64Window::Window()
65 : open(false)
66 , mouseGrabbed(false)
67 , window(nullptr)
68 , context(nullptr)
69 , displayedWindowError(false)
70 , hasSDL203orEarlier(false)
71 , contextAttribs()
72{
73 if (SDL_InitSubSystem(SDL_INIT_VIDEO) < 0)
74 throw love::Exception("Could not initialize SDL video subsystem (%s)", SDL_GetError());
75
76 // Make sure the screensaver doesn't activate by default.
77 setDisplaySleepEnabled(false);
78
79 SDL_version version = {};
80 SDL_GetVersion(&version);
81 hasSDL203orEarlier = (version.major == 2 && version.minor == 0 && version.patch <= 3);
82}
83
84Window::~Window()
85{
86 close(false);
87
88 graphics.set(nullptr);
89
90 SDL_QuitSubSystem(SDL_INIT_VIDEO);
91}
92
93void Window::setGraphics(graphics::Graphics *graphics)
94{
95 this->graphics.set(graphics);
96}
97
98void Window::setGLFramebufferAttributes(int msaa, bool sRGB, bool stencil, int depth)
99{
100 // Set GL window / framebuffer attributes.
101 SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8);
102 SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8);
103 SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8);
104 SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 8);
105 SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
106 SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, stencil ? 8 : 0);
107 SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, depth);
108 SDL_GL_SetAttribute(SDL_GL_RETAINED_BACKING, 0);
109
110 SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, (msaa > 0) ? 1 : 0);
111 SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, (msaa > 0) ? msaa : 0);
112
113 SDL_GL_SetAttribute(SDL_GL_FRAMEBUFFER_SRGB_CAPABLE, sRGB ? 1 : 0);
114
115 const char *driver = SDL_GetCurrentVideoDriver();
116 if (driver && strstr(driver, "x11") == driver)
117 {
118 // Always disable the sRGB flag when GLX is used with older SDL versions,
119 // because of this bug: https://bugzilla.libsdl.org/show_bug.cgi?id=2897
120 // In practice GLX will always give an sRGB-capable framebuffer anyway.
121 if (hasSDL203orEarlier)
122 SDL_GL_SetAttribute(SDL_GL_FRAMEBUFFER_SRGB_CAPABLE, 0);
123 }
124
125#if defined(LOVE_WINDOWS)
126 // Avoid the Microsoft OpenGL 1.1 software renderer on Windows. Apparently
127 // older Intel drivers like to use it as a fallback when requesting some
128 // unsupported framebuffer attribute values, rather than properly failing.
129 SDL_GL_SetAttribute(SDL_GL_ACCELERATED_VISUAL, 1);
130#endif
131}
132
133void Window::setGLContextAttributes(const ContextAttribs &attribs)
134{
135 int profilemask = 0;
136 int contextflags = 0;
137
138 if (attribs.gles)
139 profilemask = SDL_GL_CONTEXT_PROFILE_ES;
140 else if (attribs.versionMajor * 10 + attribs.versionMinor >= 32)
141 profilemask |= SDL_GL_CONTEXT_PROFILE_CORE;
142 else if (attribs.debug)
143 profilemask = SDL_GL_CONTEXT_PROFILE_COMPATIBILITY;
144
145 if (attribs.debug)
146 contextflags |= SDL_GL_CONTEXT_DEBUG_FLAG;
147
148 SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, attribs.versionMajor);
149 SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, attribs.versionMinor);
150 SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, profilemask);
151 SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, contextflags);
152}
153
154bool Window::checkGLVersion(const ContextAttribs &attribs, std::string &outversion)
155{
156 typedef unsigned char GLubyte;
157 typedef unsigned int GLenum;
158 typedef const GLubyte *(APIENTRY *glGetStringPtr)(GLenum name);
159 const GLenum GL_VENDOR_ENUM = 0x1F00;
160 const GLenum GL_RENDERER_ENUM = 0x1F01;
161 const GLenum GL_VERSION_ENUM = 0x1F02;
162
163 // We don't have OpenGL headers or an automatic OpenGL function loader in
164 // this module, so we have to get the glGetString function pointer ourselves.
165 glGetStringPtr glGetStringFunc = (glGetStringPtr) SDL_GL_GetProcAddress("glGetString");
166 if (!glGetStringFunc)
167 return false;
168
169 const char *glversion = (const char *) glGetStringFunc(GL_VERSION_ENUM);
170 if (!glversion)
171 return false;
172
173 outversion = glversion;
174
175 const char *glrenderer = (const char *) glGetStringFunc(GL_RENDERER_ENUM);
176 if (glrenderer)
177 outversion += " - " + std::string(glrenderer);
178
179 const char *glvendor = (const char *) glGetStringFunc(GL_VENDOR_ENUM);
180 if (glvendor)
181 outversion += " (" + std::string(glvendor) + ")";
182
183 int glmajor = 0;
184 int glminor = 0;
185
186 // glGetString(GL_VERSION) returns a string with the format "major.minor",
187 // or "OpenGL ES major.minor" in GLES contexts.
188 const char *format = "%d.%d";
189 if (attribs.gles)
190 format = "OpenGL ES %d.%d";
191
192 if (sscanf(glversion, format, &glmajor, &glminor) != 2)
193 return false;
194
195 if (glmajor < attribs.versionMajor
196 || (glmajor == attribs.versionMajor && glminor < attribs.versionMinor))
197 return false;
198
199 return true;
200}
201
202std::vector<Window::ContextAttribs> Window::getContextAttribsList() const
203{
204 // If we already have a set of context attributes that we know work, just
205 // return that. love.graphics doesn't really support switching GL versions
206 // after the first initialization.
207 if (contextAttribs.versionMajor > 0)
208 return std::vector<ContextAttribs>{contextAttribs};
209
210 bool preferGLES = false;
211
212#ifdef LOVE_GRAPHICS_USE_OPENGLES
213 preferGLES = true;
214#endif
215
216 const char *curdriver = SDL_GetCurrentVideoDriver();
217 const char *glesdrivers[] = {"RPI", "Android", "uikit", "winrt", "emscripten"};
218
219 // We always want to try OpenGL ES first on certain video backends.
220 for (const char *glesdriver : glesdrivers)
221 {
222 if (curdriver && strstr(curdriver, glesdriver) == curdriver)
223 {
224 preferGLES = true;
225
226 // Prior to SDL 2.0.4, backends that use OpenGL ES didn't properly
227 // ask for a sRGB framebuffer when requested by SDL_GL_SetAttribute.
228 // FIXME: This doesn't account for windowing backends that sometimes
229 // use EGL, e.g. the X11 and windows SDL backends.
230 if (hasSDL203orEarlier)
231 graphics::setGammaCorrect(false);
232
233 break;
234 }
235 }
236
237 if (!preferGLES)
238 {
239 const char *gleshint = SDL_GetHint("LOVE_GRAPHICS_USE_OPENGLES");
240 preferGLES = (gleshint != nullptr && gleshint[0] != '0');
241 }
242
243 // Do we want a debug context?
244 bool debug = love::graphics::isDebugEnabled();
245
246 const char *preferGL2hint = SDL_GetHint("LOVE_GRAPHICS_USE_GL2");
247 bool preferGL2 = (preferGL2hint != nullptr && preferGL2hint[0] != '0');
248
249 std::vector<ContextAttribs> glcontexts = {{2, 1, false, debug}};
250 glcontexts.insert(preferGL2 ? glcontexts.end() : glcontexts.begin(), {3, 3, false, debug});
251
252 std::vector<ContextAttribs> glescontexts = {{2, 0, true, debug}};
253
254 // While UWP SDL is above 2.0.4, it still doesn't support OpenGL ES 3+
255#ifndef LOVE_WINDOWS_UWP
256 // OpenGL ES 3+ contexts are only properly supported in SDL 2.0.4+.
257 if (!hasSDL203orEarlier)
258 glescontexts.insert(preferGL2 ? glescontexts.end() : glescontexts.begin(), {3, 0, true, debug});
259#endif
260
261 std::vector<ContextAttribs> attribslist;
262
263 if (preferGLES)
264 {
265 attribslist.insert(attribslist.end(), glescontexts.begin(), glescontexts.end());
266 attribslist.insert(attribslist.end(), glcontexts.begin(), glcontexts.end());
267 }
268 else
269 {
270 attribslist.insert(attribslist.end(), glcontexts.begin(), glcontexts.end());
271 attribslist.insert(attribslist.end(), glescontexts.begin(), glescontexts.end());
272 }
273
274 return attribslist;
275}
276
277bool Window::createWindowAndContext(int x, int y, int w, int h, Uint32 windowflags, int msaa, bool stencil, int depth)
278{
279 std::vector<ContextAttribs> attribslist = getContextAttribsList();
280
281 std::string windowerror;
282 std::string contexterror;
283 std::string glversion;
284
285 // Unfortunately some OpenGL context settings are part of the internal
286 // window state in the Windows and Linux SDL backends, so we have to
287 // recreate the window when we want to change those settings...
288 // Also, apparently some Intel drivers on Windows give back a Microsoft
289 // OpenGL 1.1 software renderer context when high MSAA values are requested!
290
291 const auto create = [&](ContextAttribs attribs) -> bool
292 {
293 if (context)
294 {
295 SDL_GL_DeleteContext(context);
296 context = nullptr;
297 }
298
299 if (window)
300 {
301 SDL_DestroyWindow(window);
302 SDL_FlushEvent(SDL_WINDOWEVENT);
303 window = nullptr;
304 }
305
306 window = SDL_CreateWindow(title.c_str(), x, y, w, h, windowflags);
307
308 if (!window)
309 {
310 windowerror = std::string(SDL_GetError());
311 return false;
312 }
313
314#ifdef LOVE_MACOSX
315 love::macosx::setWindowSRGBColorSpace(window);
316#endif
317
318 context = SDL_GL_CreateContext(window);
319
320 if (!context)
321 contexterror = std::string(SDL_GetError());
322
323 // Make sure the context's version is at least what we requested.
324 if (context && !checkGLVersion(attribs, glversion))
325 {
326 SDL_GL_DeleteContext(context);
327 context = nullptr;
328 }
329
330 if (!context)
331 {
332 SDL_DestroyWindow(window);
333 window = nullptr;
334 return false;
335 }
336
337 return true;
338 };
339
340 // Try each context profile in order.
341 for (ContextAttribs attribs : attribslist)
342 {
343 int curMSAA = msaa;
344 bool curSRGB = love::graphics::isGammaCorrect();
345
346 setGLFramebufferAttributes(curMSAA, curSRGB, stencil, depth);
347 setGLContextAttributes(attribs);
348
349 windowerror.clear();
350 contexterror.clear();
351
352 create(attribs);
353
354 if (!window && curMSAA > 0)
355 {
356 // The MSAA setting could have caused the failure.
357 setGLFramebufferAttributes(0, curSRGB, stencil, depth);
358 if (create(attribs))
359 curMSAA = 0;
360 }
361
362 if (!window && curSRGB)
363 {
364 // same with sRGB.
365 setGLFramebufferAttributes(curMSAA, false, stencil, depth);
366 if (create(attribs))
367 curSRGB = false;
368 }
369
370 if (!window && curMSAA > 0 && curSRGB)
371 {
372 // Or both!
373 setGLFramebufferAttributes(0, false, stencil, depth);
374 if (create(attribs))
375 {
376 curMSAA = 0;
377 curSRGB = false;
378 }
379 }
380
381 if (window && context)
382 {
383 // Store the successful context attributes so we can re-use them in
384 // subsequent calls to createWindowAndContext.
385 contextAttribs = attribs;
386 love::graphics::setGammaCorrect(curSRGB);
387 break;
388 }
389 }
390
391 if (!context || !window)
392 {
393 std::string title = "Unable to create OpenGL window";
394 std::string message = "This program requires a graphics card and video drivers which support OpenGL 2.1 or OpenGL ES 2.";
395
396 if (!glversion.empty())
397 message += "\n\nDetected OpenGL version:\n" + glversion;
398 else if (!contexterror.empty())
399 message += "\n\nOpenGL context creation error: " + contexterror;
400 else if (!windowerror.empty())
401 message += "\n\nSDL window creation error: " + windowerror;
402
403 std::cerr << title << std::endl << message << std::endl;
404
405 // Display a message box with the error, but only once.
406 if (!displayedWindowError)
407 {
408 showMessageBox(title, message, MESSAGEBOX_ERROR, false);
409 displayedWindowError = true;
410 }
411
412 close();
413 return false;
414 }
415
416 open = true;
417 return true;
418}
419
420bool Window::setWindow(int width, int height, WindowSettings *settings)
421{
422 if (!graphics.get())
423 graphics.set(Module::getInstance<graphics::Graphics>(Module::M_GRAPHICS));
424
425 if (graphics.get() && graphics->isCanvasActive())
426 throw love::Exception("love.window.setMode cannot be called while a Canvas is active in love.graphics.");
427
428 WindowSettings f;
429
430 if (settings)
431 f = *settings;
432
433 f.minwidth = std::max(f.minwidth, 1);
434 f.minheight = std::max(f.minheight, 1);
435
436 f.display = std::min(std::max(f.display, 0), getDisplayCount() - 1);
437
438 // Use the desktop resolution if a width or height of 0 is specified.
439 if (width == 0 || height == 0)
440 {
441 SDL_DisplayMode mode = {};
442 SDL_GetDesktopDisplayMode(f.display, &mode);
443 width = mode.w;
444 height = mode.h;
445 }
446
447 Uint32 sdlflags = SDL_WINDOW_OPENGL;
448
449 // On Android, disable fullscreen first on window creation so it's
450 // possible to change the orientation by specifying portait width and
451 // height, otherwise SDL will pick the current orientation dimensions when
452 // fullscreen flag is set. Don't worry, we'll set it back later when user
453 // also requested fullscreen after the window is created.
454 // See https://github.com/love2d/love-android/issues/196
455#ifdef LOVE_ANDROID
456 bool fullscreen = f.fullscreen;
457
458 f.fullscreen = false;
459 f.fstype = FULLSCREEN_DESKTOP;
460#endif
461
462 if (f.fullscreen)
463 {
464 if (f.fstype == FULLSCREEN_DESKTOP)
465 sdlflags |= SDL_WINDOW_FULLSCREEN_DESKTOP;
466 else
467 {
468 sdlflags |= SDL_WINDOW_FULLSCREEN;
469 SDL_DisplayMode mode = {0, width, height, 0, nullptr};
470
471 // Fullscreen window creation will bug out if no mode can be used.
472 if (SDL_GetClosestDisplayMode(f.display, &mode, &mode) == nullptr)
473 {
474 // GetClosestDisplayMode will fail if we request a size larger
475 // than the largest available display mode, so we'll try to use
476 // the largest (first) mode in that case.
477 if (SDL_GetDisplayMode(f.display, 0, &mode) < 0)
478 return false;
479 }
480
481 width = mode.w;
482 height = mode.h;
483 }
484 }
485
486 if (f.resizable)
487 sdlflags |= SDL_WINDOW_RESIZABLE;
488
489 if (f.borderless)
490 sdlflags |= SDL_WINDOW_BORDERLESS;
491
492 if (f.highdpi)
493 sdlflags |= SDL_WINDOW_ALLOW_HIGHDPI;
494
495 int x = f.x;
496 int y = f.y;
497
498 if (f.useposition)
499 {
500 // The position needs to be in the global coordinate space.
501 SDL_Rect displaybounds = {};
502 SDL_GetDisplayBounds(f.display, &displaybounds);
503 x += displaybounds.x;
504 y += displaybounds.y;
505 }
506 else
507 {
508 if (f.centered)
509 x = y = SDL_WINDOWPOS_CENTERED_DISPLAY(f.display);
510 else
511 x = y = SDL_WINDOWPOS_UNDEFINED_DISPLAY(f.display);
512 }
513
514 close();
515
516 if (!createWindowAndContext(x, y, width, height, sdlflags, f.msaa, f.stencil, f.depth))
517 return false;
518
519 // Make sure the window keeps any previously set icon.
520 setIcon(icon.get());
521
522 // Make sure the mouse keeps its previous grab setting.
523 setMouseGrab(mouseGrabbed);
524
525 // Enforce minimum window dimensions.
526 SDL_SetWindowMinimumSize(window, f.minwidth, f.minheight);
527
528 if (f.useposition || f.centered)
529 SDL_SetWindowPosition(window, x, y);
530
531 SDL_RaiseWindow(window);
532
533 setVSync(f.vsync);
534
535 updateSettings(f, false);
536
537 if (graphics.get())
538 {
539 double scaledw, scaledh;
540 fromPixels((double) pixelWidth, (double) pixelHeight, scaledw, scaledh);
541 graphics->setMode((int) scaledw, (int) scaledh, pixelWidth, pixelHeight, f.stencil);
542 }
543
544 // Set fullscreen when user requested it before.
545 // See above for explanation.
546#ifdef LOVE_ANDROID
547 setFullscreen(fullscreen);
548 love::android::setImmersive(fullscreen);
549#endif
550
551 return true;
552}
553
554bool Window::onSizeChanged(int width, int height)
555{
556 if (!window)
557 return false;
558
559 windowWidth = width;
560 windowHeight = height;
561
562 SDL_GL_GetDrawableSize(window, &pixelWidth, &pixelHeight);
563
564 if (graphics.get())
565 {
566 double scaledw, scaledh;
567 fromPixels((double) pixelWidth, (double) pixelHeight, scaledw, scaledh);
568 graphics->setViewportSize((int) scaledw, (int) scaledh, pixelWidth, pixelHeight);
569 }
570
571 return true;
572}
573
574void Window::updateSettings(const WindowSettings &newsettings, bool updateGraphicsViewport)
575{
576 Uint32 wflags = SDL_GetWindowFlags(window);
577
578 // Set the new display mode as the current display mode.
579 SDL_GetWindowSize(window, &windowWidth, &windowHeight);
580 SDL_GL_GetDrawableSize(window, &pixelWidth, &pixelHeight);
581
582 if ((wflags & SDL_WINDOW_FULLSCREEN_DESKTOP) == SDL_WINDOW_FULLSCREEN_DESKTOP)
583 {
584 settings.fullscreen = true;
585 settings.fstype = FULLSCREEN_DESKTOP;
586 }
587 else if ((wflags & SDL_WINDOW_FULLSCREEN) == SDL_WINDOW_FULLSCREEN)
588 {
589 settings.fullscreen = true;
590 settings.fstype = FULLSCREEN_EXCLUSIVE;
591 }
592 else
593 {
594 settings.fullscreen = false;
595 settings.fstype = newsettings.fstype;
596 }
597
598#ifdef LOVE_ANDROID
599 settings.fullscreen = love::android::getImmersive();
600#endif
601
602 // SDL_GetWindowMinimumSize gives back 0,0 sometimes...
603 settings.minwidth = newsettings.minwidth;
604 settings.minheight = newsettings.minheight;
605
606 settings.resizable = (wflags & SDL_WINDOW_RESIZABLE) != 0;
607 settings.borderless = (wflags & SDL_WINDOW_BORDERLESS) != 0;
608 settings.centered = newsettings.centered;
609
610 getPosition(settings.x, settings.y, settings.display);
611
612 settings.highdpi = (wflags & SDL_WINDOW_ALLOW_HIGHDPI) != 0;
613 settings.usedpiscale = newsettings.usedpiscale;
614
615 // Only minimize on focus loss if the window is in exclusive-fullscreen mode
616 if (settings.fullscreen && settings.fstype == FULLSCREEN_EXCLUSIVE)
617 SDL_SetHint(SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS, "1");
618 else
619 SDL_SetHint(SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS, "0");
620
621 // Verify MSAA setting.
622 int buffers = 0;
623 int samples = 0;
624 SDL_GL_GetAttribute(SDL_GL_MULTISAMPLEBUFFERS, &buffers);
625 SDL_GL_GetAttribute(SDL_GL_MULTISAMPLESAMPLES, &samples);
626
627 settings.msaa = (buffers > 0 ? samples : 0);
628 settings.vsync = getVSync();
629
630 settings.stencil = newsettings.stencil;
631 settings.depth = newsettings.depth;
632
633 SDL_DisplayMode dmode = {};
634 SDL_GetCurrentDisplayMode(settings.display, &dmode);
635
636 // May be 0 if the refresh rate can't be determined.
637 settings.refreshrate = (double) dmode.refresh_rate;
638
639 // Update the viewport size now instead of waiting for event polling.
640 if (updateGraphicsViewport && graphics.get())
641 {
642 double scaledw, scaledh;
643 fromPixels((double) pixelWidth, (double) pixelHeight, scaledw, scaledh);
644 graphics->setViewportSize((int) scaledw, (int) scaledh, pixelWidth, pixelHeight);
645 }
646}
647
648void Window::getWindow(int &width, int &height, WindowSettings &newsettings)
649{
650 // The window might have been modified (moved, resized, etc.) by the user.
651 if (window)
652 updateSettings(settings, true);
653
654 width = windowWidth;
655 height = windowHeight;
656 newsettings = settings;
657}
658
659void Window::close()
660{
661 close(true);
662}
663
664void Window::close(bool allowExceptions)
665{
666 if (graphics.get())
667 {
668 if (allowExceptions && graphics->isCanvasActive())
669 throw love::Exception("love.window.close cannot be called while a Canvas is active in love.graphics.");
670
671 graphics->unSetMode();
672 }
673
674 if (context)
675 {
676 SDL_GL_DeleteContext(context);
677 context = nullptr;
678 }
679
680 if (window)
681 {
682 SDL_DestroyWindow(window);
683 window = nullptr;
684
685 // The old window may have generated pending events which are no longer
686 // relevant. Destroy them all!
687 SDL_FlushEvent(SDL_WINDOWEVENT);
688 }
689
690 open = false;
691}
692
693bool Window::setFullscreen(bool fullscreen, Window::FullscreenType fstype)
694{
695 if (!window)
696 return false;
697
698 if (graphics.get() && graphics->isCanvasActive())
699 throw love::Exception("love.window.setFullscreen cannot be called while a Canvas is active in love.graphics.");
700
701 WindowSettings newsettings = settings;
702 newsettings.fullscreen = fullscreen;
703 newsettings.fstype = fstype;
704
705 Uint32 sdlflags = 0;
706
707 if (fullscreen)
708 {
709 if (fstype == FULLSCREEN_DESKTOP)
710 sdlflags = SDL_WINDOW_FULLSCREEN_DESKTOP;
711 else
712 {
713 sdlflags = SDL_WINDOW_FULLSCREEN;
714
715 SDL_DisplayMode mode = {};
716 mode.w = windowWidth;
717 mode.h = windowHeight;
718
719 SDL_GetClosestDisplayMode(SDL_GetWindowDisplayIndex(window), &mode, &mode);
720 SDL_SetWindowDisplayMode(window, &mode);
721 }
722 }
723
724#ifdef LOVE_ANDROID
725 love::android::setImmersive(fullscreen);
726#endif
727
728 if (SDL_SetWindowFullscreen(window, sdlflags) == 0)
729 {
730 SDL_GL_MakeCurrent(window, context);
731 updateSettings(newsettings, true);
732 return true;
733 }
734
735 return false;
736}
737
738bool Window::setFullscreen(bool fullscreen)
739{
740 return setFullscreen(fullscreen, settings.fstype);
741}
742
743int Window::getDisplayCount() const
744{
745 return SDL_GetNumVideoDisplays();
746}
747
748const char *Window::getDisplayName(int displayindex) const
749{
750 const char *name = SDL_GetDisplayName(displayindex);
751
752 if (name == nullptr)
753 throw love::Exception("Invalid display index: %d", displayindex + 1);
754
755 return name;
756}
757
758Window::DisplayOrientation Window::getDisplayOrientation(int displayindex) const
759{
760 // TODO: We can expose this everywhere, we just need to watch out for the
761 // SDL binary being older than the headers on Linux.
762#if SDL_VERSION_ATLEAST(2, 0, 9) && (defined(LOVE_ANDROID) || !defined(LOVE_LINUX))
763 switch (SDL_GetDisplayOrientation(displayindex))
764 {
765 case SDL_ORIENTATION_UNKNOWN: return ORIENTATION_UNKNOWN;
766 case SDL_ORIENTATION_LANDSCAPE: return ORIENTATION_LANDSCAPE;
767 case SDL_ORIENTATION_LANDSCAPE_FLIPPED: return ORIENTATION_LANDSCAPE_FLIPPED;
768 case SDL_ORIENTATION_PORTRAIT: return ORIENTATION_PORTRAIT;
769 case SDL_ORIENTATION_PORTRAIT_FLIPPED: return ORIENTATION_PORTRAIT_FLIPPED;
770 }
771#else
772 LOVE_UNUSED(displayindex);
773#endif
774
775 return ORIENTATION_UNKNOWN;
776}
777
778std::vector<Window::WindowSize> Window::getFullscreenSizes(int displayindex) const
779{
780 std::vector<WindowSize> sizes;
781
782 for (int i = 0; i < SDL_GetNumDisplayModes(displayindex); i++)
783 {
784 SDL_DisplayMode mode = {};
785 SDL_GetDisplayMode(displayindex, i, &mode);
786
787 WindowSize w = {mode.w, mode.h};
788
789 // SDL2's display mode list has multiple entries for modes of the same
790 // size with different bits per pixel, so we need to filter those out.
791 if (std::find(sizes.begin(), sizes.end(), w) == sizes.end())
792 sizes.push_back(w);
793 }
794
795 return sizes;
796}
797
798void Window::getDesktopDimensions(int displayindex, int &width, int &height) const
799{
800 if (displayindex >= 0 && displayindex < getDisplayCount())
801 {
802 SDL_DisplayMode mode = {};
803 SDL_GetDesktopDisplayMode(displayindex, &mode);
804 width = mode.w;
805 height = mode.h;
806 }
807 else
808 {
809 width = 0;
810 height = 0;
811 }
812}
813
814void Window::setPosition(int x, int y, int displayindex)
815{
816 if (!window)
817 return;
818
819 displayindex = std::min(std::max(displayindex, 0), getDisplayCount() - 1);
820
821 SDL_Rect displaybounds = {};
822 SDL_GetDisplayBounds(displayindex, &displaybounds);
823
824 // The position needs to be in the global coordinate space.
825 x += displaybounds.x;
826 y += displaybounds.y;
827
828 SDL_SetWindowPosition(window, x, y);
829
830 settings.useposition = true;
831}
832
833void Window::getPosition(int &x, int &y, int &displayindex)
834{
835 if (!window)
836 {
837 x = y = 0;
838 displayindex = 0;
839 return;
840 }
841
842 displayindex = std::max(SDL_GetWindowDisplayIndex(window), 0);
843
844 SDL_GetWindowPosition(window, &x, &y);
845
846 // In SDL <= 2.0.3, fullscreen windows are always reported as 0,0. In every
847 // other case we need to convert the position from global coordinates to the
848 // monitor's coordinate space.
849 if (x != 0 || y != 0)
850 {
851 SDL_Rect displaybounds = {};
852 SDL_GetDisplayBounds(displayindex, &displaybounds);
853
854 x -= displaybounds.x;
855 y -= displaybounds.y;
856 }
857}
858
859Rect Window::getSafeArea() const
860{
861#if defined(LOVE_IOS)
862 if (window != nullptr)
863 return love::ios::getSafeArea(window);
864#elif defined(LOVE_ANDROID)
865 if (window != nullptr)
866 {
867 int top, left, bottom, right;
868
869 if (love::android::getSafeArea(top, left, bottom, right))
870 {
871 // DisplayCutout API returns safe area in pixels
872 // and is affected by display orientation.
873 double safeLeft, safeTop, safeWidth, safeHeight;
874 fromPixels(left, top, safeLeft, safeTop);
875 fromPixels(pixelWidth - left - right, pixelHeight - top - bottom, safeWidth, safeHeight);
876 return {(int) safeLeft, (int) safeTop, (int) safeWidth, (int) safeHeight};
877 }
878 }
879#endif
880
881 double dw, dh;
882 fromPixels(pixelWidth, pixelHeight, dw, dh);
883 return {0, 0, (int) dw, (int) dh};
884}
885
886bool Window::isOpen() const
887{
888 return open;
889}
890
891void Window::setWindowTitle(const std::string &title)
892{
893 this->title = title;
894
895 if (window)
896 SDL_SetWindowTitle(window, title.c_str());
897}
898
899const std::string &Window::getWindowTitle() const
900{
901 return title;
902}
903
904bool Window::setIcon(love::image::ImageData *imgd)
905{
906 if (!imgd)
907 return false;
908
909 if (imgd->getFormat() != PIXELFORMAT_RGBA8)
910 throw love::Exception("setIcon only accepts 32-bit RGBA images.");
911
912 icon.set(imgd);
913
914 if (!window)
915 return false;
916
917 Uint32 rmask, gmask, bmask, amask;
918#ifdef LOVE_BIG_ENDIAN
919 rmask = 0xFF000000;
920 gmask = 0x00FF0000;
921 bmask = 0x0000FF00;
922 amask = 0x000000FF;
923#else
924 rmask = 0x000000FF;
925 gmask = 0x0000FF00;
926 bmask = 0x00FF0000;
927 amask = 0xFF000000;
928#endif
929
930 int w = imgd->getWidth();
931 int h = imgd->getHeight();
932 int bytesperpixel = (int) getPixelFormatSize(imgd->getFormat());
933 int pitch = w * bytesperpixel;
934
935 SDL_Surface *sdlicon = nullptr;
936
937 {
938 // We don't want another thread modifying the ImageData mid-copy.
939 love::thread::Lock lock(imgd->getMutex());
940 sdlicon = SDL_CreateRGBSurfaceFrom(imgd->getData(), w, h, bytesperpixel * 8, pitch, rmask, gmask, bmask, amask);
941 }
942
943 if (!sdlicon)
944 return false;
945
946 SDL_SetWindowIcon(window, sdlicon);
947 SDL_FreeSurface(sdlicon);
948
949 return true;
950}
951
952love::image::ImageData *Window::getIcon()
953{
954 return icon.get();
955}
956
957void Window::setVSync(int vsync)
958{
959 if (context == nullptr)
960 return;
961
962 SDL_GL_SetSwapInterval(vsync);
963
964 // Check if adaptive vsync was requested but not supported, and fall back
965 // to regular vsync if so.
966 if (vsync == -1 && SDL_GL_GetSwapInterval() != -1)
967 SDL_GL_SetSwapInterval(1);
968}
969
970int Window::getVSync() const
971{
972 return context != nullptr ? SDL_GL_GetSwapInterval() : 0;
973}
974
975void Window::setDisplaySleepEnabled(bool enable)
976{
977 if (enable)
978 SDL_EnableScreenSaver();
979 else
980 SDL_DisableScreenSaver();
981}
982
983bool Window::isDisplaySleepEnabled() const
984{
985 return SDL_IsScreenSaverEnabled() != SDL_FALSE;
986}
987
988void Window::minimize()
989{
990 if (window != nullptr)
991 SDL_MinimizeWindow(window);
992}
993
994void Window::maximize()
995{
996 if (window != nullptr)
997 {
998 SDL_MaximizeWindow(window);
999 updateSettings(settings, true);
1000 }
1001}
1002
1003void Window::restore()
1004{
1005 if (window != nullptr)
1006 {
1007 SDL_RestoreWindow(window);
1008 updateSettings(settings, true);
1009 }
1010}
1011
1012bool Window::isMaximized() const
1013{
1014 return window != nullptr && (SDL_GetWindowFlags(window) & SDL_WINDOW_MAXIMIZED);
1015}
1016
1017bool Window::isMinimized() const
1018{
1019 return window != nullptr && (SDL_GetWindowFlags(window) & SDL_WINDOW_MINIMIZED);
1020}
1021
1022void Window::swapBuffers()
1023{
1024#ifdef LOVE_WINDOWS
1025 bool useDwmFlush = false;
1026 int swapInterval = getVSync();
1027
1028 // https://github.com/love2d/love/issues/1628
1029 // VSync can interact badly with Windows desktop composition (DWM) in windowed mode. DwmFlush can be used instead
1030 // of vsync, but it's much less flexible so we're very conservative here with where it's used:
1031 // - It won't work with exclusive or desktop fullscreen.
1032 // - DWM refreshes don't always match the refresh rate of the monitor the window is in (or the requested swap
1033 // interval), so we only use it when they do match.
1034 // - The user may force GL vsync, and DwmFlush shouldn't be used together with GL vsync.
1035 if (context != nullptr && !settings.fullscreen && swapInterval == 1)
1036 {
1037 // Desktop composition is always enabled in Windows 8+. But DwmIsCompositionEnabled won't always return true...
1038 // (see DwmIsCompositionEnabled docs).
1039 BOOL compositionEnabled = IsWindows8OrGreater();
1040 if (compositionEnabled || (SUCCEEDED(DwmIsCompositionEnabled(&compositionEnabled)) && compositionEnabled))
1041 {
1042 DWM_TIMING_INFO info = {};
1043 info.cbSize = sizeof(DWM_TIMING_INFO);
1044 double dwmRefreshRate = 0;
1045 if (SUCCEEDED(DwmGetCompositionTimingInfo(nullptr, &info)))
1046 dwmRefreshRate = (double)info.rateRefresh.uiNumerator / (double)info.rateRefresh.uiDenominator;
1047
1048 SDL_DisplayMode dmode = {};
1049 int displayindex = SDL_GetWindowDisplayIndex(window);
1050
1051 if (displayindex >= 0)
1052 SDL_GetCurrentDisplayMode(displayindex, &dmode);
1053
1054 if (dmode.refresh_rate > 0 && dwmRefreshRate > 0 && (fabs(dmode.refresh_rate - dwmRefreshRate) < 2))
1055 {
1056 SDL_GL_SetSwapInterval(0);
1057 if (SDL_GL_GetSwapInterval() == 0)
1058 useDwmFlush = true;
1059 else
1060 SDL_GL_SetSwapInterval(swapInterval);
1061 }
1062 }
1063 }
1064#endif
1065
1066 SDL_GL_SwapWindow(window);
1067
1068#ifdef LOVE_WINDOWS
1069 if (useDwmFlush)
1070 {
1071 DwmFlush();
1072 SDL_GL_SetSwapInterval(swapInterval);
1073 }
1074#endif
1075}
1076
1077bool Window::hasFocus() const
1078{
1079 return (window && SDL_GetKeyboardFocus() == window);
1080}
1081
1082bool Window::hasMouseFocus() const
1083{
1084 return (window && SDL_GetMouseFocus() == window);
1085}
1086
1087bool Window::isVisible() const
1088{
1089 return window && (SDL_GetWindowFlags(window) & SDL_WINDOW_SHOWN) != 0;
1090}
1091
1092void Window::setMouseGrab(bool grab)
1093{
1094 mouseGrabbed = grab;
1095 if (window)
1096 SDL_SetWindowGrab(window, (SDL_bool) grab);
1097}
1098
1099bool Window::isMouseGrabbed() const
1100{
1101 if (window)
1102 return SDL_GetWindowGrab(window) != SDL_FALSE;
1103 else
1104 return mouseGrabbed;
1105}
1106
1107int Window::getWidth() const
1108{
1109 return windowWidth;
1110}
1111
1112int Window::getHeight() const
1113{
1114 return windowHeight;
1115}
1116
1117int Window::getPixelWidth() const
1118{
1119 return pixelWidth;
1120}
1121
1122int Window::getPixelHeight() const
1123{
1124 return pixelHeight;
1125}
1126
1127
1128void Window::windowToPixelCoords(double *x, double *y) const
1129{
1130 if (x != nullptr)
1131 *x = (*x) * ((double) pixelWidth / (double) windowWidth);
1132 if (y != nullptr)
1133 *y = (*y) * ((double) pixelHeight / (double) windowHeight);
1134}
1135
1136void Window::pixelToWindowCoords(double *x, double *y) const
1137{
1138 if (x != nullptr)
1139 *x = (*x) * ((double) windowWidth / (double) pixelWidth);
1140 if (y != nullptr)
1141 *y = (*y) * ((double) windowHeight / (double) pixelHeight);
1142}
1143
1144void Window::windowToDPICoords(double *x, double *y) const
1145{
1146 double px = x != nullptr ? *x : 0.0;
1147 double py = y != nullptr ? *y : 0.0;
1148
1149 windowToPixelCoords(&px, &py);
1150
1151 double dpix = 0.0;
1152 double dpiy = 0.0;
1153
1154 fromPixels(px, py, dpix, dpiy);
1155
1156 if (x != nullptr)
1157 *x = dpix;
1158 if (y != nullptr)
1159 *y = dpiy;
1160}
1161
1162void Window::DPIToWindowCoords(double *x, double *y) const
1163{
1164 double dpix = x != nullptr ? *x : 0.0;
1165 double dpiy = y != nullptr ? *y : 0.0;
1166
1167 double px = 0.0;
1168 double py = 0.0;
1169
1170 toPixels(dpix, dpiy, px, py);
1171 pixelToWindowCoords(&px, &py);
1172
1173 if (x != nullptr)
1174 *x = px;
1175 if (y != nullptr)
1176 *y = py;
1177}
1178
1179double Window::getDPIScale() const
1180{
1181 return settings.usedpiscale ? getNativeDPIScale() : 1.0;
1182}
1183
1184double Window::getNativeDPIScale() const
1185{
1186#ifdef LOVE_ANDROID
1187 return love::android::getScreenScale();
1188#else
1189 return (double) pixelHeight / (double) windowHeight;
1190#endif
1191}
1192
1193double Window::toPixels(double x) const
1194{
1195 return x * getDPIScale();
1196}
1197
1198void Window::toPixels(double wx, double wy, double &px, double &py) const
1199{
1200 double scale = getDPIScale();
1201 px = wx * scale;
1202 py = wy * scale;
1203}
1204
1205double Window::fromPixels(double x) const
1206{
1207 return x / getDPIScale();
1208}
1209
1210void Window::fromPixels(double px, double py, double &wx, double &wy) const
1211{
1212 double scale = getDPIScale();
1213 wx = px / scale;
1214 wy = py / scale;
1215}
1216
1217const void *Window::getHandle() const
1218{
1219 return window;
1220}
1221
1222SDL_MessageBoxFlags Window::convertMessageBoxType(MessageBoxType type) const
1223{
1224 switch (type)
1225 {
1226 case MESSAGEBOX_ERROR:
1227 return SDL_MESSAGEBOX_ERROR;
1228 case MESSAGEBOX_WARNING:
1229 return SDL_MESSAGEBOX_WARNING;
1230 case MESSAGEBOX_INFO:
1231 default:
1232 return SDL_MESSAGEBOX_INFORMATION;
1233 }
1234}
1235
1236bool Window::showMessageBox(const std::string &title, const std::string &message, MessageBoxType type, bool attachtowindow)
1237{
1238 SDL_MessageBoxFlags flags = convertMessageBoxType(type);
1239 SDL_Window *sdlwindow = attachtowindow ? window : nullptr;
1240
1241 return SDL_ShowSimpleMessageBox(flags, title.c_str(), message.c_str(), sdlwindow) >= 0;
1242}
1243
1244int Window::showMessageBox(const MessageBoxData &data)
1245{
1246 SDL_MessageBoxData sdldata = {};
1247
1248 sdldata.flags = convertMessageBoxType(data.type);
1249 sdldata.title = data.title.c_str();
1250 sdldata.message = data.message.c_str();
1251 sdldata.window = data.attachToWindow ? window : nullptr;
1252
1253 sdldata.numbuttons = (int) data.buttons.size();
1254
1255 std::vector<SDL_MessageBoxButtonData> sdlbuttons;
1256
1257 for (int i = 0; i < (int) data.buttons.size(); i++)
1258 {
1259 SDL_MessageBoxButtonData sdlbutton = {};
1260
1261 sdlbutton.buttonid = i;
1262 sdlbutton.text = data.buttons[i].c_str();
1263
1264 if (i == data.enterButtonIndex)
1265 sdlbutton.flags |= SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT;
1266
1267 if (i == data.escapeButtonIndex)
1268 sdlbutton.flags |= SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT;
1269
1270 sdlbuttons.push_back(sdlbutton);
1271 }
1272
1273 sdldata.buttons = &sdlbuttons[0];
1274
1275 int pressedbutton = -2;
1276 SDL_ShowMessageBox(&sdldata, &pressedbutton);
1277
1278 return pressedbutton;
1279}
1280
1281void Window::requestAttention(bool continuous)
1282{
1283#if defined(LOVE_WINDOWS) && !defined(LOVE_WINDOWS_UWP)
1284
1285 if (hasFocus())
1286 return;
1287
1288 SDL_SysWMinfo wminfo = {};
1289 SDL_VERSION(&wminfo.version);
1290
1291 if (SDL_GetWindowWMInfo(window, &wminfo))
1292 {
1293 FLASHWINFO flashinfo = {};
1294 flashinfo.cbSize = sizeof(FLASHWINFO);
1295 flashinfo.hwnd = wminfo.info.win.window;
1296 flashinfo.uCount = 1;
1297 flashinfo.dwFlags = FLASHW_ALL;
1298
1299 if (continuous)
1300 {
1301 flashinfo.uCount = 0;
1302 flashinfo.dwFlags |= FLASHW_TIMERNOFG;
1303 }
1304
1305 FlashWindowEx(&flashinfo);
1306 }
1307
1308#elif defined(LOVE_MACOSX)
1309
1310 love::macosx::requestAttention(continuous);
1311
1312#else
1313
1314 LOVE_UNUSED(continuous);
1315
1316#endif
1317
1318 // TODO: Linux?
1319}
1320
1321const char *Window::getName() const
1322{
1323 return "love.window.sdl";
1324}
1325
1326} // sdl
1327} // window
1328} // love
1329