Download
1647 lines 40.5 KiBC++Modified 2026-07-18 raw
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647
/**
 * Copyright (c) 2006-2026 LOVE Development Team
 *
 * This software is provided 'as-is', without any express or implied
 * warranty.  In no event will the authors be held liable for any damages
 * arising from the use of this software.
 *
 * Permission is granted to anyone to use this software for any purpose,
 * including commercial applications, and to alter it and redistribute it
 * freely, subject to the following restrictions:
 *
 * 1. The origin of this software must not be misrepresented; you must not
 *    claim that you wrote the original software. If you use this software
 *    in a product, an acknowledgment in the product documentation would be
 *    appreciated but is not required.
 * 2. Altered source versions must be plainly marked as such, and must not be
 *    misrepresented as being the original software.
 * 3. This notice may not be removed or altered from any source distribution.
 **/

// LOVE
#include "common/config.h"
#include "graphics/Graphics.h"
#ifdef LOVE_GRAPHICS_VULKAN
#	include "graphics/vulkan/Graphics.h"
#	include "graphics/vulkan/Vulkan.h"
#endif
#include "Window.h"
#include "filesystem/Filesystem.h"

#ifdef LOVE_ANDROID
#include "common/android.h"
#endif

#ifdef LOVE_IOS
#include "common/ios.h"
#endif

// C++
#include <iostream>
#include <vector>
#include <algorithm>

// C
#include <cstdio>

#ifdef LOVE_GRAPHICS_VULKAN
#include <SDL3/SDL_vulkan.h>
#endif

#if defined(LOVE_WINDOWS)
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <dwmapi.h>
#include <VersionHelpers.h>
#elif defined(LOVE_MACOS)
#include "common/macos.h"
#endif

#ifndef APIENTRY
#define APIENTRY
#endif

namespace love
{
namespace window
{

// See src/modules/window/Window.cpp.
void setHighDPIAllowedImplementation(bool enable)
{
	LOVE_UNUSED(enable);
}

namespace sdl
{

Window::Window()
	: love::window::Window("love.window.sdl")
	, open(false)
	, mouseGrabbed(false)
	, window(nullptr)
	, glcontext(nullptr)
#ifdef LOVE_GRAPHICS_METAL
	, metalView(nullptr)
#endif
	, contextAttribs()
{
	if (!SDL_InitSubSystem(SDL_INIT_VIDEO | SDL_INIT_EVENTS))
		throw love::Exception("Could not initialize SDL video subsystem (%s)", SDL_GetError());

	// Make sure the screensaver doesn't activate by default.
	setDisplaySleepEnabled(false);

#ifdef LOVE_WINDOWS
	// Turned off by default, because it (ironically) causes stuttering issues
	// on some setups. More investigation is needed before enabling it.
	canUseDwmFlush = SDL_GetHintBoolean("LOVE_GRAPHICS_VSYNC_DWM", false);
#endif

	dialogEventId = SDL_RegisterEvents(1);
}

Window::~Window()
{
	close(false);
	graphics.set(nullptr);
	SDL_QuitSubSystem(SDL_INIT_VIDEO | SDL_INIT_EVENTS);
}

void Window::setGraphics(graphics::Graphics *graphics)
{
	this->graphics.set(graphics);
}

void Window::setGLFramebufferAttributes(bool sRGB)
{
	// Set GL window / framebuffer attributes.
	SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8);
	SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8);
	SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8);
	SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 8);
	SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
	SDL_GL_SetAttribute(SDL_GL_RETAINED_BACKING, 0);

	// Always use 24/8 depth/stencil.
	// Changing this after initial window creation would need the context to be
	// destroyed and recreated, which we really don't want.
	SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
	SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8);

	// Backbuffer MSAA is handled by the love.graphics implementation.
	SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 0);
	SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 0);

	SDL_GL_SetAttribute(SDL_GL_FRAMEBUFFER_SRGB_CAPABLE, sRGB ? 1 : 0);

#if defined(LOVE_WINDOWS)
	// Avoid the Microsoft OpenGL 1.1 software renderer on Windows. Apparently
	// older Intel drivers like to use it as a fallback when requesting some
	// unsupported framebuffer attribute values, rather than properly failing.
	SDL_GL_SetAttribute(SDL_GL_ACCELERATED_VISUAL, 1);
#endif
}

void Window::setGLContextAttributes(const ContextAttribs &attribs)
{
	int profilemask = 0;
	int contextflags = 0;

	if (attribs.gles)
		profilemask = SDL_GL_CONTEXT_PROFILE_ES;
	else if (attribs.versionMajor * 10 + attribs.versionMinor >= 32)
		profilemask |= SDL_GL_CONTEXT_PROFILE_CORE;
	else if (attribs.debug)
		profilemask = SDL_GL_CONTEXT_PROFILE_COMPATIBILITY;

	if (attribs.debug)
		contextflags |= SDL_GL_CONTEXT_DEBUG_FLAG;

	SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, attribs.versionMajor);
	SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, attribs.versionMinor);
	SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, profilemask);
	SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, contextflags);
}

bool Window::checkGLVersion(const ContextAttribs &attribs, std::string &outversion)
{
	typedef unsigned char GLubyte;
	typedef unsigned int GLenum;
	typedef const GLubyte *(APIENTRY *glGetStringPtr)(GLenum name);
	const GLenum GL_VENDOR_ENUM   = 0x1F00;
	const GLenum GL_RENDERER_ENUM = 0x1F01;
	const GLenum GL_VERSION_ENUM  = 0x1F02;

	// We don't have OpenGL headers or an automatic OpenGL function loader in
	// this module, so we have to get the glGetString function pointer ourselves.
	glGetStringPtr glGetStringFunc = (glGetStringPtr) SDL_GL_GetProcAddress("glGetString");
	if (!glGetStringFunc)
		return false;

	const char *glversion = (const char *) glGetStringFunc(GL_VERSION_ENUM);
	if (!glversion)
		return false;

	outversion = glversion;

	const char *glrenderer = (const char *) glGetStringFunc(GL_RENDERER_ENUM);
	if (glrenderer)
		outversion += " - " + std::string(glrenderer);

	const char *glvendor = (const char *) glGetStringFunc(GL_VENDOR_ENUM);
	if (glvendor)
		outversion += " (" + std::string(glvendor) + ")";

	int glmajor = 0;
	int glminor = 0;

	// glGetString(GL_VERSION) returns a string with the format "major.minor",
	// or "OpenGL ES major.minor" in GLES contexts.
	const char *format = "%d.%d";
	if (attribs.gles)
		format = "OpenGL ES %d.%d";

	if (sscanf(glversion, format, &glmajor, &glminor) != 2)
		return false;

	if (glmajor < attribs.versionMajor
		|| (glmajor == attribs.versionMajor && glminor < attribs.versionMinor))
		return false;

	return true;
}

std::vector<Window::ContextAttribs> Window::getContextAttribsList() const
{
	// If we already have a set of context attributes that we know work, just
	// return that. love.graphics doesn't really support switching GL versions
	// after the first initialization.
	if (contextAttribs.versionMajor > 0)
		return std::vector<ContextAttribs>{contextAttribs};

	bool preferGLES = false;

#ifdef LOVE_GRAPHICS_USE_OPENGLES
	preferGLES = true;
#endif

	const char *curdriver = SDL_GetCurrentVideoDriver();
	const char *glesdrivers[] = {"RPI", "Android", "uikit", "winrt", "emscripten"};

	// We always want to try OpenGL ES first on certain video backends.
	for (const char *glesdriver : glesdrivers)
	{
		if (curdriver && strstr(curdriver, glesdriver) == curdriver)
		{
			preferGLES = true;
			break;
		}
	}

	const char *gleshint = SDL_GetHint("LOVE_GRAPHICS_USE_OPENGLES");
	if (gleshint != nullptr)
		preferGLES = (gleshint != nullptr && gleshint[0] != '0');

	// Do we want a debug context?
	bool debug = love::graphics::isDebugEnabled();

	const char *preferGL3hint = SDL_GetHint("LOVE_GRAPHICS_USE_GL3");
	bool preferGL3 = (preferGL3hint != nullptr && preferGL3hint[0] != '0');

	std::vector<ContextAttribs> glcontexts =
	{
		{4, 3, false, debug},
		{3, 3, false, debug},
	};

	std::vector<ContextAttribs> glescontexts =
	{
		{3, 2, true, debug},
		{3, 0, true, debug},
	};

	if (preferGL3)
	{
		std::swap(glcontexts[0], glcontexts[1]);
		std::swap(glescontexts[0], glescontexts[1]);
	}

	std::vector<ContextAttribs> attribslist;

	if (preferGLES)
	{
		attribslist.insert(attribslist.end(), glescontexts.begin(), glescontexts.end());
		attribslist.insert(attribslist.end(), glcontexts.begin(), glcontexts.end());
	}
	else
	{
		attribslist.insert(attribslist.end(), glcontexts.begin(), glcontexts.end());
		attribslist.insert(attribslist.end(), glescontexts.begin(), glescontexts.end());
	}

	return attribslist;
}

bool Window::createWindowAndContext(int x, int y, int w, int h, Uint32 windowflags, graphics::Renderer renderer)
{
	bool needsglcontext = (windowflags & SDL_WINDOW_OPENGL) != 0;
#ifdef LOVE_GRAPHICS_METAL
	bool needsmetalview = (windowflags & SDL_WINDOW_METAL) != 0;
#endif

	std::string windowerror;
	std::string contexterror;
	std::string glversion;

	// Unfortunately some OpenGL context settings are part of the internal
	// window state in the Windows and Linux SDL backends, so we have to
	// recreate the window when we want to change those settings...
	// Also, apparently some Intel drivers on Windows give back a Microsoft
	// OpenGL 1.1 software renderer context when high MSAA values are requested!

	const auto create = [&](const ContextAttribs *attribs) -> bool
	{
		if (glcontext)
		{
			SDL_GL_DestroyContext(glcontext);
			glcontext = nullptr;
		}

#ifdef LOVE_GRAPHICS_METAL
		if (metalView)
		{
			SDL_Metal_DestroyView(metalView);
			metalView = nullptr;
		}
#endif

		if (window)
		{
			SDL_DestroyWindow(window);
			SDL_FlushEvents(SDL_EVENT_WINDOW_FIRST, SDL_EVENT_WINDOW_LAST);
			window = nullptr;
		}

		window = SDL_CreateWindow(title.c_str(), w, h, windowflags);

		if (!window)
		{
			windowerror = std::string(SDL_GetError());
			return false;
		}

		SDL_SetWindowPosition(window, x, y);

		if (attribs != nullptr && renderer == love::graphics::Renderer::RENDERER_OPENGL)
		{
#ifdef LOVE_MACOS
			love::macos::setWindowSRGBColorSpace(window);
#endif

			glcontext = SDL_GL_CreateContext(window);

			if (!glcontext)
				contexterror = std::string(SDL_GetError());

			// Make sure the context's version is at least what we requested.
			if (glcontext && !checkGLVersion(*attribs, glversion))
			{
				SDL_GL_DestroyContext(glcontext);
				glcontext = nullptr;
			}

			if (!glcontext)
			{
				SDL_DestroyWindow(window);
				window = nullptr;
				return false;
			}
		}

		return true;
	};

	if (renderer == graphics::RENDERER_OPENGL)
	{
		std::vector<ContextAttribs> attribslist = getContextAttribsList();

		// Try each context profile in order.
		for (ContextAttribs attribs : attribslist)
		{
			bool curSRGB = love::graphics::isGammaCorrect();

			setGLFramebufferAttributes(curSRGB);
			setGLContextAttributes(attribs);

			windowerror.clear();
			contexterror.clear();

			create(&attribs);

			if (!window && curSRGB)
			{
				// The sRGB setting could have caused the failure.
				setGLFramebufferAttributes(false);
				if (create(&attribs))
					curSRGB = false;
			}

			if (window && glcontext)
			{
				// Store the successful context attributes so we can re-use them in
				// subsequent calls to createWindowAndContext.
				contextAttribs = attribs;
				love::graphics::setGammaCorrect(curSRGB);
				break;
			}
		}
	}
#ifdef LOVE_GRAPHICS_METAL
	else if (renderer == graphics::RENDERER_METAL)
	{
		if (create(nullptr) && window != nullptr)
			metalView = SDL_Metal_CreateView(window);

		if (metalView == nullptr && window != nullptr)
		{
			contexterror = SDL_GetError();
			SDL_DestroyWindow(window);
			window = nullptr;
		}
	}
#endif
	else
	{
		create(nullptr);
	}

	bool failed = window == nullptr;
	failed |= (needsglcontext && !glcontext);
#ifdef LOVE_GRAPHICS_METAL
	failed |= (needsmetalview && !metalView);
#endif

	if (failed)
	{
		std::string message = "This program requires a graphics card and video drivers which support Vulkan, OpenGL 3.3, or OpenGL ES 3.0.";

		if (!glversion.empty())
			message += "\n\nDetected OpenGL version:\n" + glversion;
		else if (!contexterror.empty())
			message += "\n\nRenderer context creation error: " + contexterror;
		else if (!windowerror.empty())
			message += "\n\nSDL window creation error: " + windowerror;

		close(false);

		throw love::Exception("Unable to create renderer.\n%s", message.c_str());
		return false;
	}

	open = true;
	return true;
}

struct SDLDisplayIDs
{
	SDLDisplayIDs()
	{
		ids = SDL_GetDisplays(&count);
	}

	~SDLDisplayIDs()
	{
		if (ids)
			SDL_free(ids);
	}

	int count = 0;
	SDL_DisplayID *ids = nullptr;
};

static SDL_DisplayID GetSDLDisplayIDForIndex(int displayindex)
{
	SDLDisplayIDs displayids;
	if (displayindex < 0 || displayindex >= displayids.count)
		return (SDL_DisplayID) 0;
	return displayids.ids[displayindex];
}

bool Window::setWindow(int width, int height, WindowSettings *settings)
{
	if (!graphics.get())
		graphics.set(Module::getInstance<graphics::Graphics>(Module::M_GRAPHICS));

	if (graphics.get() && graphics->isRenderTargetActive())
		throw love::Exception("love.window.setMode cannot be called while a render target is active in love.graphics.");

	auto renderer = graphics != nullptr ? graphics->getRenderer() : graphics::RENDERER_NONE;

	if (isOpen())
		updateSettings(this->settings, false);

	WindowSettings f;

	if (settings)
		f = *settings;

	f.minwidth = std::max(f.minwidth, 1);
	f.minheight = std::max(f.minheight, 1);

	SDLDisplayIDs displays;
	int displaycount = displays.count;

	f.displayindex = std::min(std::max(f.displayindex, 0), displaycount - 1);

	// Use the desktop resolution if a width or height of 0 is specified.
	if (width == 0 || height == 0)
	{
		const SDL_DisplayMode *mode = SDL_GetDesktopDisplayMode(displays.ids[f.displayindex]);
		width = mode->w;
		height = mode->h;
	}

	// On Android, disable fullscreen first on window creation so it's
	// possible to change the orientation by specifying portait width and
	// height, otherwise SDL will pick the current orientation dimensions when
	// fullscreen flag is set. Don't worry, we'll set it back later when user
	// also requested fullscreen after the window is created.
	// See https://github.com/love2d/love-android/issues/196
#ifdef LOVE_ANDROID
	bool fullscreen = f.fullscreen;

	f.fullscreen = false;
	f.fstype = FULLSCREEN_DESKTOP;
#endif

	int x = f.x;
	int y = f.y;

	if (f.useposition)
	{
		// The position needs to be in the global coordinate space.
		SDL_Rect displaybounds = {};
		SDL_GetDisplayBounds(displays.ids[f.displayindex], &displaybounds);
		x += displaybounds.x;
		y += displaybounds.y;
	}
	else
	{
		if (f.centered)
			x = y = SDL_WINDOWPOS_CENTERED_DISPLAY(displays.ids[f.displayindex]);
		else
			x = y = SDL_WINDOWPOS_UNDEFINED_DISPLAY(displays.ids[f.displayindex]);
	}

	Uint32 sdlflags = 0;
	SDL_DisplayMode fsmode = {};

	if (f.fullscreen)
	{
		sdlflags |= SDL_WINDOW_FULLSCREEN;

		if (f.fstype == FULLSCREEN_EXCLUSIVE)
		{
			SDL_DisplayID display = displays.ids[f.displayindex];
			if (!SDL_GetClosestFullscreenDisplayMode(display, width, height, 0, isHighDPIAllowed(), &fsmode))
			{
				// GetClosestDisplayMode will fail if we request a size larger
				// than the largest available display mode, so we'll try to use
				// the largest (first) mode in that case.
				int modecount = 0;
				SDL_DisplayMode **modes = SDL_GetFullscreenDisplayModes(display, &modecount);
				if (modecount > 0)
					fsmode = *modes[0];
				SDL_free(modes);
				if (fsmode.w == 0 || fsmode.h == 0)
					return false;
			}
		}
	}

	bool needsetmode = false;

	if (renderer != windowRenderer && isOpen())
		close();

	if (isOpen())
	{
		SDL_SetWindowMinimumSize(window, f.minwidth, f.minheight);

		if (fsmode.w > 0 && fsmode.h > 0)
			SDL_SetWindowFullscreenMode(window, &fsmode);
		else
			SDL_SetWindowFullscreenMode(window, nullptr);

		if (SDL_SetWindowFullscreen(window, (sdlflags & SDL_WINDOW_FULLSCREEN) != 0) && renderer == graphics::RENDERER_OPENGL)
			SDL_GL_MakeCurrent(window, glcontext);

		// TODO: should we make this conditional, to avoid love.resize events when the size doesn't change?
		SDL_SetWindowSize(window, width, height);

		if (this->settings.resizable != f.resizable)
			SDL_SetWindowResizable(window, f.resizable);

		if (this->settings.borderless != f.borderless)
			SDL_SetWindowBordered(window, !f.borderless);
	}
	else
	{
		if (renderer == graphics::RENDERER_OPENGL)
			sdlflags |= SDL_WINDOW_OPENGL;
	#ifdef LOVE_GRAPHICS_METAL
		if (renderer == graphics::RENDERER_METAL)
			sdlflags |= SDL_WINDOW_METAL;
	#endif

		if (renderer == graphics::RENDERER_VULKAN)
			sdlflags |= SDL_WINDOW_VULKAN;

		if (f.resizable)
			 sdlflags |= SDL_WINDOW_RESIZABLE;

		if (f.borderless)
			 sdlflags |= SDL_WINDOW_BORDERLESS;

		// Note: this flag is ignored on Windows.
		if (isHighDPIAllowed())
			sdlflags |= SDL_WINDOW_HIGH_PIXEL_DENSITY;

		Uint32 createflags = sdlflags & (~SDL_WINDOW_FULLSCREEN);

		if (!createWindowAndContext(x, y, width, height, createflags, renderer))
			return false;

		SDL_SetWindowMinimumSize(window, f.minwidth, f.minheight);

		if (f.fullscreen)
		{
			if (fsmode.w > 0 && fsmode.h > 0)
				SDL_SetWindowFullscreenMode(window, &fsmode);
			else
				SDL_SetWindowFullscreenMode(window, nullptr);
			SDL_SetWindowFullscreen(window, true);
		}

		needsetmode = true;
	}

	windowRenderer = renderer;

	// Make sure the window keeps any previously set icon.
	setIcon(icon.get());

	// Make sure the mouse keeps its previous grab setting.
	setMouseGrab(mouseGrabbed);

	if (this->settings.displayindex != f.displayindex || f.useposition || f.centered)
		SDL_SetWindowPosition(window, x, y);

	SDL_RaiseWindow(window);

	setVSync(f.vsync);

	updateSettings(f, false);

	if (graphics.get())
	{
		double scaledw, scaledh;
		fromPixels((double) pixelWidth, (double) pixelHeight, scaledw, scaledh);

		graphics::Graphics::BackbufferSettings backbufferSettings;
		backbufferSettings.width = (int)scaledw;
		backbufferSettings.height = (int)scaledh;
		backbufferSettings.pixelWidth = pixelWidth;
		backbufferSettings.pixelHeight = pixelHeight;
		backbufferSettings.stencil = f.stencil;
		backbufferSettings.depth = f.depth;
		backbufferSettings.msaa = f.msaa;

		if (needsetmode)
		{
			void *context = nullptr;
			if (renderer == graphics::RENDERER_OPENGL)
				context = (void *) glcontext;
#ifdef LOVE_GRAPHICS_METAL
			if (renderer == graphics::RENDERER_METAL && metalView)
				context = (void *) SDL_Metal_GetLayer(metalView);
#endif

			try
			{
				graphics->setMode(context, backbufferSettings);
			}
			catch (std::exception &e)
			{
				close(false);
				throw love::Exception("Failed to initialize graphics.\n%s", e.what());
			}
		}
		else
		{
			graphics->backbufferChanged(backbufferSettings);
		}

		this->settings.msaa = graphics->getBackbufferMSAA();
	}

	// Set fullscreen when user requested it before.
	// See above for explanation.
#ifdef LOVE_ANDROID
	setFullscreen(fullscreen);
	love::android::setImmersive(fullscreen);
#endif

	SDL_SyncWindow(window);

	return true;
}

bool Window::onSizeChanged(int width, int height)
{
	if (!window)
		return false;

	SDL_GetWindowSize(window, &windowWidth, &windowHeight);

	if (!SDL_GetWindowSizeInPixels(window, &pixelWidth, &pixelHeight))
	{
		pixelWidth = width;
		pixelHeight = height;
	}

	if (graphics.get())
	{
		double scaledw, scaledh;
		fromPixels((double) pixelWidth, (double) pixelHeight, scaledw, scaledh);
		graphics->backbufferChanged((int) scaledw, (int) scaledh, pixelWidth, pixelHeight);
	}

	return true;
}

void Window::updateSettings(const WindowSettings &newsettings, bool updateGraphicsViewport)
{
	SDL_SyncWindow(window);

	Uint32 wflags = SDL_GetWindowFlags(window);

	// Set the new display mode as the current display mode.
	SDL_GetWindowSize(window, &windowWidth, &windowHeight);

	pixelWidth = windowWidth;
	pixelHeight = windowHeight;

	SDL_GetWindowSizeInPixels(window, &pixelWidth, &pixelHeight);

	if (((wflags & SDL_WINDOW_FULLSCREEN) == SDL_WINDOW_FULLSCREEN) && SDL_GetWindowFullscreenMode(window) == nullptr)
	{
		settings.fullscreen = true;
		settings.fstype = FULLSCREEN_DESKTOP;
	}
	else if ((wflags & SDL_WINDOW_FULLSCREEN) == SDL_WINDOW_FULLSCREEN)
	{
		settings.fullscreen = true;
		settings.fstype = FULLSCREEN_EXCLUSIVE;
	}
	else
	{
		settings.fullscreen = false;
		settings.fstype = newsettings.fstype;
	}

#ifdef LOVE_ANDROID
	settings.fullscreen = love::android::getImmersive();
#endif

	// SDL_GetWindowMinimumSize gives back 0,0 sometimes...
	settings.minwidth = newsettings.minwidth;
	settings.minheight = newsettings.minheight;

	settings.resizable = (wflags & SDL_WINDOW_RESIZABLE) != 0;
	settings.borderless = (wflags & SDL_WINDOW_BORDERLESS) != 0;
	settings.centered = newsettings.centered;

	getPosition(settings.x, settings.y, settings.displayindex);

	setHighDPIAllowed((wflags & SDL_WINDOW_HIGH_PIXEL_DENSITY) != 0);

	settings.usedpiscale = newsettings.usedpiscale;

	settings.vsync = getVSync();

	settings.stencil = newsettings.stencil;
	settings.depth = newsettings.depth;

	SDLDisplayIDs displayids;
	const SDL_DisplayMode *dmode = SDL_GetCurrentDisplayMode(displayids.ids[settings.displayindex]);

	// May be 0 if the refresh rate can't be determined.
	settings.refreshrate = dmode->refresh_rate;

	// Update the viewport size now instead of waiting for event polling.
	if (updateGraphicsViewport && graphics.get())
	{
		double scaledw, scaledh;
		fromPixels((double) pixelWidth, (double) pixelHeight, scaledw, scaledh);
		graphics->backbufferChanged((int) scaledw, (int) scaledh, pixelWidth, pixelHeight);
	}
}

void Window::getWindow(int &width, int &height, WindowSettings &newsettings)
{
	// The window might have been modified (moved, resized, etc.) by the user.
	if (window)
		updateSettings(settings, true);

	width = windowWidth;
	height = windowHeight;
	newsettings = settings;
}

void Window::close()
{
	close(true);
}

void Window::close(bool allowExceptions)
{
	if (graphics.get())
	{
		if (allowExceptions && graphics->isRenderTargetActive())
			throw love::Exception("love.window.close cannot be called while a render target is active in love.graphics.");

		graphics->unSetMode();
	}

	if (glcontext)
	{
		SDL_GL_DestroyContext(glcontext);
		glcontext = nullptr;
	}

#ifdef LOVE_GRAPHICS_METAL
	if (metalView)
	{
		SDL_Metal_DestroyView(metalView);
		metalView = nullptr;
	}
#endif

	if (window)
	{
		SDL_DestroyWindow(window);
		window = nullptr;

		// The old window may have generated pending events which are no longer
		// relevant. Destroy them all!
		SDL_FlushEvents(SDL_EVENT_WINDOW_FIRST, SDL_EVENT_WINDOW_LAST);
	}

	open = false;
}

bool Window::setFullscreen(bool fullscreen, FullscreenType fstype)
{
	if (!window)
		return false;

	if (graphics.get() && graphics->isRenderTargetActive())
		throw love::Exception("love.window.setFullscreen cannot be called while a render target is active in love.graphics.");

	WindowSettings newsettings = settings;
	newsettings.fullscreen = fullscreen;
	newsettings.fstype = fstype;

	if (fullscreen)
	{
		if (fstype == FULLSCREEN_DESKTOP)
			SDL_SetWindowFullscreenMode(window, nullptr);
		else
		{
			SDL_DisplayID displayid = SDL_GetDisplayForWindow(window);
			const SDL_DisplayMode *mode = SDL_GetDesktopDisplayMode(displayid);
			if (mode != nullptr)
				SDL_SetWindowFullscreenMode(window, mode);
			else
				return false;
		}
	}

#ifdef LOVE_ANDROID
	love::android::setImmersive(fullscreen);
#endif

	if (SDL_SetWindowFullscreen(window, fullscreen))
	{
		if (glcontext)
			SDL_GL_MakeCurrent(window, glcontext);

		updateSettings(newsettings, true);
		return true;
	}

	return false;
}

bool Window::setFullscreen(bool fullscreen)
{
	return setFullscreen(fullscreen, settings.fstype);
}

int Window::getDisplayCount() const
{
	SDLDisplayIDs displayids;
	return displayids.count;
}

const char *Window::getDisplayName(int displayindex) const
{
	const char *name = SDL_GetDisplayName(GetSDLDisplayIDForIndex(displayindex));

	if (name == nullptr)
		throw love::Exception("Invalid display index: %d", displayindex + 1);

	return name;
}

Window::DisplayOrientation Window::getDisplayOrientation(int displayindex) const
{
	switch (SDL_GetCurrentDisplayOrientation(GetSDLDisplayIDForIndex(displayindex)))
	{
		case SDL_ORIENTATION_UNKNOWN: return ORIENTATION_UNKNOWN;
		case SDL_ORIENTATION_LANDSCAPE: return ORIENTATION_LANDSCAPE;
		case SDL_ORIENTATION_LANDSCAPE_FLIPPED: return ORIENTATION_LANDSCAPE_FLIPPED;
		case SDL_ORIENTATION_PORTRAIT: return ORIENTATION_PORTRAIT;
		case SDL_ORIENTATION_PORTRAIT_FLIPPED: return ORIENTATION_PORTRAIT_FLIPPED;
	}

	return ORIENTATION_UNKNOWN;
}

std::vector<Window::DisplayMode> Window::getFullscreenModes(int displayindex) const
{
	std::vector<DisplayMode> sizes;

	int count = 0;
	SDL_DisplayMode **modes = SDL_GetFullscreenDisplayModes(GetSDLDisplayIDForIndex(displayindex), &count);

	for (int i = 0; i < count; i++)
	{
		// TODO: other mode properties?
		double refreshrate = (double)modes[i]->refresh_rate_numerator / (double)modes[i]->refresh_rate_denominator;
		DisplayMode w = {modes[i]->w, modes[i]->h, refreshrate};

		// SDL2's display mode list has multiple entries for modes of the same
		// size with different bits per pixel, so we need to filter those out.
		if (std::find(sizes.begin(), sizes.end(), w) == sizes.end())
			sizes.push_back(w);
	}

	SDL_free(modes);

	return sizes;
}

void Window::getDesktopDimensions(int displayindex, int &width, int &height) const
{
	const SDL_DisplayMode *mode = SDL_GetDesktopDisplayMode(GetSDLDisplayIDForIndex(displayindex));
	if (mode != nullptr)
	{
		// TODO: other properties?
		width = mode->w;
		height = mode->h;
	}
	else
	{
		width = 0;
		height = 0;
	}
}

void Window::setPosition(int x, int y, int displayindex, bool waitForSync)
{
	if (!window)
		return;

	SDLDisplayIDs displayids;

	displayindex = std::min(std::max(displayindex, 0), displayids.count - 1);

	SDL_Rect displaybounds = {};
	SDL_GetDisplayBounds(displayids.ids[displayindex], &displaybounds);

	// The position needs to be in the global coordinate space.
	x += displaybounds.x;
	y += displaybounds.y;

	SDL_SetWindowPosition(window, x, y);
	if (waitForSync)
		SDL_SyncWindow(window);

	settings.useposition = true;
}

void Window::getPosition(int &x, int &y, int &displayindex)
{
	if (!window)
	{
		x = y =  0;
		displayindex = 0;
		return;
	}

	SDL_DisplayID displayid = SDL_GetDisplayForWindow(window);
	SDLDisplayIDs displayids;
	displayindex = 0;
	for (int i = 0; i < displayids.count; i++)
	{
		if (displayids.ids[i] == displayid)
		{
			displayindex = i;
			break;
		}
	}

	SDL_GetWindowPosition(window, &x, &y);

	// In SDL <= 2.0.3, fullscreen windows are always reported as 0,0. In every
	// other case we need to convert the position from global coordinates to the
	// monitor's coordinate space.
	if (x != 0 || y != 0)
	{
		SDL_Rect displaybounds = {};
		SDL_GetDisplayBounds(displayid, &displaybounds);

		x -= displaybounds.x;
		y -= displaybounds.y;
	}
}

Rect Window::getSafeArea() const
{
	SDL_Rect sdlrect = {};
	if (window != nullptr && SDL_GetWindowSafeArea(window, &sdlrect))
	{
		double x = sdlrect.x;
		double y = sdlrect.y;
		double w = sdlrect.w;
		double h = sdlrect.h;
		windowToDPICoords(&x, &y);
		windowToDPICoords(&w, &h);

		return {(int)x, (int)y, (int)w, (int)h};
	}

	double dw, dh;
	fromPixels(pixelWidth, pixelHeight, dw, dh);
	return {0, 0, (int) dw, (int) dh};
}

bool Window::isOpen() const
{
	return open;
}

void Window::setWindowTitle(const std::string &title)
{
	this->title = title;

	if (window)
		SDL_SetWindowTitle(window, title.c_str());
}

const std::string &Window::getWindowTitle() const
{
	return title;
}

bool Window::setIcon(love::image::ImageData *imgd)
{
	if (!imgd)
		return false;

	if (imgd->getFormat() != PIXELFORMAT_RGBA8_UNORM)
		throw love::Exception("setIcon only accepts 32-bit RGBA images.");

	icon.set(imgd);

	if (!window)
		return false;

	int w = imgd->getWidth();
	int h = imgd->getHeight();
	int bytesperpixel = (int) getPixelFormatBlockSize(imgd->getFormat());
	int pitch = w * bytesperpixel;

	SDL_Surface *sdlicon = SDL_CreateSurfaceFrom(w, h, SDL_PIXELFORMAT_ABGR8888, imgd->getData(), pitch);

	if (!sdlicon)
		return false;

	SDL_SetWindowIcon(window, sdlicon);
	SDL_DestroySurface(sdlicon);

	return true;
}

love::image::ImageData *Window::getIcon()
{
	return icon.get();
}

void Window::setVSync(int vsync)
{
	if (glcontext != nullptr)
	{
		SDL_GL_SetSwapInterval(vsync);

		// Check if adaptive vsync was requested but not supported, and fall
		// back to regular vsync if so.
		if (vsync == -1)
		{
			int actualvsync = 0;
			SDL_GL_GetSwapInterval(&actualvsync);
			if (actualvsync != -1)
				SDL_GL_SetSwapInterval(1);
		}
	}

#ifdef LOVE_GRAPHICS_VULKAN
	if (windowRenderer == love::graphics::RENDERER_VULKAN)
	{
		auto vgfx = dynamic_cast<love::graphics::vulkan::Graphics*>(graphics.get());
		vgfx->setVsync(vsync);
	}
#endif

#if defined(LOVE_GRAPHICS_METAL) && defined(LOVE_MACOS)
	if (metalView != nullptr)
	{
		void *metallayer = SDL_Metal_GetLayer(metalView);
		love::macos::setMetalLayerVSync(metallayer, vsync != 0);
	}
#endif
}

int Window::getVSync() const
{
	if (glcontext != nullptr)
	{
		int interval = 0;
		SDL_GL_GetSwapInterval(&interval);
		return interval;
	}

#if defined(LOVE_GRAPHICS_METAL)
	if (metalView != nullptr)
	{
#ifdef LOVE_MACOS
		void *metallayer = SDL_Metal_GetLayer(metalView);
		return love::macos::getMetalLayerVSync(metallayer) ? 1 : 0;
#else
		return 1;
#endif
	}
#endif

#ifdef LOVE_GRAPHICS_VULKAN
	if (windowRenderer == love::graphics::RENDERER_VULKAN)
	{
		auto vgfx = dynamic_cast<love::graphics::vulkan::Graphics*>(graphics.get());
		return vgfx->getVsync();
	}
#endif

	return 0;
}

void Window::setDisplaySleepEnabled(bool enable)
{
	if (enable)
		SDL_EnableScreenSaver();
	else
		SDL_DisableScreenSaver();
}

bool Window::isDisplaySleepEnabled() const
{
	return SDL_ScreenSaverEnabled();
}

void Window::minimize()
{
	if (window != nullptr)
	{
		SDL_MinimizeWindow(window);
		updateSettings(settings, true);
	}
}

void Window::maximize()
{
	if (window != nullptr)
	{
		SDL_MaximizeWindow(window);
		updateSettings(settings, true);
	}
}

void Window::restore()
{
	if (window != nullptr)
	{
		SDL_RestoreWindow(window);
		updateSettings(settings, true);
	}
}

void Window::focus()
{
	if (window != nullptr)
	{
		SDL_RaiseWindow(window);
		updateSettings(settings, true);
	}
}

bool Window::isMaximized() const
{
	return window != nullptr && (SDL_GetWindowFlags(window) & SDL_WINDOW_MAXIMIZED);
}

bool Window::isMinimized() const
{
	return window != nullptr && (SDL_GetWindowFlags(window) & SDL_WINDOW_MINIMIZED);
}

void Window::swapBuffers()
{
	if (glcontext)
	{
#ifdef LOVE_WINDOWS
		bool useDwmFlush = false;
		int swapInterval = getVSync();

		// https://github.com/love2d/love/issues/1628
		// VSync can interact badly with Windows desktop composition (DWM) in windowed mode. DwmFlush can be used instead
		// of vsync, but it's much less flexible so we're very conservative here with where it's used:
		// - It won't work with exclusive or desktop fullscreen.
		// - DWM refreshes don't always match the refresh rate of the monitor the window is in (or the requested swap
		//   interval), so we only use it when they do match.
		// - The user may force GL vsync, and DwmFlush shouldn't be used together with GL vsync.
		if (canUseDwmFlush && !settings.fullscreen && swapInterval == 1)
		{
			// Desktop composition is always enabled in Windows 8+. But DwmIsCompositionEnabled won't always return true...
			// (see DwmIsCompositionEnabled docs).
			BOOL compositionEnabled = IsWindows8OrGreater();
			if (compositionEnabled || (SUCCEEDED(DwmIsCompositionEnabled(&compositionEnabled)) && compositionEnabled))
			{
				DWM_TIMING_INFO info = {};
				info.cbSize = sizeof(DWM_TIMING_INFO);
				double dwmRefreshRate = 0;
				if (SUCCEEDED(DwmGetCompositionTimingInfo(nullptr, &info)))
					dwmRefreshRate = (double)info.rateRefresh.uiNumerator / (double)info.rateRefresh.uiDenominator;

				SDL_DisplayMode dmode = {};
				SDL_DisplayID display = SDL_GetDisplayForWindow(window);
				const SDL_DisplayMode* modePtr = SDL_GetCurrentDisplayMode(display);
				if (modePtr)
					dmode = *modePtr;

				if (dmode.refresh_rate > 0 && dwmRefreshRate > 0 && (fabs(dmode.refresh_rate - dwmRefreshRate) < 2))
				{
					SDL_GL_SetSwapInterval(0);
					int interval = 0;
					if (SDL_GL_GetSwapInterval(&interval) == 0 && interval == 0)
						useDwmFlush = true;
					else
						SDL_GL_SetSwapInterval(swapInterval);
				}
			}
		}
#endif

		SDL_GL_SwapWindow(window);

#ifdef LOVE_WINDOWS
		if (useDwmFlush)
		{
			DwmFlush();
			SDL_GL_SetSwapInterval(swapInterval);
		}
#endif
	}
}

bool Window::hasFocus() const
{
	return (window && SDL_GetKeyboardFocus() == window);
}

bool Window::hasMouseFocus() const
{
	return (window && SDL_GetMouseFocus() == window);
}

bool Window::isVisible() const
{
	return window && (SDL_GetWindowFlags(window) & (SDL_WINDOW_HIDDEN | SDL_WINDOW_MINIMIZED)) == 0;
}

bool Window::isOccluded() const
{
	return window && (SDL_GetWindowFlags(window) & SDL_WINDOW_OCCLUDED) != 0;
}

void Window::setMouseGrab(bool grab)
{
	mouseGrabbed = grab;
	if (window)
		SDL_SetWindowMouseGrab(window, grab);
}

bool Window::isMouseGrabbed() const
{
	if (window)
		return SDL_GetWindowMouseGrab(window);
	else
		return mouseGrabbed;
}

int Window::getWidth() const
{
	return windowWidth;
}

int Window::getHeight() const
{
	return windowHeight;
}

int Window::getPixelWidth() const
{
	return pixelWidth;
}

int Window::getPixelHeight() const
{
	return pixelHeight;
}

void Window::clampPositionInWindow(double *wx, double *wy) const
{
	if (wx != nullptr)
		*wx = std::min(std::max(0.0, *wx), (double) getWidth() - 1);
	if (wy != nullptr)
		*wy = std::min(std::max(0.0, *wy), (double) getHeight() - 1);
}

void Window::windowToPixelCoords(double *x, double *y) const
{
	if (x != nullptr)
		*x = (*x) * ((double) pixelWidth / (double) windowWidth);
	if (y != nullptr)
		*y = (*y) * ((double) pixelHeight / (double) windowHeight);
}

void Window::pixelToWindowCoords(double *x, double *y) const
{
	if (x != nullptr)
		*x = (*x) * ((double) windowWidth / (double) pixelWidth);
	if (y != nullptr)
		*y = (*y) * ((double) windowHeight / (double) pixelHeight);
}

void Window::windowToDPICoords(double *x, double *y) const
{
	double px = x != nullptr ? *x : 0.0;
	double py = y != nullptr ? *y : 0.0;

	windowToPixelCoords(&px, &py);

	double dpix = 0.0;
	double dpiy = 0.0;

	fromPixels(px, py, dpix, dpiy);

	if (x != nullptr)
		*x = dpix;
	if (y != nullptr)
		*y = dpiy;
}

void Window::DPIToWindowCoords(double *x, double *y) const
{
	double dpix = x != nullptr ? *x : 0.0;
	double dpiy = y != nullptr ? *y : 0.0;

	double px = 0.0;
	double py = 0.0;

	toPixels(dpix, dpiy, px, py);
	pixelToWindowCoords(&px, &py);

	if (x != nullptr)
		*x = px;
	if (y != nullptr)
		*y = py;
}

double Window::getDPIScale() const
{
	return settings.usedpiscale ? getNativeDPIScale() : 1.0;
}

double Window::getNativeDPIScale() const
{
#ifdef LOVE_ANDROID
	return love::android::getScreenScale();
#else
	return window != nullptr ? SDL_GetWindowDisplayScale(window) : 1.0;
#endif
}

double Window::toPixels(double x) const
{
	return x * getDPIScale();
}

void Window::toPixels(double wx, double wy, double &px, double &py) const
{
	double scale = getDPIScale();
	px = wx * scale;
	py = wy * scale;
}

double Window::fromPixels(double x) const
{
	return x / getDPIScale();
}

void Window::fromPixels(double px, double py, double &wx, double &wy) const
{
	double scale = getDPIScale();
	wx = px / scale;
	wy = py / scale;
}

void *Window::getHandle() const
{
	return window;
}

SDL_MessageBoxFlags Window::convertMessageBoxType(MessageBoxType type) const
{
	switch (type)
	{
	case MESSAGEBOX_ERROR:
		return SDL_MESSAGEBOX_ERROR;
	case MESSAGEBOX_WARNING:
		return SDL_MESSAGEBOX_WARNING;
	case MESSAGEBOX_INFO:
	default:
		return SDL_MESSAGEBOX_INFORMATION;
	}
}

bool Window::showMessageBox(const std::string &title, const std::string &message, MessageBoxType type, bool attachtowindow)
{
	SDL_MessageBoxFlags flags = convertMessageBoxType(type);
	SDL_Window *sdlwindow = attachtowindow ? window : nullptr;

	return SDL_ShowSimpleMessageBox(flags, title.c_str(), message.c_str(), sdlwindow);
}

int Window::showMessageBox(const MessageBoxData &data)
{
	SDL_MessageBoxData sdldata = {};

	sdldata.flags = convertMessageBoxType(data.type);
	sdldata.title = data.title.c_str();
	sdldata.message = data.message.c_str();
	sdldata.window = data.attachToWindow ? window : nullptr;

	sdldata.numbuttons = (int) data.buttons.size();

	std::vector<SDL_MessageBoxButtonData> sdlbuttons;

	for (int i = 0; i < (int) data.buttons.size(); i++)
	{
		SDL_MessageBoxButtonData sdlbutton = {};

		sdlbutton.buttonID = i;
		sdlbutton.text = data.buttons[i].c_str();

		if (i == data.enterButtonIndex)
			sdlbutton.flags |= SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT;

		if (i == data.escapeButtonIndex)
			sdlbutton.flags |= SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT;

		sdlbuttons.push_back(sdlbutton);
	}

	sdldata.buttons = &sdlbuttons[0];

	int pressedbutton = -2;
	SDL_ShowMessageBox(&sdldata, &pressedbutton);

	return pressedbutton;
}

// As of a SDL3 prerelease, a lot of SDL file dialog parameters need to persist
// until the callback completes, so we store them here.
// This is also used to retrieve some useful info to pass to love's own callback,
// and to send that along to SDL events (see below).
class FileDialogState : public love::Object
{
public:

	void *context;
	Window::FileDialogCallback callback;
	Uint32 dialogEventId;
	Window::FileDialogData data;
	std::vector<SDL_DialogFileFilter> sdlFilters;
	SDL_PropertiesID props;

	Optional<std::string> err;
	std::vector<std::string> files;
	int filterIndex;
};

static void SDLCALL fileDialogCallbackSDL(void *userdata, const char *const *filelist, int filter)
{
	auto state = (FileDialogState *) userdata;
	if (state == nullptr)
		return;

	if (filelist != nullptr)
	{
		// SDL's file list only lasts until the end of the callback, so we copy it.
		for (int i = 0; filelist[i] != nullptr; i++)
			state->files.push_back(filelist[i]);
	}
	else
	{
		state->err.set(SDL_GetError());
	}

	state->filterIndex = filter;

	SDL_DestroyProperties(state->props);

	// The SDL dialog callback isn't guaranteed to be called on the main thread,
	// whereas SDL event polling will happen there. This is needed because Lua states
	// aren't thread safe.
	SDL_Event event = {};
	event.type = state->dialogEventId;
	event.user.data1 = state;

	SDL_PushEvent(&event);
}

void Window::handleSDLEvent(const SDL_Event &event)
{
	if (event.type == dialogEventId)
	{
		// Releases itself when it goes out of scope.
		StrongRef<FileDialogState> state((FileDialogState *) event.user.data1, Acquire::NORETAIN);

		const char *filtername = state->filterIndex >= 0
			? state->data.filters[state->filterIndex].name.c_str()
			: nullptr;

		state->callback(state->context, state->files, filtername, state->err.hasValue ? state->err.value.c_str() : nullptr);
	}
}

void Window::showFileDialog(const FileDialogData &data, FileDialogCallback callback, void *context)
{
	SDL_FileDialogType sdltype = SDL_FILEDIALOG_OPENFILE;
	switch (data.type)
	{
	case FILEDIALOG_OPENFILE:
	default:
		sdltype = SDL_FILEDIALOG_OPENFILE;
		break;
	case FILEDIALOG_OPENFOLDER:
		sdltype = SDL_FILEDIALOG_OPENFOLDER;
		break;
	case FILEDIALOG_SAVEFILE:
		sdltype = SDL_FILEDIALOG_SAVEFILE;
		break;
	}

	auto state = new FileDialogState();
	state->callback = callback;
	state->context = context;
	state->dialogEventId = dialogEventId;
	state->data = data;

	for (const auto &filter : state->data.filters)
	{
		SDL_DialogFileFilter f = {};
		f.name = filter.name.c_str();
		f.pattern = filter.pattern.c_str();
		state->sdlFilters.push_back(f);
	}

	// We destroy this in the dialog callback, since it needs to persist until then (until that's fixed in SDL code).
	state->props = SDL_CreateProperties();

	if (!data.title.empty())
		SDL_SetStringProperty(state->props, SDL_PROP_FILE_DIALOG_TITLE_STRING, state->data.title.c_str());

	if (!data.acceptLabel.empty())
		SDL_SetStringProperty(state->props, SDL_PROP_FILE_DIALOG_ACCEPT_STRING, state->data.acceptLabel.c_str());

	if (!data.cancelLabel.empty())
		SDL_SetStringProperty(state->props, SDL_PROP_FILE_DIALOG_CANCEL_STRING, state->data.cancelLabel.c_str());

	if (!data.defaultName.empty())
		SDL_SetStringProperty(state->props, SDL_PROP_FILE_DIALOG_LOCATION_STRING, state->data.defaultName.c_str());

	if (data.attachToWindow)
		SDL_SetPointerProperty(state->props, SDL_PROP_FILE_DIALOG_WINDOW_POINTER, window);

	if (!state->sdlFilters.empty())
	{
		SDL_SetPointerProperty(state->props, SDL_PROP_FILE_DIALOG_FILTERS_POINTER, state->sdlFilters.data());
		SDL_SetNumberProperty(state->props, SDL_PROP_FILE_DIALOG_NFILTERS_NUMBER, state->sdlFilters.size());
	}

	SDL_SetBooleanProperty(state->props, SDL_PROP_FILE_DIALOG_MANY_BOOLEAN, data.multiSelect);

	SDL_ShowFileDialogWithProperties(sdltype, fileDialogCallbackSDL, state, state->props);
}

void Window::requestAttention(bool continuous)
{
	if (window != nullptr)
		SDL_FlashWindow(window, continuous ? SDL_FLASH_UNTIL_FOCUSED : SDL_FLASH_BRIEFLY);
}

Window::SystemTheme Window::getSystemTheme() const
{
	SDL_SystemTheme sdlstate = SDL_GetSystemTheme();
	SystemTheme state = THEME_UNKNOWN;
	systemThemes.find(sdlstate, state);

	return state;
}

EnumMap<Window::SystemTheme, SDL_SystemTheme, Window::THEME_MAX_ENUM>::Entry Window::systemThemeEntries[] =
{
	{Window::THEME_UNKNOWN, SDL_SYSTEM_THEME_UNKNOWN},
	{Window::THEME_LIGHT, SDL_SYSTEM_THEME_LIGHT},
	{Window::THEME_DARK, SDL_SYSTEM_THEME_DARK},
};

EnumMap<Window::SystemTheme, SDL_SystemTheme, Window::THEME_MAX_ENUM> Window::systemThemes(Window::systemThemeEntries, sizeof(Window::systemThemeEntries));


} // sdl
} // window
} // love