Browse Source

Initial source code support for SDL3. No build system support.

Sasha Szpakowski 1 year ago
parent
commit
62ac858f30

+ 8 - 2
src/common/delay.cpp

@@ -21,15 +21,21 @@
 #include "delay.h"
 #include "delay.h"
 
 
 #include <SDL_timer.h>
 #include <SDL_timer.h>
+#include <SDL_version.h>
 
 
 namespace love
 namespace love
 {
 {
 
 
-void sleep(unsigned int ms)
+// TODO: use ns.
+void sleep(double ms)
 {
 {
 	// We don't need to initialize the SDL timer subsystem for SDL_Delay to
 	// We don't need to initialize the SDL timer subsystem for SDL_Delay to
 	// function - and doing so causes SDL to create a worker thread.
 	// function - and doing so causes SDL to create a worker thread.
-	SDL_Delay(ms);
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+	SDL_DelayNS(SDL_MS_TO_NS(ms));
+#else
+	SDL_Delay((Uint32)ms);
+#endif
 }
 }
 
 
 } // love
 } // love

+ 1 - 1
src/common/delay.h

@@ -24,7 +24,7 @@
 namespace love
 namespace love
 {
 {
 
 
-void sleep(unsigned int ms);
+void sleep(double ms);
 
 
 } // namespace love
 } // namespace love
 
 

+ 15 - 4
src/common/macos.mm

@@ -26,12 +26,9 @@
 #import <Cocoa/Cocoa.h>
 #import <Cocoa/Cocoa.h>
 #import <QuartzCore/CAMetalLayer.h>
 #import <QuartzCore/CAMetalLayer.h>
 
 
-#ifdef LOVE_MACOSX_SDL_DIRECT_INCLUDE
 #include <SDL.h>
 #include <SDL.h>
+#if !SDL_VERSION_ATLEAST(3, 0, 0)
 #include <SDL_syswm.h>
 #include <SDL_syswm.h>
-#else
-#include <SDL2/SDL.h>
-#include <SDL2/SDL_syswm.h>
 #endif
 #endif
 
 
 namespace love
 namespace love
@@ -63,6 +60,13 @@ std::string checkDropEvents()
 	SDL_InitSubSystem(SDL_INIT_VIDEO);
 	SDL_InitSubSystem(SDL_INIT_VIDEO);
 
 
 	SDL_PumpEvents();
 	SDL_PumpEvents();
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+	if (SDL_PeepEvents(&event, 1, SDL_GETEVENT, SDL_EVENT_DROP_FILE, SDL_EVENT_DROP_FILE) > 0)
+	{
+		if (event.type == SDL_EVENT_DROP_FILE)
+			dropstr = std::string(event.drop.data);
+	}
+#else
 	if (SDL_PeepEvents(&event, 1, SDL_GETEVENT, SDL_DROPFILE, SDL_DROPFILE) > 0)
 	if (SDL_PeepEvents(&event, 1, SDL_GETEVENT, SDL_DROPFILE, SDL_DROPFILE) > 0)
 	{
 	{
 		if (event.type == SDL_DROPFILE)
 		if (event.type == SDL_DROPFILE)
@@ -71,6 +75,7 @@ std::string checkDropEvents()
 			SDL_free(event.drop.file);
 			SDL_free(event.drop.file);
 		}
 		}
 	}
 	}
+#endif
 
 
 	SDL_QuitSubSystem(SDL_INIT_VIDEO);
 	SDL_QuitSubSystem(SDL_INIT_VIDEO);
 
 
@@ -122,9 +127,15 @@ void setWindowSRGBColorSpace(SDL_Window *window)
 		// (at least, it was back when I tested in December 2016).
 		// (at least, it was back when I tested in December 2016).
 		if (@available(macOS 11.0, *))
 		if (@available(macOS 11.0, *))
 		{
 		{
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+			SDL_PropertiesID props = SDL_GetWindowProperties(window);
+			NSWindow *window = (__bridge NSWindow *) SDL_GetProperty(props, SDL_PROP_WINDOW_COCOA_WINDOW_POINTER, nullptr);
+			window.colorSpace = [NSColorSpace sRGBColorSpace];
+#else
 			SDL_SysWMinfo info = {};
 			SDL_SysWMinfo info = {};
 			if (SDL_GetWindowWMInfo(window, &info))
 			if (SDL_GetWindowWMInfo(window, &info))
 				info.info.cocoa.window.colorSpace = [NSColorSpace sRGBColorSpace];
 				info.info.cocoa.window.colorSpace = [NSColorSpace sRGBColorSpace];
+#endif
 		}
 		}
 	}
 	}
 }
 }

+ 225 - 98
src/modules/event/sdl/Event.cpp

@@ -24,7 +24,6 @@
 #include "filesystem/Filesystem.h"
 #include "filesystem/Filesystem.h"
 #include "keyboard/sdl/Keyboard.h"
 #include "keyboard/sdl/Keyboard.h"
 #include "joystick/JoystickModule.h"
 #include "joystick/JoystickModule.h"
-#include "joystick/sdl/Joystick.h"
 #include "touch/sdl/Touch.h"
 #include "touch/sdl/Touch.h"
 #include "graphics/Graphics.h"
 #include "graphics/Graphics.h"
 #include "window/Window.h"
 #include "window/Window.h"
@@ -38,6 +37,58 @@
 
 
 #include <SDL_version.h>
 #include <SDL_version.h>
 
 
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+#include "joystick/sdl/JoystickSDL3.h"
+#else
+#include "joystick/sdl/Joystick.h"
+#endif
+
+#if !SDL_VERSION_ATLEAST(3, 0, 0)
+#define SDL_EVENT_DID_ENTER_BACKGROUND SDL_APP_DIDENTERBACKGROUND
+#define SDL_EVENT_WILL_ENTER_FOREGROUND SDL_APP_WILLENTERFOREGROUND
+#define SDL_EVENT_KEY_DOWN SDL_KEYDOWN
+#define SDL_EVENT_KEY_UP SDL_KEYUP
+#define SDL_EVENT_TEXT_INPUT SDL_TEXTINPUT
+#define SDL_EVENT_TEXT_EDITING SDL_TEXTEDITING
+#define SDL_EVENT_MOUSE_MOTION SDL_MOUSEMOTION
+#define SDL_EVENT_MOUSE_BUTTON_DOWN SDL_MOUSEBUTTONDOWN
+#define SDL_EVENT_MOUSE_BUTTON_UP SDL_MOUSEBUTTONUP
+#define SDL_EVENT_MOUSE_WHEEL SDL_MOUSEWHEEL
+#define SDL_EVENT_FINGER_DOWN SDL_FINGERDOWN
+#define SDL_EVENT_FINGER_UP SDL_FINGERUP
+#define SDL_EVENT_FINGER_MOTION SDL_FINGERMOTION
+
+#define SDL_EVENT_DROP_FILE SDL_DROPFILE
+#define SDL_EVENT_QUIT SDL_QUIT
+#define SDL_EVENT_TERMINATING SDL_APP_TERMINATING
+#define SDL_EVENT_LOW_MEMORY SDL_APP_LOWMEMORY
+#define SDL_EVENT_LOCALE_CHANGED SDL_LOCALECHANGED
+#define SDL_EVENT_SENSOR_UPDATE SDL_SENSORUPDATE
+
+#define SDL_EVENT_JOYSTICK_BUTTON_DOWN SDL_JOYBUTTONDOWN
+#define SDL_EVENT_JOYSTICK_BUTTON_UP SDL_JOYBUTTONUP
+#define SDL_EVENT_JOYSTICK_AXIS_MOTION SDL_JOYAXISMOTION
+#define SDL_EVENT_JOYSTICK_HAT_MOTION SDL_JOYHATMOTION
+#define SDL_EVENT_JOYSTICK_ADDED SDL_JOYDEVICEADDED
+#define SDL_EVENT_JOYSTICK_REMOVED SDL_JOYDEVICEREMOVED
+#define SDL_EVENT_GAMEPAD_BUTTON_DOWN SDL_CONTROLLERBUTTONDOWN
+#define SDL_EVENT_GAMEPAD_BUTTON_UP SDL_CONTROLLERBUTTONUP
+#define SDL_EVENT_GAMEPAD_AXIS_MOTION SDL_CONTROLLERAXISMOTION
+#define SDL_EVENT_GAMEPAD_SENSOR_UPDATE SDL_CONTROLLERSENSORUPDATE
+
+#define SDL_EVENT_WINDOW_FOCUS_GAINED SDL_WINDOWEVENT_FOCUS_GAINED
+#define SDL_EVENT_WINDOW_FOCUS_LOST SDL_WINDOWEVENT_FOCUS_LOST
+#define SDL_EVENT_WINDOW_MOUSE_ENTER SDL_WINDOWEVENT_ENTER
+#define SDL_EVENT_WINDOW_MOUSE_LEAVE SDL_WINDOWEVENT_LEAVE
+#define SDL_EVENT_WINDOW_SHOWN SDL_WINDOWEVENT_SHOWN
+#define SDL_EVENT_WINDOW_HIDDEN SDL_WINDOWEVENT_HIDDEN
+#define SDL_EVENT_WINDOW_RESIZED SDL_WINDOWEVENT_RESIZED
+#define SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED SDL_WINDOWEVENT_SIZE_CHANGED
+#define SDL_EVENT_WINDOW_MINIMIZED SDL_WINDOWEVENT_MINIMIZED
+#define SDL_EVENT_WINDOW_RESTORED SDL_WINDOWEVENT_RESTORED
+
+#endif
+
 namespace love
 namespace love
 {
 {
 namespace event
 namespace event
@@ -93,10 +144,10 @@ static int SDLCALL watchAppEvents(void * /*udata*/, SDL_Event *event)
 	// On iOS, calling any OpenGL ES function after the function which triggers
 	// On iOS, calling any OpenGL ES function after the function which triggers
 	// SDL_APP_DIDENTERBACKGROUND is called will kill the app, so we handle it
 	// SDL_APP_DIDENTERBACKGROUND is called will kill the app, so we handle it
 	// with an event watch callback, which will be called inside that function.
 	// with an event watch callback, which will be called inside that function.
-	case SDL_APP_DIDENTERBACKGROUND:
-	case SDL_APP_WILLENTERFOREGROUND:
+	case SDL_EVENT_DID_ENTER_BACKGROUND:
+	case SDL_EVENT_WILL_ENTER_FOREGROUND:
 		if (gfx)
 		if (gfx)
-			gfx->setActive(event->type == SDL_APP_WILLENTERFOREGROUND);
+			gfx->setActive(event->type == SDL_EVENT_WILL_ENTER_FOREGROUND);
 		break;
 		break;
 	default:
 	default:
 		break;
 		break;
@@ -202,7 +253,7 @@ Message *Event::convert(const SDL_Event &e)
 
 
 	switch (e.type)
 	switch (e.type)
 	{
 	{
-	case SDL_KEYDOWN:
+	case SDL_EVENT_KEY_DOWN:
 		if (e.key.repeat)
 		if (e.key.repeat)
 		{
 		{
 			auto kb = Module::getInstance<love::keyboard::Keyboard>(Module::M_KEYBOARD);
 			auto kb = Module::getInstance<love::keyboard::Keyboard>(Module::M_KEYBOARD);
@@ -226,7 +277,7 @@ Message *Event::convert(const SDL_Event &e)
 		vargs.emplace_back(e.key.repeat != 0);
 		vargs.emplace_back(e.key.repeat != 0);
 		msg = new Message("keypressed", vargs);
 		msg = new Message("keypressed", vargs);
 		break;
 		break;
-	case SDL_KEYUP:
+	case SDL_EVENT_KEY_UP:
 		keyit = keys.find(e.key.keysym.sym);
 		keyit = keys.find(e.key.keysym.sym);
 		if (keyit != keys.end())
 		if (keyit != keys.end())
 			key = keyit->second;
 			key = keyit->second;
@@ -242,19 +293,19 @@ Message *Event::convert(const SDL_Event &e)
 		vargs.emplace_back(txt2, strlen(txt2));
 		vargs.emplace_back(txt2, strlen(txt2));
 		msg = new Message("keyreleased", vargs);
 		msg = new Message("keyreleased", vargs);
 		break;
 		break;
-	case SDL_TEXTINPUT:
+	case SDL_EVENT_TEXT_INPUT:
 		txt = e.text.text;
 		txt = e.text.text;
 		vargs.emplace_back(txt, strlen(txt));
 		vargs.emplace_back(txt, strlen(txt));
 		msg = new Message("textinput", vargs);
 		msg = new Message("textinput", vargs);
 		break;
 		break;
-	case SDL_TEXTEDITING:
+	case SDL_EVENT_TEXT_EDITING:
 		txt = e.edit.text;
 		txt = e.edit.text;
 		vargs.emplace_back(txt, strlen(txt));
 		vargs.emplace_back(txt, strlen(txt));
 		vargs.emplace_back((double) e.edit.start);
 		vargs.emplace_back((double) e.edit.start);
 		vargs.emplace_back((double) e.edit.length);
 		vargs.emplace_back((double) e.edit.length);
 		msg = new Message("textedited", vargs);
 		msg = new Message("textedited", vargs);
 		break;
 		break;
-	case SDL_MOUSEMOTION:
+	case SDL_EVENT_MOUSE_MOTION:
 		{
 		{
 			double x = (double) e.motion.x;
 			double x = (double) e.motion.x;
 			double y = (double) e.motion.y;
 			double y = (double) e.motion.y;
@@ -278,8 +329,8 @@ Message *Event::convert(const SDL_Event &e)
 			msg = new Message("mousemoved", vargs);
 			msg = new Message("mousemoved", vargs);
 		}
 		}
 		break;
 		break;
-	case SDL_MOUSEBUTTONDOWN:
-	case SDL_MOUSEBUTTONUP:
+	case SDL_EVENT_MOUSE_BUTTON_DOWN:
+	case SDL_EVENT_MOUSE_BUTTON_UP:
 		{
 		{
 			// SDL uses button 3 for the right mouse button, but we use button 2
 			// SDL uses button 3 for the right mouse button, but we use button 2
 			int button = e.button.button;
 			int button = e.button.button;
@@ -305,14 +356,14 @@ Message *Event::convert(const SDL_Event &e)
 			vargs.emplace_back(e.button.which == SDL_TOUCH_MOUSEID);
 			vargs.emplace_back(e.button.which == SDL_TOUCH_MOUSEID);
 			vargs.emplace_back((double) e.button.clicks);
 			vargs.emplace_back((double) e.button.clicks);
 
 
-			bool down = e.type == SDL_MOUSEBUTTONDOWN;
+			bool down = e.type == SDL_EVENT_MOUSE_BUTTON_DOWN;
 			msg = new Message(down ? "mousepressed" : "mousereleased", vargs);
 			msg = new Message(down ? "mousepressed" : "mousereleased", vargs);
 		}
 		}
 		break;
 		break;
-	case SDL_MOUSEWHEEL:
+	case SDL_EVENT_MOUSE_WHEEL:
 		vargs.emplace_back((double) e.wheel.x);
 		vargs.emplace_back((double) e.wheel.x);
 		vargs.emplace_back((double) e.wheel.y);
 		vargs.emplace_back((double) e.wheel.y);
-#if SDL_VERSION_ATLEAST(2, 0, 18)
+#if SDL_VERSION_ATLEAST(2, 0, 18) && !SDL_VERSION_ATLEAST(3, 0, 0)
 		// These values will be garbage if 2.0.18+ headers are used but a lower
 		// These values will be garbage if 2.0.18+ headers are used but a lower
 		// version of SDL is used at runtime, but other bits of code already
 		// version of SDL is used at runtime, but other bits of code already
 		// prevent running in that situation.
 		// prevent running in that situation.
@@ -328,9 +379,9 @@ Message *Event::convert(const SDL_Event &e)
 
 
 		msg = new Message("wheelmoved", vargs);
 		msg = new Message("wheelmoved", vargs);
 		break;
 		break;
-	case SDL_FINGERDOWN:
-	case SDL_FINGERUP:
-	case SDL_FINGERMOTION:
+	case SDL_EVENT_FINGER_DOWN:
+	case SDL_EVENT_FINGER_UP:
+	case SDL_EVENT_FINGER_MOTION:
 		// Touch events are disabled in OS X because we only actually want touch
 		// Touch events are disabled in OS X because we only actually want touch
 		// screen events, but most touch devices in OS X aren't touch screens
 		// screen events, but most touch devices in OS X aren't touch screens
 		// (and SDL doesn't differentiate.) Non-screen touch devices like Mac
 		// (and SDL doesn't differentiate.) Non-screen touch devices like Mac
@@ -364,35 +415,51 @@ Message *Event::convert(const SDL_Event &e)
 		vargs.emplace_back(touchinfo.dy);
 		vargs.emplace_back(touchinfo.dy);
 		vargs.emplace_back(touchinfo.pressure);
 		vargs.emplace_back(touchinfo.pressure);
 
 
-		if (e.type == SDL_FINGERDOWN)
+		if (e.type == SDL_EVENT_FINGER_DOWN)
 			txt = "touchpressed";
 			txt = "touchpressed";
-		else if (e.type == SDL_FINGERUP)
+		else if (e.type == SDL_EVENT_FINGER_UP)
 			txt = "touchreleased";
 			txt = "touchreleased";
 		else
 		else
 			txt = "touchmoved";
 			txt = "touchmoved";
 		msg = new Message(txt, vargs);
 		msg = new Message(txt, vargs);
 #endif
 #endif
 		break;
 		break;
-	case SDL_JOYBUTTONDOWN:
-	case SDL_JOYBUTTONUP:
-	case SDL_JOYAXISMOTION:
-	case SDL_JOYBALLMOTION:
-	case SDL_JOYHATMOTION:
-	case SDL_JOYDEVICEADDED:
-	case SDL_JOYDEVICEREMOVED:
-	case SDL_CONTROLLERBUTTONDOWN:
-	case SDL_CONTROLLERBUTTONUP:
-	case SDL_CONTROLLERAXISMOTION:
+	case SDL_EVENT_JOYSTICK_BUTTON_DOWN:
+	case SDL_EVENT_JOYSTICK_BUTTON_UP:
+	case SDL_EVENT_JOYSTICK_AXIS_MOTION:
+	case SDL_EVENT_JOYSTICK_HAT_MOTION:
+	case SDL_EVENT_JOYSTICK_ADDED:
+	case SDL_EVENT_JOYSTICK_REMOVED:
+	case SDL_EVENT_GAMEPAD_BUTTON_DOWN:
+	case SDL_EVENT_GAMEPAD_BUTTON_UP:
+	case SDL_EVENT_GAMEPAD_AXIS_MOTION:
 #if SDL_VERSION_ATLEAST(2, 0, 14) && defined(LOVE_ENABLE_SENSOR)
 #if SDL_VERSION_ATLEAST(2, 0, 14) && defined(LOVE_ENABLE_SENSOR)
-	case SDL_CONTROLLERSENSORUPDATE:
+	case SDL_EVENT_GAMEPAD_SENSOR_UPDATE:
 #endif
 #endif
 		msg = convertJoystickEvent(e);
 		msg = convertJoystickEvent(e);
 		break;
 		break;
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+	case SDL_EVENT_WINDOW_FOCUS_GAINED:
+	case SDL_EVENT_WINDOW_FOCUS_LOST:
+	case SDL_EVENT_WINDOW_MOUSE_ENTER:
+	case SDL_EVENT_WINDOW_MOUSE_LEAVE:
+	case SDL_EVENT_WINDOW_SHOWN:
+	case SDL_EVENT_WINDOW_HIDDEN:
+	case SDL_EVENT_WINDOW_RESIZED:
+	case SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED:
+	case SDL_EVENT_WINDOW_MINIMIZED:
+	case SDL_EVENT_WINDOW_RESTORED:
+#else
 	case SDL_WINDOWEVENT:
 	case SDL_WINDOWEVENT:
+#endif
 		msg = convertWindowEvent(e);
 		msg = convertWindowEvent(e);
 		break;
 		break;
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+	case SDL_EVENT_DISPLAY_ORIENTATION:
+#else
 	case SDL_DISPLAYEVENT:
 	case SDL_DISPLAYEVENT:
 		if (e.display.event == SDL_DISPLAYEVENT_ORIENTATION)
 		if (e.display.event == SDL_DISPLAYEVENT_ORIENTATION)
+#endif
 		{
 		{
 			auto orientation = window::Window::ORIENTATION_UNKNOWN;
 			auto orientation = window::Window::ORIENTATION_UNKNOWN;
 			switch ((SDL_DisplayOrientation) e.display.data1)
 			switch ((SDL_DisplayOrientation) e.display.data1)
@@ -418,47 +485,70 @@ Message *Event::convert(const SDL_Event &e)
 			if (!window::Window::getConstant(orientation, txt))
 			if (!window::Window::getConstant(orientation, txt))
 				txt = "unknown";
 				txt = "unknown";
 
 
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+			int count = 0;
+			int displayindex = 0;
+			SDL_DisplayID *displays = SDL_GetDisplays(&count);
+			for (int i = 0; i < count; i++)
+			{
+				if (displays[i] == e.display.displayID)
+				{
+					displayindex = i;
+					break;
+				}
+			}
+			SDL_free(displays);
+			vargs.emplace_back((double)(displayindex + 1));
+#else
 			vargs.emplace_back((double)(e.display.display + 1));
 			vargs.emplace_back((double)(e.display.display + 1));
+#endif
 			vargs.emplace_back(txt, strlen(txt));
 			vargs.emplace_back(txt, strlen(txt));
 
 
 			msg = new Message("displayrotated", vargs);
 			msg = new Message("displayrotated", vargs);
 		}
 		}
 		break;
 		break;
-	case SDL_DROPFILE:
+	case SDL_EVENT_DROP_FILE:
 		filesystem = Module::getInstance<filesystem::Filesystem>(Module::M_FILESYSTEM);
 		filesystem = Module::getInstance<filesystem::Filesystem>(Module::M_FILESYSTEM);
 		if (filesystem != nullptr)
 		if (filesystem != nullptr)
 		{
 		{
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+			const char *filepath = e.drop.data;
+#else
+			const char *filepath = e.drop.file;
+#endif
 			// Allow mounting any dropped path, so zips or dirs can be mounted.
 			// Allow mounting any dropped path, so zips or dirs can be mounted.
-			filesystem->allowMountingForPath(e.drop.file);
+			filesystem->allowMountingForPath(filepath);
 
 
-			if (filesystem->isRealDirectory(e.drop.file))
+			if (filesystem->isRealDirectory(filepath))
 			{
 			{
-				vargs.emplace_back(e.drop.file, strlen(e.drop.file));
+				vargs.emplace_back(filepath, strlen(filepath));
 				msg = new Message("directorydropped", vargs);
 				msg = new Message("directorydropped", vargs);
 			}
 			}
 			else
 			else
 			{
 			{
-				auto *file = new love::filesystem::NativeFile(e.drop.file, love::filesystem::File::MODE_CLOSED);
+				auto *file = new love::filesystem::NativeFile(filepath, love::filesystem::File::MODE_CLOSED);
 				vargs.emplace_back(&love::filesystem::NativeFile::type, file);
 				vargs.emplace_back(&love::filesystem::NativeFile::type, file);
 				msg = new Message("filedropped", vargs);
 				msg = new Message("filedropped", vargs);
 				file->release();
 				file->release();
 			}
 			}
 		}
 		}
+#if !SDL_VERSION_ATLEAST(3, 0, 0)
 		SDL_free(e.drop.file);
 		SDL_free(e.drop.file);
+#endif
 		break;
 		break;
-	case SDL_QUIT:
-	case SDL_APP_TERMINATING:
+	case SDL_EVENT_QUIT:
+	case SDL_EVENT_TERMINATING:
 		msg = new Message("quit");
 		msg = new Message("quit");
 		break;
 		break;
-	case SDL_APP_LOWMEMORY:
+	case SDL_EVENT_LOW_MEMORY:
 		msg = new Message("lowmemory");
 		msg = new Message("lowmemory");
 		break;
 		break;
 #if SDL_VERSION_ATLEAST(2, 0, 14)
 #if SDL_VERSION_ATLEAST(2, 0, 14)
-	case SDL_LOCALECHANGED:
+	case SDL_EVENT_LOCALE_CHANGED:
 		msg = new Message("localechanged");
 		msg = new Message("localechanged");
 		break;
 		break;
 #endif
 #endif
-	case SDL_SENSORUPDATE:
+	case SDL_EVENT_SENSOR_UPDATE:
 		sensorInstance = Module::getInstance<sensor::Sensor>(M_SENSOR);
 		sensorInstance = Module::getInstance<sensor::Sensor>(M_SENSOR);
 		if (sensorInstance)
 		if (sensorInstance)
 		{
 		{
@@ -467,13 +557,22 @@ Message *Event::convert(const SDL_Event &e)
 			for (void *s: sensors)
 			for (void *s: sensors)
 			{
 			{
 				SDL_Sensor *sensor = (SDL_Sensor *) s;
 				SDL_Sensor *sensor = (SDL_Sensor *) s;
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+				SDL_SensorID id = SDL_GetSensorInstanceID(sensor);
+#else
 				SDL_SensorID id = SDL_SensorGetInstanceID(sensor);
 				SDL_SensorID id = SDL_SensorGetInstanceID(sensor);
+#endif
 
 
 				if (e.sensor.which == id)
 				if (e.sensor.which == id)
 				{
 				{
 					// Found sensor
 					// Found sensor
 					const char *sensorType;
 					const char *sensorType;
-					if (!sensor::Sensor::getConstant(sensor::sdl::Sensor::convert(SDL_SensorGetType(sensor)), sensorType))
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+					auto sdltype = SDL_GetSensorType(sensor);
+#else
+					auto sdltype = SDL_SensorGetType(sensor);
+#endif
+					if (!sensor::Sensor::getConstant(sensor::sdl::Sensor::convert(sdltype), sensorType))
 						sensorType = "unknown";
 						sensorType = "unknown";
 
 
 					vargs.emplace_back(sensorType, strlen(sensorType));
 					vargs.emplace_back(sensorType, strlen(sensorType));
@@ -516,19 +615,19 @@ Message *Event::convertJoystickEvent(const SDL_Event &e) const
 
 
 	switch (e.type)
 	switch (e.type)
 	{
 	{
-	case SDL_JOYBUTTONDOWN:
-	case SDL_JOYBUTTONUP:
+	case SDL_EVENT_JOYSTICK_BUTTON_DOWN:
+	case SDL_EVENT_JOYSTICK_BUTTON_UP:
 		stick = joymodule->getJoystickFromID(e.jbutton.which);
 		stick = joymodule->getJoystickFromID(e.jbutton.which);
 		if (!stick)
 		if (!stick)
 			break;
 			break;
 
 
 		vargs.emplace_back(joysticktype, stick);
 		vargs.emplace_back(joysticktype, stick);
 		vargs.emplace_back((double)(e.jbutton.button+1));
 		vargs.emplace_back((double)(e.jbutton.button+1));
-		msg = new Message((e.type == SDL_JOYBUTTONDOWN) ?
+		msg = new Message((e.type == SDL_EVENT_JOYSTICK_BUTTON_DOWN) ?
 						  "joystickpressed" : "joystickreleased",
 						  "joystickpressed" : "joystickreleased",
 						  vargs);
 						  vargs);
 		break;
 		break;
-	case SDL_JOYAXISMOTION:
+	case SDL_EVENT_JOYSTICK_AXIS_MOTION:
 		{
 		{
 			stick = joymodule->getJoystickFromID(e.jaxis.which);
 			stick = joymodule->getJoystickFromID(e.jaxis.which);
 			if (!stick)
 			if (!stick)
@@ -541,7 +640,7 @@ Message *Event::convertJoystickEvent(const SDL_Event &e) const
 			msg = new Message("joystickaxis", vargs);
 			msg = new Message("joystickaxis", vargs);
 		}
 		}
 		break;
 		break;
-	case SDL_JOYHATMOTION:
+	case SDL_EVENT_JOYSTICK_HAT_MOTION:
 		if (!joystick::sdl::Joystick::getConstant(e.jhat.value, hat) || !joystick::Joystick::getConstant(hat, txt))
 		if (!joystick::sdl::Joystick::getConstant(e.jhat.value, hat) || !joystick::Joystick::getConstant(hat, txt))
 			break;
 			break;
 
 
@@ -554,41 +653,59 @@ Message *Event::convertJoystickEvent(const SDL_Event &e) const
 		vargs.emplace_back(txt, strlen(txt));
 		vargs.emplace_back(txt, strlen(txt));
 		msg = new Message("joystickhat", vargs);
 		msg = new Message("joystickhat", vargs);
 		break;
 		break;
-	case SDL_CONTROLLERBUTTONDOWN:
-	case SDL_CONTROLLERBUTTONUP:
-		if (!joystick::sdl::Joystick::getConstant((SDL_GameControllerButton) e.cbutton.button, padbutton))
-			break;
+	case SDL_EVENT_GAMEPAD_BUTTON_DOWN:
+	case SDL_EVENT_GAMEPAD_BUTTON_UP:
+		{
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+			const auto &b = e.gbutton;
+			if (!joystick::sdl::Joystick::getConstant((SDL_GamepadButton) b.button, padbutton))
+#else
+			const auto &b = e.cbutton;
+			if (!joystick::sdl::Joystick::getConstant((SDL_GameControllerButton) b.button, padbutton))
+#endif
+				break;
 
 
-		if (!joystick::Joystick::getConstant(padbutton, txt))
-			break;
+			if (!joystick::Joystick::getConstant(padbutton, txt))
+				break;
 
 
-		stick = joymodule->getJoystickFromID(e.cbutton.which);
-		if (!stick)
-			break;
+			stick = joymodule->getJoystickFromID(b.which);
+			if (!stick)
+				break;
 
 
-		vargs.emplace_back(joysticktype, stick);
-		vargs.emplace_back(txt, strlen(txt));
-		msg = new Message(e.type == SDL_CONTROLLERBUTTONDOWN ?
-						  "gamepadpressed" : "gamepadreleased", vargs);
+			vargs.emplace_back(joysticktype, stick);
+			vargs.emplace_back(txt, strlen(txt));
+			msg = new Message(e.type == SDL_EVENT_GAMEPAD_BUTTON_DOWN ?
+							  "gamepadpressed" : "gamepadreleased", vargs);
+		}
 		break;
 		break;
-	case SDL_CONTROLLERAXISMOTION:
+	case SDL_EVENT_GAMEPAD_AXIS_MOTION:
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+		if (joystick::sdl::Joystick::getConstant((SDL_GamepadAxis) e.gaxis.axis, padaxis))
+#else
 		if (joystick::sdl::Joystick::getConstant((SDL_GameControllerAxis) e.caxis.axis, padaxis))
 		if (joystick::sdl::Joystick::getConstant((SDL_GameControllerAxis) e.caxis.axis, padaxis))
+#endif
 		{
 		{
 			if (!joystick::Joystick::getConstant(padaxis, txt))
 			if (!joystick::Joystick::getConstant(padaxis, txt))
 				break;
 				break;
 
 
-			stick = joymodule->getJoystickFromID(e.caxis.which);
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+			const auto &a = e.gaxis;
+#else
+			const auto &a = e.caxis;
+#endif
+
+			stick = joymodule->getJoystickFromID(a.which);
 			if (!stick)
 			if (!stick)
 				break;
 				break;
 
 
 			vargs.emplace_back(joysticktype, stick);
 			vargs.emplace_back(joysticktype, stick);
 			vargs.emplace_back(txt, strlen(txt));
 			vargs.emplace_back(txt, strlen(txt));
-			float value = joystick::Joystick::clampval(e.caxis.value / 32768.0f);
+			float value = joystick::Joystick::clampval(a.value / 32768.0f);
 			vargs.emplace_back((double) value);
 			vargs.emplace_back((double) value);
 			msg = new Message("gamepadaxis", vargs);
 			msg = new Message("gamepadaxis", vargs);
 		}
 		}
 		break;
 		break;
-	case SDL_JOYDEVICEADDED:
+	case SDL_EVENT_JOYSTICK_ADDED:
 		// jdevice.which is the joystick device index.
 		// jdevice.which is the joystick device index.
 		stick = joymodule->addJoystick(e.jdevice.which);
 		stick = joymodule->addJoystick(e.jdevice.which);
 		if (stick)
 		if (stick)
@@ -597,7 +714,7 @@ Message *Event::convertJoystickEvent(const SDL_Event &e) const
 			msg = new Message("joystickadded", vargs);
 			msg = new Message("joystickadded", vargs);
 		}
 		}
 		break;
 		break;
-	case SDL_JOYDEVICEREMOVED:
+	case SDL_EVENT_JOYSTICK_REMOVED:
 		// jdevice.which is the joystick instance ID now.
 		// jdevice.which is the joystick instance ID now.
 		stick = joymodule->getJoystickFromID(e.jdevice.which);
 		stick = joymodule->getJoystickFromID(e.jdevice.which);
 		if (stick)
 		if (stick)
@@ -608,23 +725,30 @@ Message *Event::convertJoystickEvent(const SDL_Event &e) const
 		}
 		}
 		break;
 		break;
 #if SDL_VERSION_ATLEAST(2, 0, 14) && defined(LOVE_ENABLE_SENSOR)
 #if SDL_VERSION_ATLEAST(2, 0, 14) && defined(LOVE_ENABLE_SENSOR)
-	case SDL_CONTROLLERSENSORUPDATE:
-		stick = joymodule->getJoystickFromID(e.csensor.which);
-		if (stick)
+	case SDL_EVENT_GAMEPAD_SENSOR_UPDATE:
 		{
 		{
-			using Sensor = love::sensor::Sensor;
-
-			const char *sensorName;
-			Sensor::SensorType sensorType = love::sensor::sdl::Sensor::convert((SDL_SensorType) e.csensor.sensor);
-			if (!Sensor::getConstant(sensorType, sensorName))
-				sensorName = "unknown";
-
-			vargs.emplace_back(joysticktype, stick);
-			vargs.emplace_back(sensorName, strlen(sensorName));
-			vargs.emplace_back(e.csensor.data[0]);
-			vargs.emplace_back(e.csensor.data[1]);
-			vargs.emplace_back(e.csensor.data[2]);
-			msg = new Message("joysticksensorupdated", vargs);
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+			const auto &sens = e.gsensor;
+#else
+			const auto &sens = e.csensor;
+#endif
+			stick = joymodule->getJoystickFromID(sens.which);
+			if (stick)
+			{
+				using Sensor = love::sensor::Sensor;
+
+				const char *sensorName;
+				Sensor::SensorType sensorType = love::sensor::sdl::Sensor::convert((SDL_SensorType) sens.sensor);
+				if (!Sensor::getConstant(sensorType, sensorName))
+					sensorName = "unknown";
+
+				vargs.emplace_back(joysticktype, stick);
+				vargs.emplace_back(sensorName, strlen(sensorName));
+				vargs.emplace_back(sens.data[0]);
+				vargs.emplace_back(sens.data[1]);
+				vargs.emplace_back(sens.data[2]);
+				msg = new Message("joysticksensorupdated", vargs);
+			}
 		}
 		}
 		break;
 		break;
 #endif // SDL_VERSION_ATLEAST(2, 0, 14) && defined(LOVE_ENABLE_SENSOR)
 #endif // SDL_VERSION_ATLEAST(2, 0, 14) && defined(LOVE_ENABLE_SENSOR)
@@ -645,27 +769,30 @@ Message *Event::convertWindowEvent(const SDL_Event &e)
 	window::Window *win = nullptr;
 	window::Window *win = nullptr;
 	graphics::Graphics *gfx = nullptr;
 	graphics::Graphics *gfx = nullptr;
 
 
-	if (e.type != SDL_WINDOWEVENT)
-		return nullptr;
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+	auto event = e.type;
+#else
+	auto event = e.window.event;
+#endif
 
 
-	switch (e.window.event)
+	switch (event)
 	{
 	{
-	case SDL_WINDOWEVENT_FOCUS_GAINED:
-	case SDL_WINDOWEVENT_FOCUS_LOST:
-		vargs.emplace_back(e.window.event == SDL_WINDOWEVENT_FOCUS_GAINED);
+	case SDL_EVENT_WINDOW_FOCUS_GAINED:
+	case SDL_EVENT_WINDOW_FOCUS_LOST:
+		vargs.emplace_back(event == SDL_EVENT_WINDOW_FOCUS_GAINED);
 		msg = new Message("focus", vargs);
 		msg = new Message("focus", vargs);
 		break;
 		break;
-	case SDL_WINDOWEVENT_ENTER:
-	case SDL_WINDOWEVENT_LEAVE:
-		vargs.emplace_back(e.window.event == SDL_WINDOWEVENT_ENTER);
+	case SDL_EVENT_WINDOW_MOUSE_ENTER:
+	case SDL_EVENT_WINDOW_MOUSE_LEAVE:
+		vargs.emplace_back(event == SDL_EVENT_WINDOW_MOUSE_ENTER);
 		msg = new Message("mousefocus", vargs);
 		msg = new Message("mousefocus", vargs);
 		break;
 		break;
-	case SDL_WINDOWEVENT_SHOWN:
-	case SDL_WINDOWEVENT_HIDDEN:
-		vargs.emplace_back(e.window.event == SDL_WINDOWEVENT_SHOWN);
+	case SDL_EVENT_WINDOW_SHOWN:
+	case SDL_EVENT_WINDOW_HIDDEN:
+		vargs.emplace_back(event == SDL_EVENT_WINDOW_SHOWN);
 		msg = new Message("visible", vargs);
 		msg = new Message("visible", vargs);
 		break;
 		break;
-	case SDL_WINDOWEVENT_RESIZED:
+	case SDL_EVENT_WINDOW_RESIZED:
 		{
 		{
 			double width  = e.window.data1;
 			double width  = e.window.data1;
 			double height = e.window.data2;
 			double height = e.window.data2;
@@ -693,19 +820,19 @@ Message *Event::convertWindowEvent(const SDL_Event &e)
 			msg = new Message("resize", vargs);
 			msg = new Message("resize", vargs);
 		}
 		}
 		break;
 		break;
-	case SDL_WINDOWEVENT_SIZE_CHANGED:
+	case SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED:
 		win = Module::getInstance<window::Window>(Module::M_WINDOW);
 		win = Module::getInstance<window::Window>(Module::M_WINDOW);
 		if (win)
 		if (win)
 			win->onSizeChanged(e.window.data1, e.window.data2);
 			win->onSizeChanged(e.window.data1, e.window.data2);
 		break;
 		break;
-	case SDL_WINDOWEVENT_MINIMIZED:
-	case SDL_WINDOWEVENT_RESTORED:
+	case SDL_EVENT_WINDOW_MINIMIZED:
+	case SDL_EVENT_WINDOW_RESTORED:
 #ifdef LOVE_ANDROID
 #ifdef LOVE_ANDROID
 		if (auto audio = Module::getInstance<audio::Audio>(Module::M_AUDIO))
 		if (auto audio = Module::getInstance<audio::Audio>(Module::M_AUDIO))
 		{
 		{
-			if (e.window.event == SDL_WINDOWEVENT_MINIMIZED)
+			if (event == SDL_EVENT_WINDOW_MINIMIZED)
 				audio->pauseContext();
 				audio->pauseContext();
-			else if (e.window.event == SDL_WINDOWEVENT_RESTORED)
+			else if (event == SDL_EVENT_WINDOW_RESTORED)
 				audio->resumeContext();
 				audio->resumeContext();
 		}
 		}
 #endif
 #endif

+ 7 - 1
src/modules/filesystem/wrap_Filesystem.cpp

@@ -35,6 +35,7 @@
 
 
 // SDL
 // SDL
 #include <SDL_loadso.h>
 #include <SDL_loadso.h>
+#include <SDL_version.h>
 
 
 // STL
 // STL
 #include <vector>
 #include <vector>
@@ -987,7 +988,12 @@ int extloader(lua_State *L)
 
 
 	// We look for both loveopen_ and luaopen_, so libraries with specific love support
 	// We look for both loveopen_ and luaopen_, so libraries with specific love support
 	// can tell when they've been loaded by love.
 	// can tell when they've been loaded by love.
-	void *func = SDL_LoadFunction(handle, ("loveopen_" + tokenized_function).c_str());
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+	SDL_FunctionPointer func = nullptr;
+#else
+	void *func = nullptr;
+#endif
+	func = SDL_LoadFunction(handle, ("loveopen_" + tokenized_function).c_str());
 	if (!func)
 	if (!func)
 		func = SDL_LoadFunction(handle, ("luaopen_" + tokenized_function).c_str());
 		func = SDL_LoadFunction(handle, ("luaopen_" + tokenized_function).c_str());
 
 

+ 1 - 1
src/modules/graphics/opengl/OpenGL.cpp

@@ -62,7 +62,7 @@ static void *LOVEGetProcAddress(const char *name)
 		return proc;
 		return proc;
 #endif
 #endif
 
 
-	return SDL_GL_GetProcAddress(name);
+	return (void *) SDL_GL_GetProcAddress(name);
 }
 }
 
 
 OpenGL::TempDebugGroup::TempDebugGroup(const char *name)
 OpenGL::TempDebugGroup::TempDebugGroup(const char *name)

+ 3 - 2
src/modules/joystick/Joystick.h

@@ -24,6 +24,7 @@
 // LOVE
 // LOVE
 #include "common/Object.h"
 #include "common/Object.h"
 #include "common/StringMap.h"
 #include "common/StringMap.h"
+#include "common/int.h"
 #include "sensor/Sensor.h"
 #include "sensor/Sensor.h"
 
 
 // stdlib
 // stdlib
@@ -172,7 +173,7 @@ public:
 
 
 	virtual ~Joystick() {}
 	virtual ~Joystick() {}
 
 
-	virtual bool open(int deviceindex) = 0;
+	virtual bool open(int64 deviceid) = 0;
 	virtual void close() = 0;
 	virtual void close() = 0;
 
 
 	virtual bool isConnected() const = 0;
 	virtual bool isConnected() const = 0;
@@ -194,7 +195,7 @@ public:
 	virtual void setPlayerIndex(int index) = 0;
 	virtual void setPlayerIndex(int index) = 0;
 	virtual int getPlayerIndex() const = 0;
 	virtual int getPlayerIndex() const = 0;
 
 
-	virtual bool openGamepad(int deviceindex) = 0;
+	virtual bool openGamepad(int64 deviceid) = 0;
 	virtual bool isGamepad() const = 0;
 	virtual bool isGamepad() const = 0;
 
 
 	virtual GamepadType getGamepadType() const = 0;
 	virtual GamepadType getGamepadType() const = 0;

+ 1 - 1
src/modules/joystick/JoystickModule.h

@@ -43,7 +43,7 @@ public:
 	 * Adds a connected Joystick device and opens it for use.
 	 * Adds a connected Joystick device and opens it for use.
 	 * Returns NULL if the Joystick could not be added.
 	 * Returns NULL if the Joystick could not be added.
 	 **/
 	 **/
-	virtual Joystick *addJoystick(int deviceindex) = 0;
+	virtual Joystick *addJoystick(int64 deviceid) = 0;
 
 
 	/**
 	/**
 	 * Removes a disconnected Joystick device.
 	 * Removes a disconnected Joystick device.

+ 10 - 2
src/modules/joystick/sdl/Joystick.cpp

@@ -31,6 +31,8 @@
 #include <algorithm>
 #include <algorithm>
 #include <limits>
 #include <limits>
 
 
+#if !SDL_VERSION_ATLEAST(3, 0, 0)
+
 #ifndef SDL_TICKS_PASSED
 #ifndef SDL_TICKS_PASSED
 #define SDL_TICKS_PASSED(A, B)  ((Sint32)((B) - (A)) <= 0)
 #define SDL_TICKS_PASSED(A, B)  ((Sint32)((B) - (A)) <= 0)
 #endif
 #endif
@@ -69,8 +71,10 @@ Joystick::~Joystick()
 	close();
 	close();
 }
 }
 
 
-bool Joystick::open(int deviceindex)
+bool Joystick::open(int64 deviceid)
 {
 {
+	int deviceindex = (int) deviceid;
+
 	close();
 	close();
 
 
 	joyhandle = SDL_JoystickOpen(deviceindex);
 	joyhandle = SDL_JoystickOpen(deviceindex);
@@ -263,8 +267,10 @@ int Joystick::getPlayerIndex() const
 #endif
 #endif
 }
 }
 
 
-bool Joystick::openGamepad(int deviceindex)
+bool Joystick::openGamepad(int64 deviceid)
 {
 {
+	int deviceindex = (int) deviceid;
+
 	if (!SDL_IsGameController(deviceindex))
 	if (!SDL_IsGameController(deviceindex))
 		return false;
 		return false;
 
 
@@ -852,3 +858,5 @@ EnumMap<Joystick::GamepadButton, SDL_GameControllerButton, Joystick::GAMEPAD_BUT
 } // sdl
 } // sdl
 } // joystick
 } // joystick
 } // love
 } // love
+
+#endif // !SDL_VERSION_ATLEAST(3, 0, 0)

+ 6 - 2
src/modules/joystick/sdl/Joystick.h

@@ -28,6 +28,8 @@
 // SDL
 // SDL
 #include <SDL.h>
 #include <SDL.h>
 
 
+#if !SDL_VERSION_ATLEAST(3, 0, 0)
+
 namespace love
 namespace love
 {
 {
 namespace joystick
 namespace joystick
@@ -44,7 +46,7 @@ public:
 
 
 	virtual ~Joystick();
 	virtual ~Joystick();
 
 
-	bool open(int deviceindex) override;
+	bool open(int64 deviceid) override;
 	void close() override;
 	void close() override;
 
 
 	bool isConnected() const override;
 	bool isConnected() const override;
@@ -66,7 +68,7 @@ public:
 	void setPlayerIndex(int index) override;
 	void setPlayerIndex(int index) override;
 	int getPlayerIndex() const override;
 	int getPlayerIndex() const override;
 
 
-	bool openGamepad(int deviceindex) override;
+	bool openGamepad(int64 deviceid) override;
 	bool isGamepad() const override;
 	bool isGamepad() const override;
 
 
 	GamepadType getGamepadType() const override;
 	GamepadType getGamepadType() const override;
@@ -155,4 +157,6 @@ private:
 } // joystick
 } // joystick
 } // love
 } // love
 
 
+#endif // !SDL_VERSION_ATLEAST(3, 0, 0)
+
 #endif // LOVE_JOYSTICK_SDL_JOYSTICK_H
 #endif // LOVE_JOYSTICK_SDL_JOYSTICK_H

+ 113 - 12
src/modules/joystick/sdl/JoystickModule.cpp

@@ -21,6 +21,7 @@
 #include "common/config.h"
 #include "common/config.h"
 #include "JoystickModule.h"
 #include "JoystickModule.h"
 #include "Joystick.h"
 #include "Joystick.h"
+#include "JoystickSDL3.h"
 
 
 // SDL
 // SDL
 #include <SDL.h>
 #include <SDL.h>
@@ -41,10 +42,26 @@ namespace sdl
 
 
 JoystickModule::JoystickModule()
 JoystickModule::JoystickModule()
 {
 {
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+	if (SDL_InitSubSystem(SDL_INIT_JOYSTICK | SDL_INIT_GAMEPAD) < 0)
+#else
 	if (SDL_InitSubSystem(SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER) < 0)
 	if (SDL_InitSubSystem(SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER) < 0)
+#endif
 		throw love::Exception("Could not initialize SDL joystick subsystem (%s)", SDL_GetError());
 		throw love::Exception("Could not initialize SDL joystick subsystem (%s)", SDL_GetError());
 
 
 	// Initialize any joysticks which are already connected.
 	// Initialize any joysticks which are already connected.
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+	int count = 0;
+	SDL_JoystickID *sticks = SDL_GetJoysticks(&count);
+	for (int i = 0; i < count; i++)
+		addJoystick((int64) sticks[i]);
+	SDL_free(sticks);
+
+	// Start joystick event watching. Joysticks are automatically added and
+	// removed via love.event.
+	SDL_SetJoystickEventsEnabled(SDL_TRUE);
+	SDL_SetGamepadEventsEnabled(SDL_TRUE);
+#else
 	for (int i = 0; i < SDL_NumJoysticks(); i++)
 	for (int i = 0; i < SDL_NumJoysticks(); i++)
 		addJoystick(i);
 		addJoystick(i);
 
 
@@ -52,6 +69,7 @@ JoystickModule::JoystickModule()
 	// removed via love.event.
 	// removed via love.event.
 	SDL_JoystickEventState(SDL_ENABLE);
 	SDL_JoystickEventState(SDL_ENABLE);
 	SDL_GameControllerEventState(SDL_ENABLE);
 	SDL_GameControllerEventState(SDL_ENABLE);
+#endif
 }
 }
 
 
 JoystickModule::~JoystickModule()
 JoystickModule::~JoystickModule()
@@ -66,7 +84,11 @@ JoystickModule::~JoystickModule()
 	if (SDL_WasInit(SDL_INIT_HAPTIC) != 0)
 	if (SDL_WasInit(SDL_INIT_HAPTIC) != 0)
 		SDL_QuitSubSystem(SDL_INIT_HAPTIC);
 		SDL_QuitSubSystem(SDL_INIT_HAPTIC);
 
 
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+	SDL_QuitSubSystem(SDL_INIT_JOYSTICK | SDL_INIT_GAMEPAD);
+#else
 	SDL_QuitSubSystem(SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER);
 	SDL_QuitSubSystem(SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER);
+#endif
 }
 }
 
 
 const char *JoystickModule::getName() const
 const char *JoystickModule::getName() const
@@ -110,12 +132,16 @@ love::joystick::Joystick *JoystickModule::getJoystickFromID(int instanceid)
 	return nullptr;
 	return nullptr;
 }
 }
 
 
-love::joystick::Joystick *JoystickModule::addJoystick(int deviceindex)
+love::joystick::Joystick *JoystickModule::addJoystick(int64 deviceid)
 {
 {
-	if (deviceindex < 0 || deviceindex >= SDL_NumJoysticks())
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+	if (deviceid == 0)
+#else
+	if (deviceid < 0 || (int) deviceid >= SDL_NumJoysticks())
+#endif
 		return nullptr;
 		return nullptr;
 
 
-	std::string guidstr = getDeviceGUID(deviceindex);
+	std::string guidstr = getDeviceGUID(deviceid);
 	joystick::Joystick *joystick = 0;
 	joystick::Joystick *joystick = 0;
 	bool reused = false;
 	bool reused = false;
 
 
@@ -132,14 +158,18 @@ love::joystick::Joystick *JoystickModule::addJoystick(int deviceindex)
 
 
 	if (!joystick)
 	if (!joystick)
 	{
 	{
+#if SDL_VERSION_ATLEAST(3, 0, 0)
 		joystick = new Joystick((int) joysticks.size());
 		joystick = new Joystick((int) joysticks.size());
+#else
+		joystick = new Joystick((int) joysticks.size());
+#endif
 		joysticks.push_back(joystick);
 		joysticks.push_back(joystick);
 	}
 	}
 
 
 	// Make sure the Joystick object isn't in the active list already.
 	// Make sure the Joystick object isn't in the active list already.
 	removeJoystick(joystick);
 	removeJoystick(joystick);
 
 
-	if (!joystick->open(deviceindex))
+	if (!joystick->open(deviceid))
 		return nullptr;
 		return nullptr;
 
 
 	// Make sure multiple instances of the same physical joystick aren't added
 	// Make sure multiple instances of the same physical joystick aren't added
@@ -188,10 +218,17 @@ bool JoystickModule::setGamepadMapping(const std::string &guid, Joystick::Gamepa
 	if (guid.length() != 32)
 	if (guid.length() != 32)
 		throw love::Exception("Invalid joystick GUID: %s", guid.c_str());
 		throw love::Exception("Invalid joystick GUID: %s", guid.c_str());
 
 
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+	SDL_JoystickGUID sdlguid = SDL_GetJoystickGUIDFromString(guid.c_str());
+	std::string mapstr;
+
+	char *sdlmapstr = SDL_GetGamepadMappingForGUID(sdlguid);
+#else
 	SDL_JoystickGUID sdlguid = SDL_JoystickGetGUIDFromString(guid.c_str());
 	SDL_JoystickGUID sdlguid = SDL_JoystickGetGUIDFromString(guid.c_str());
 	std::string mapstr;
 	std::string mapstr;
 
 
 	char *sdlmapstr = SDL_GameControllerMappingForGUID(sdlguid);
 	char *sdlmapstr = SDL_GameControllerMappingForGUID(sdlguid);
+#endif
 	if (sdlmapstr)
 	if (sdlmapstr)
 	{
 	{
 		mapstr = sdlmapstr;
 		mapstr = sdlmapstr;
@@ -273,7 +310,11 @@ bool JoystickModule::setGamepadMapping(const std::string &guid, Joystick::Gamepa
 	}
 	}
 
 
 	// 1 == added, 0 == updated, -1 == error.
 	// 1 == added, 0 == updated, -1 == error.
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+	int status = SDL_AddGamepadMapping(mapstr.c_str());
+#else
 	int status = SDL_GameControllerAddMapping(mapstr.c_str());
 	int status = SDL_GameControllerAddMapping(mapstr.c_str());
+#endif
 
 
 	if (status != -1)
 	if (status != -1)
 		recentGamepadGUIDs[guid] = true;
 		recentGamepadGUIDs[guid] = true;
@@ -288,8 +329,13 @@ bool JoystickModule::setGamepadMapping(const std::string &guid, Joystick::Gamepa
 
 
 std::string JoystickModule::stringFromGamepadInput(Joystick::GamepadInput gpinput) const
 std::string JoystickModule::stringFromGamepadInput(Joystick::GamepadInput gpinput) const
 {
 {
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+	SDL_GamepadAxis sdlaxis;
+	SDL_GamepadButton sdlbutton;
+#else
 	SDL_GameControllerAxis sdlaxis;
 	SDL_GameControllerAxis sdlaxis;
 	SDL_GameControllerButton sdlbutton;
 	SDL_GameControllerButton sdlbutton;
+#endif
 
 
 	const char *gpinputname = nullptr;
 	const char *gpinputname = nullptr;
 
 
@@ -297,11 +343,19 @@ std::string JoystickModule::stringFromGamepadInput(Joystick::GamepadInput gpinpu
 	{
 	{
 	case Joystick::INPUT_TYPE_AXIS:
 	case Joystick::INPUT_TYPE_AXIS:
 		if (Joystick::getConstant(gpinput.axis, sdlaxis))
 		if (Joystick::getConstant(gpinput.axis, sdlaxis))
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+			gpinputname = SDL_GetGamepadStringForAxis(sdlaxis);
+#else
 			gpinputname = SDL_GameControllerGetStringForAxis(sdlaxis);
 			gpinputname = SDL_GameControllerGetStringForAxis(sdlaxis);
+#endif
 		break;
 		break;
 	case Joystick::INPUT_TYPE_BUTTON:
 	case Joystick::INPUT_TYPE_BUTTON:
 		if (Joystick::getConstant(gpinput.button, sdlbutton))
 		if (Joystick::getConstant(gpinput.button, sdlbutton))
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+			gpinputname = SDL_GetGamepadStringForButton(sdlbutton);
+#else
 			gpinputname = SDL_GameControllerGetStringForButton(sdlbutton);
 			gpinputname = SDL_GameControllerGetStringForButton(sdlbutton);
+#endif
 		break;
 		break;
 	default:
 	default:
 		break;
 		break;
@@ -351,12 +405,25 @@ void JoystickModule::checkGamepads(const std::string &guid) const
 
 
 	// Make sure all connected joysticks of a certain guid that are
 	// Make sure all connected joysticks of a certain guid that are
 	// gamepad-capable are opened as such.
 	// gamepad-capable are opened as such.
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+	int count = 0;
+	SDL_JoystickID *sdlsticks = SDL_GetJoysticks(&count);
+	for (int d_index = 0; d_index < count; d_index++)
+	{
+		if (!SDL_IsGamepad(sdlsticks[d_index]))
+			continue;
+
+		auto sdlid = sdlsticks[d_index];
+#else
 	for (int d_index = 0; d_index < SDL_NumJoysticks(); d_index++)
 	for (int d_index = 0; d_index < SDL_NumJoysticks(); d_index++)
 	{
 	{
 		if (!SDL_IsGameController(d_index))
 		if (!SDL_IsGameController(d_index))
 			continue;
 			continue;
 
 
-		if (guid.compare(getDeviceGUID(d_index)) != 0)
+		auto sdlid = d_index;
+#endif
+
+		if (guid.compare(getDeviceGUID(sdlid)) != 0)
 			continue;
 			continue;
 
 
 		for (auto stick : activeSticks)
 		for (auto stick : activeSticks)
@@ -366,6 +433,17 @@ void JoystickModule::checkGamepads(const std::string &guid) const
 
 
 			// Big hack time: open the index as a game controller and compare
 			// Big hack time: open the index as a game controller and compare
 			// the underlying joystick handle to the active stick's.
 			// the underlying joystick handle to the active stick's.
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+			SDL_Gamepad *controller = SDL_OpenGamepad(sdlid);
+			if (controller == nullptr)
+				continue;
+
+			// GameController objects are reference-counted in SDL, so we don't want to
+			// have a joystick open when trying to re-initialize it
+			SDL_Joystick *sdlstick = SDL_GetGamepadJoystick(controller);
+			bool open_gamepad = (sdlstick == (SDL_Joystick *) stick->getHandle());
+			SDL_CloseGamepad(controller);
+#else
 			SDL_GameController *controller = SDL_GameControllerOpen(d_index);
 			SDL_GameController *controller = SDL_GameControllerOpen(d_index);
 			if (controller == nullptr)
 			if (controller == nullptr)
 				continue;
 				continue;
@@ -375,25 +453,36 @@ void JoystickModule::checkGamepads(const std::string &guid) const
 			SDL_Joystick *sdlstick = SDL_GameControllerGetJoystick(controller);
 			SDL_Joystick *sdlstick = SDL_GameControllerGetJoystick(controller);
 			bool open_gamepad = (sdlstick == (SDL_Joystick *) stick->getHandle());
 			bool open_gamepad = (sdlstick == (SDL_Joystick *) stick->getHandle());
 			SDL_GameControllerClose(controller);
 			SDL_GameControllerClose(controller);
+#endif
 
 
 			// open as gamepad if necessary
 			// open as gamepad if necessary
 			if (open_gamepad)
 			if (open_gamepad)
-				stick->openGamepad(d_index);
+				stick->openGamepad(sdlid);
 		}
 		}
 	}
 	}
 }
 }
 
 
-std::string JoystickModule::getDeviceGUID(int deviceindex) const
+std::string JoystickModule::getDeviceGUID(int64 deviceid) const
 {
 {
-	if (deviceindex < 0 || deviceindex >= SDL_NumJoysticks())
-		return std::string("");
-
 	// SDL_JoystickGetGUIDString uses 32 bytes plus the null terminator.
 	// SDL_JoystickGetGUIDString uses 32 bytes plus the null terminator.
 	char guidstr[33] = {'\0'};
 	char guidstr[33] = {'\0'};
 
 
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+	if (deviceid <= 0)
+		return std::string("");
+
+	// SDL's GUIDs identify *classes* of devices, instead of unique devices.
+	SDL_JoystickGUID guid = SDL_GetJoystickInstanceGUID((SDL_JoystickID)deviceid);
+	SDL_GetJoystickGUIDString(guid, guidstr, sizeof(guidstr));
+#else
+	int deviceindex = (int) deviceid;
+	if (deviceindex < 0 || deviceindex >= SDL_NumJoysticks())
+		return std::string("");
+
 	// SDL2's GUIDs identify *classes* of devices, instead of unique devices.
 	// SDL2's GUIDs identify *classes* of devices, instead of unique devices.
 	SDL_JoystickGUID guid = SDL_JoystickGetDeviceGUID(deviceindex);
 	SDL_JoystickGUID guid = SDL_JoystickGetDeviceGUID(deviceindex);
 	SDL_JoystickGetGUIDString(guid, guidstr, sizeof(guidstr));
 	SDL_JoystickGetGUIDString(guid, guidstr, sizeof(guidstr));
+#endif
 
 
 	return std::string(guidstr);
 	return std::string(guidstr);
 }
 }
@@ -438,7 +527,11 @@ void JoystickModule::loadGamepadMappings(const std::string &mappings)
 			mapping.erase(pstartpos, pendpos - pstartpos + 1);
 			mapping.erase(pstartpos, pendpos - pstartpos + 1);
 		}
 		}
 
 
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+		if (SDL_AddGamepadMapping(mapping.c_str()) != -1)
+#else
 		if (SDL_GameControllerAddMapping(mapping.c_str()) != -1)
 		if (SDL_GameControllerAddMapping(mapping.c_str()) != -1)
+#endif
 		{
 		{
 			success = true;
 			success = true;
 			std::string guid = mapping.substr(0, mapping.find_first_of(','));
 			std::string guid = mapping.substr(0, mapping.find_first_of(','));
@@ -458,9 +551,13 @@ void JoystickModule::loadGamepadMappings(const std::string &mappings)
 
 
 std::string JoystickModule::getGamepadMappingString(const std::string &guid) const
 std::string JoystickModule::getGamepadMappingString(const std::string &guid) const
 {
 {
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+	SDL_JoystickGUID sdlguid = SDL_GetJoystickGUIDFromString(guid.c_str());
+	char *sdlmapping = SDL_GetGamepadMappingForGUID(sdlguid);
+#else
 	SDL_JoystickGUID sdlguid = SDL_JoystickGetGUIDFromString(guid.c_str());
 	SDL_JoystickGUID sdlguid = SDL_JoystickGetGUIDFromString(guid.c_str());
-
 	char *sdlmapping = SDL_GameControllerMappingForGUID(sdlguid);
 	char *sdlmapping = SDL_GameControllerMappingForGUID(sdlguid);
+#endif
 	if (sdlmapping == nullptr)
 	if (sdlmapping == nullptr)
 		return "";
 		return "";
 
 
@@ -483,9 +580,13 @@ std::string JoystickModule::saveGamepadMappings()
 
 
 	for (const auto &g : recentGamepadGUIDs)
 	for (const auto &g : recentGamepadGUIDs)
 	{
 	{
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+		SDL_JoystickGUID sdlguid = SDL_GetJoystickGUIDFromString(g.first.c_str());
+		char *sdlmapping = SDL_GetGamepadMappingForGUID(sdlguid);
+#else
 		SDL_JoystickGUID sdlguid = SDL_JoystickGetGUIDFromString(g.first.c_str());
 		SDL_JoystickGUID sdlguid = SDL_JoystickGetGUIDFromString(g.first.c_str());
-
 		char *sdlmapping = SDL_GameControllerMappingForGUID(sdlguid);
 		char *sdlmapping = SDL_GameControllerMappingForGUID(sdlguid);
+#endif
 		if (sdlmapping == nullptr)
 		if (sdlmapping == nullptr)
 			continue;
 			continue;
 
 

+ 2 - 2
src/modules/joystick/sdl/JoystickModule.h

@@ -48,7 +48,7 @@ public:
 	const char *getName() const override;
 	const char *getName() const override;
 
 
 	// Implements JoystickModule.
 	// Implements JoystickModule.
-	love::joystick::Joystick *addJoystick(int deviceindex) override;
+	love::joystick::Joystick *addJoystick(int64 deviceid) override;
 	void removeJoystick(love::joystick::Joystick *joystick) override;
 	void removeJoystick(love::joystick::Joystick *joystick) override;
 	love::joystick::Joystick *getJoystickFromID(int instanceid) override;
 	love::joystick::Joystick *getJoystickFromID(int instanceid) override;
 	love::joystick::Joystick *getJoystick(int joyindex) override;
 	love::joystick::Joystick *getJoystick(int joyindex) override;
@@ -69,7 +69,7 @@ private:
 	void checkGamepads(const std::string &guid) const;
 	void checkGamepads(const std::string &guid) const;
 
 
 	// SDL2's GUIDs identify *classes* of devices, instead of unique devices.
 	// SDL2's GUIDs identify *classes* of devices, instead of unique devices.
-	std::string getDeviceGUID(int deviceindex) const;
+	std::string getDeviceGUID(int64 deviceid) const;
 
 
 	// Lists of currently connected Joysticks.
 	// Lists of currently connected Joysticks.
 	std::vector<Joystick *> activeSticks;
 	std::vector<Joystick *> activeSticks;

+ 649 - 0
src/modules/joystick/sdl/JoystickSDL3.cpp

@@ -0,0 +1,649 @@
+/**
+ * Copyright (c) 2006-2023 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 "JoystickSDL3.h"
+#include "common/int.h"
+#include "sensor/sdl/Sensor.h"
+
+// SDL
+#include <SDL_version.h>
+
+// C++
+#include <algorithm>
+#include <limits>
+
+namespace love
+{
+namespace joystick
+{
+namespace sdl
+{
+
+Joystick::Joystick(int id)
+	: joyhandle(nullptr)
+	, controller(nullptr)
+	, joystickType(JOYSTICK_TYPE_UNKNOWN)
+	, instanceid(-1)
+	, id(id)
+{
+}
+
+Joystick::~Joystick()
+{
+	close();
+}
+
+bool Joystick::open(int64 deviceid)
+{
+	close();
+
+	joyhandle = SDL_OpenJoystick((SDL_JoystickID) deviceid);
+
+	if (joyhandle)
+	{
+		instanceid = SDL_GetJoystickInstanceID(joyhandle);
+
+		// SDL_JoystickGetGUIDString uses 32 bytes plus the null terminator.
+		char cstr[33];
+
+		SDL_JoystickGUID sdlguid = SDL_GetJoystickGUID(joyhandle);
+		SDL_GetJoystickGUIDString(sdlguid, cstr, (int) sizeof(cstr));
+
+		pguid = std::string(cstr);
+
+		// See if SDL thinks this is a Game Controller.
+		openGamepad(deviceid);
+
+		// Prefer the Joystick name for consistency.
+		const char *joyname = SDL_GetJoystickName(joyhandle);
+		if (!joyname && controller)
+			joyname = SDL_GetGamepadName(controller);
+
+		if (joyname)
+			name = joyname;
+
+		switch (SDL_GetJoystickType(joyhandle))
+		{
+		case SDL_JOYSTICK_TYPE_GAMEPAD:
+			joystickType = JOYSTICK_TYPE_GAMEPAD;
+			break;
+		case SDL_JOYSTICK_TYPE_WHEEL:
+			joystickType = JOYSTICK_TYPE_WHEEL;
+			break;
+		case SDL_JOYSTICK_TYPE_ARCADE_STICK:
+			joystickType = JOYSTICK_TYPE_ARCADE_STICK;
+			break;
+		case SDL_JOYSTICK_TYPE_FLIGHT_STICK:
+			joystickType = JOYSTICK_TYPE_FLIGHT_STICK;
+			break;
+		case SDL_JOYSTICK_TYPE_DANCE_PAD:
+			joystickType = JOYSTICK_TYPE_DANCE_PAD;
+			break;
+		case SDL_JOYSTICK_TYPE_GUITAR:
+			joystickType = JOYSTICK_TYPE_GUITAR;
+			break;
+		case SDL_JOYSTICK_TYPE_DRUM_KIT:
+			joystickType = JOYSTICK_TYPE_DRUM_KIT;
+			break;
+		case SDL_JOYSTICK_TYPE_ARCADE_PAD:
+			joystickType = JOYSTICK_TYPE_ARCADE_PAD;
+			break;
+		case SDL_JOYSTICK_TYPE_THROTTLE:
+			joystickType = JOYSTICK_TYPE_THROTTLE;
+			break;
+		default:
+			joystickType = JOYSTICK_TYPE_UNKNOWN;
+			break;
+		}
+	}
+
+	return isConnected();
+}
+
+void Joystick::close()
+{
+	if (controller)
+		SDL_CloseGamepad(controller);
+
+	if (joyhandle)
+		SDL_CloseJoystick(joyhandle);
+
+	joyhandle = nullptr;
+	controller = nullptr;
+	instanceid = -1;
+}
+
+bool Joystick::isConnected() const
+{
+	return joyhandle != nullptr && SDL_JoystickConnected(joyhandle);
+}
+
+const char *Joystick::getName() const
+{
+	return name.c_str();
+}
+
+Joystick::JoystickType Joystick::getJoystickType() const
+{
+	return joystickType;
+}
+
+int Joystick::getAxisCount() const
+{
+	return isConnected() ? SDL_GetNumJoystickAxes(joyhandle) : 0;
+}
+
+int Joystick::getButtonCount() const
+{
+	return isConnected() ? SDL_GetNumJoystickButtons(joyhandle) : 0;
+}
+
+int Joystick::getHatCount() const
+{
+	return isConnected() ? SDL_GetNumJoystickHats(joyhandle) : 0;
+}
+
+float Joystick::getAxis(int axisindex) const
+{
+	if (!isConnected() || axisindex < 0 || axisindex >= getAxisCount())
+		return 0;
+
+	return clampval(((float) SDL_GetJoystickAxis(joyhandle, axisindex))/32768.0f);
+}
+
+std::vector<float> Joystick::getAxes() const
+{
+	std::vector<float> axes;
+	int count = getAxisCount();
+
+	if (!isConnected() || count <= 0)
+		return axes;
+
+	axes.reserve(count);
+
+	for (int i = 0; i < count; i++)
+		axes.push_back(clampval(((float) SDL_GetJoystickAxis(joyhandle, i))/32768.0f));
+
+	return axes;
+}
+
+Joystick::Hat Joystick::getHat(int hatindex) const
+{
+	Hat h = HAT_INVALID;
+
+	if (!isConnected() || hatindex < 0 || hatindex >= getHatCount())
+		return h;
+
+	getConstant(SDL_GetJoystickHat(joyhandle, hatindex), h);
+
+	return h;
+}
+
+bool Joystick::isDown(const std::vector<int> &buttonlist) const
+{
+	if (!isConnected())
+		return false;
+
+	int numbuttons = getButtonCount();
+
+	for (int button : buttonlist)
+	{
+		if (button < 0 || button >= numbuttons)
+			continue;
+
+		if (SDL_GetJoystickButton(joyhandle, button) == 1)
+			return true;
+	}
+
+	return false;
+}
+
+void Joystick::setPlayerIndex(int index)
+{
+	if (!isConnected())
+		return;
+
+	SDL_SetJoystickPlayerIndex(joyhandle, index);
+}
+
+int Joystick::getPlayerIndex() const
+{
+	if (!isConnected())
+		return -1;
+
+	return SDL_GetJoystickPlayerIndex(joyhandle);
+}
+
+bool Joystick::openGamepad(int64 deviceid)
+{
+	if (!SDL_IsGamepad((SDL_JoystickID)deviceid))
+		return false;
+
+	if (isGamepad())
+	{
+		SDL_CloseGamepad(controller);
+		controller = nullptr;
+	}
+
+	controller = SDL_OpenGamepad((SDL_JoystickID)deviceid);
+	return isGamepad();
+}
+
+bool Joystick::isGamepad() const
+{
+	return controller != nullptr;
+}
+
+Joystick::GamepadType Joystick::getGamepadType() const
+{
+	if (controller == nullptr)
+		return GAMEPAD_TYPE_UNKNOWN;
+
+	switch (SDL_GetGamepadType(controller))
+	{
+		case SDL_GAMEPAD_TYPE_UNKNOWN: return GAMEPAD_TYPE_UNKNOWN;
+		case SDL_GAMEPAD_TYPE_XBOX360: return GAMEPAD_TYPE_XBOX360;
+		case SDL_GAMEPAD_TYPE_XBOXONE: return GAMEPAD_TYPE_XBOXONE;
+		case SDL_GAMEPAD_TYPE_PS3: return GAMEPAD_TYPE_PS3;
+		case SDL_GAMEPAD_TYPE_PS4: return GAMEPAD_TYPE_PS4;
+		case SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_PRO: return GAMEPAD_TYPE_NINTENDO_SWITCH_PRO;
+		case SDL_GAMEPAD_TYPE_PS5: return GAMEPAD_TYPE_PS5;
+		case SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_LEFT: return GAMEPAD_TYPE_JOYCON_LEFT;
+		case SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_RIGHT: return GAMEPAD_TYPE_JOYCON_RIGHT;
+		case SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_PAIR: return GAMEPAD_TYPE_JOYCON_PAIR;
+		default: return GAMEPAD_TYPE_UNKNOWN;
+	}
+
+	return GAMEPAD_TYPE_UNKNOWN;
+}
+
+float Joystick::getGamepadAxis(love::joystick::Joystick::GamepadAxis axis) const
+{
+	if (!isConnected() || !isGamepad())
+		return 0.f;
+
+	SDL_GamepadAxis sdlaxis;
+	if (!getConstant(axis, sdlaxis))
+		return 0.f;
+
+	Sint16 value = SDL_GetGamepadAxis(controller, sdlaxis);
+
+	return clampval((float) value / 32768.0f);
+}
+
+bool Joystick::isGamepadDown(const std::vector<GamepadButton> &blist) const
+{
+	if (!isConnected() || !isGamepad())
+		return false;
+
+	SDL_GamepadButton sdlbutton;
+
+	for (GamepadButton button : blist)
+	{
+		if (!getConstant(button, sdlbutton))
+			continue;
+
+		if (SDL_GetGamepadButton(controller, sdlbutton) == 1)
+			return true;
+	}
+
+	return false;
+}
+
+Joystick::JoystickInput Joystick::getGamepadMapping(const GamepadInput &input) const
+{
+	Joystick::JoystickInput jinput;
+	jinput.type = INPUT_TYPE_MAX_ENUM;
+
+	if (!isGamepad())
+		return jinput;
+
+	SDL_GamepadButton sdlbutton = SDL_GAMEPAD_BUTTON_INVALID;
+	SDL_GamepadAxis sdlaxis = SDL_GAMEPAD_AXIS_INVALID;
+
+	switch (input.type)
+	{
+	case INPUT_TYPE_BUTTON:
+		getConstant(input.button, sdlbutton);
+		break;
+	case INPUT_TYPE_AXIS:
+		getConstant(input.axis, sdlaxis);
+		break;
+	default:
+		break;
+	}
+
+	int bindcount = 0;
+	SDL_GamepadBinding **sdlbindings = SDL_GetGamepadBindings(controller, &bindcount);
+	const SDL_GamepadBinding *sdlbinding = nullptr;
+	for (int i = 0; i < bindcount; i++)
+	{
+		const SDL_GamepadBinding *b = sdlbindings[i];
+		if ((input.type == INPUT_TYPE_BUTTON && b->outputType == SDL_GAMEPAD_BINDTYPE_BUTTON && b->output.button == sdlbutton)
+			|| (input.type == INPUT_TYPE_AXIS && b->outputType == SDL_GAMEPAD_BINDTYPE_AXIS && b->output.axis.axis == sdlaxis))
+		{
+			switch (b->inputType)
+			{
+			case SDL_GAMEPAD_BINDTYPE_BUTTON:
+				jinput.type = INPUT_TYPE_BUTTON;
+				jinput.button = b->input.button;
+				break;
+			case SDL_GAMEPAD_BINDTYPE_AXIS:
+				jinput.type = INPUT_TYPE_AXIS;
+				jinput.axis = b->input.axis.axis;
+				break;
+			case SDL_GAMEPAD_BINDTYPE_HAT:
+				if (getConstant(b->input.hat.hat_mask, jinput.hat.value))
+				{
+					jinput.type = INPUT_TYPE_HAT;
+					jinput.hat.index = b->input.hat.hat;
+				}
+				break;
+			case SDL_GAMEPAD_BINDTYPE_NONE:
+			default:
+				break;
+			}
+
+			break;
+		}
+	}
+
+	SDL_free(sdlbindings);
+
+	return jinput;
+}
+
+std::string Joystick::getGamepadMappingString() const
+{
+	char *sdlmapping = nullptr;
+
+	if (controller != nullptr)
+		sdlmapping = SDL_GetGamepadMapping(controller);
+
+	if (sdlmapping == nullptr)
+	{
+		SDL_JoystickGUID sdlguid = SDL_GetJoystickGUIDFromString(pguid.c_str());
+		sdlmapping = SDL_GetGamepadMappingForGUID(sdlguid);
+	}
+
+	if (sdlmapping == nullptr)
+		return "";
+
+	std::string mappingstr(sdlmapping);
+	SDL_free(sdlmapping);
+
+	// Matches SDL_GameControllerAddMappingsFromRW.
+	if (mappingstr.find_last_of(',') != mappingstr.length() - 1)
+		mappingstr += ",";
+
+	if (mappingstr.find("platform:") == std::string::npos)
+		mappingstr += "platform:" + std::string(SDL_GetPlatform());
+
+	return mappingstr;
+}
+
+void *Joystick::getHandle() const
+{
+	return joyhandle;
+}
+
+std::string Joystick::getGUID() const
+{
+	// SDL2's GUIDs identify *classes* of devices, instead of unique devices.
+	return pguid;
+}
+
+int Joystick::getInstanceID() const
+{
+	return instanceid;
+}
+
+int Joystick::getID() const
+{
+	return id;
+}
+
+void Joystick::getDeviceInfo(int &vendorID, int &productID, int &productVersion) const
+{
+	if (joyhandle != nullptr)
+	{
+		vendorID = SDL_GetJoystickVendor(joyhandle);
+		productID = SDL_GetJoystickProduct(joyhandle);
+		productVersion = SDL_GetJoystickProductVersion(joyhandle);
+	}
+	else
+	{
+		vendorID = 0;
+		productID = 0;
+		productVersion = 0;
+	}
+}
+
+bool Joystick::isVibrationSupported()
+{
+	if (!isConnected())
+		return false;
+
+	SDL_PropertiesID props = SDL_GetJoystickProperties(joyhandle);
+	return SDL_GetBooleanProperty(props, SDL_PROP_JOYSTICK_CAP_RUMBLE_BOOLEAN, SDL_FALSE);
+}
+
+bool Joystick::setVibration(float left, float right, float duration)
+{
+	left = std::min(std::max(left, 0.0f), 1.0f);
+	right = std::min(std::max(right, 0.0f), 1.0f);
+
+	if (left == 0.0f && right == 0.0f)
+		return setVibration();
+
+	if (!isConnected())
+		return false;
+
+	Uint32 length = LOVE_UINT32_MAX;
+	if (duration >= 0.0f)
+	{
+		float maxduration = (float) (std::numeric_limits<Uint32>::max() / 1000.0);
+		length = Uint32(std::min(duration, maxduration) * 1000);
+	}
+
+	return SDL_RumbleJoystick(joyhandle, (Uint16)(left * LOVE_UINT16_MAX), (Uint16)(right * LOVE_UINT16_MAX), length) == 0;
+}
+
+bool Joystick::setVibration()
+{
+	return isConnected() && SDL_RumbleJoystick(joyhandle, 0, 0, 0) == 0;
+}
+
+void Joystick::getVibration(float &left, float &right)
+{
+	// Deprecated.
+	left = 0.0f;
+	right = 0.0f;
+}
+
+bool Joystick::hasSensor(Sensor::SensorType type) const
+{
+#if defined(LOVE_ENABLE_SENSOR)
+	using SDLSensor = love::sensor::sdl::Sensor;
+
+	if (!isGamepad())
+		return false;
+
+	return SDL_GamepadHasSensor(controller, SDLSensor::convert(type)) == SDL_TRUE;
+#else
+	return false;
+#endif
+}
+
+bool Joystick::isSensorEnabled(Sensor::SensorType type) const
+{
+#if defined(LOVE_ENABLE_SENSOR)
+	using SDLSensor = love::sensor::sdl::Sensor;
+
+	if (!isGamepad())
+		return false;
+
+	return SDL_GamepadSensorEnabled(controller, SDLSensor::convert(type)) == SDL_TRUE;
+#else
+	return false;
+#endif
+}
+
+void Joystick::setSensorEnabled(Sensor::SensorType type, bool enabled)
+{
+#if defined(LOVE_ENABLE_SENSOR)
+	using SDLSensor = love::sensor::sdl::Sensor;
+
+	if (!isGamepad())
+		throw love::Exception("Sensor is only supported on gamepad");
+
+	if (SDL_SetGamepadSensorEnabled(controller, SDLSensor::convert(type), enabled ? SDL_TRUE : SDL_FALSE) != 0)
+	{
+		const char *name = nullptr;
+		SDLSensor::getConstant(type, name);
+
+		throw love::Exception("Could not open \"%s\" SDL gamepad sensor (%s)", name, SDL_GetError());
+	}
+#else
+	throw love::Exception("Compiled version of LOVE does not support gamepad sensor");
+#endif
+}
+
+std::vector<float> Joystick::getSensorData(Sensor::SensorType type) const
+{
+#if defined(LOVE_ENABLE_SENSOR)
+	using SDLSensor = love::sensor::sdl::Sensor;
+
+	if (!isGamepad())
+		throw love::Exception("Sensor is only supported on gamepad");
+
+	std::vector<float> data(3);
+
+	if (!isSensorEnabled(type))
+	{
+		const char *name = nullptr;
+		SDLSensor::getConstant(type, name);
+
+		throw love::Exception("\"%s\" gamepad sensor is not enabled", name);
+	}
+
+	if (SDL_GetGamepadSensorData(controller, SDLSensor::convert(type), data.data(), (int) data.size()) != 0)
+	{
+		const char *name = nullptr;
+		SDLSensor::getConstant(type, name);
+
+		throw love::Exception("Could not get \"%s\" SDL gamepad sensor data (%s)", name, SDL_GetError());
+	}
+
+	return data;
+#else
+	throw love::Exception("Compiled version of LOVE does not support gamepad sensor");
+#endif
+}
+
+bool Joystick::getConstant(Uint8 in, Joystick::Hat &out)
+{
+	return hats.find(in, out);
+}
+
+bool Joystick::getConstant(Joystick::Hat in, Uint8 &out)
+{
+	return hats.find(in, out);
+}
+
+bool Joystick::getConstant(SDL_GamepadAxis in, Joystick::GamepadAxis &out)
+{
+	return gpAxes.find(in, out);
+}
+
+bool Joystick::getConstant(Joystick::GamepadAxis in, SDL_GamepadAxis &out)
+{
+	return gpAxes.find(in, out);
+}
+
+bool Joystick::getConstant(SDL_GamepadButton in, Joystick::GamepadButton &out)
+{
+	return gpButtons.find(in, out);
+}
+
+bool Joystick::getConstant(Joystick::GamepadButton in, SDL_GamepadButton &out)
+{
+	return gpButtons.find(in, out);
+}
+
+EnumMap<Joystick::Hat, Uint8, Joystick::HAT_MAX_ENUM>::Entry Joystick::hatEntries[] =
+{
+	{Joystick::HAT_CENTERED, SDL_HAT_CENTERED},
+	{Joystick::HAT_UP, SDL_HAT_UP},
+	{Joystick::HAT_RIGHT, SDL_HAT_RIGHT},
+	{Joystick::HAT_DOWN, SDL_HAT_DOWN},
+	{Joystick::HAT_LEFT, SDL_HAT_LEFT},
+	{Joystick::HAT_RIGHTUP, SDL_HAT_RIGHTUP},
+	{Joystick::HAT_RIGHTDOWN, SDL_HAT_RIGHTDOWN},
+	{Joystick::HAT_LEFTUP, SDL_HAT_LEFTUP},
+	{Joystick::HAT_LEFTDOWN, SDL_HAT_LEFTDOWN},
+};
+
+EnumMap<Joystick::Hat, Uint8, Joystick::HAT_MAX_ENUM> Joystick::hats(Joystick::hatEntries, sizeof(Joystick::hatEntries));
+
+EnumMap<Joystick::GamepadAxis, SDL_GamepadAxis, Joystick::GAMEPAD_AXIS_MAX_ENUM>::Entry Joystick::gpAxisEntries[] =
+{
+	{Joystick::GAMEPAD_AXIS_LEFTX, SDL_GAMEPAD_AXIS_LEFTX},
+	{Joystick::GAMEPAD_AXIS_LEFTY, SDL_GAMEPAD_AXIS_LEFTY},
+	{Joystick::GAMEPAD_AXIS_RIGHTX, SDL_GAMEPAD_AXIS_RIGHTX},
+	{Joystick::GAMEPAD_AXIS_RIGHTY, SDL_GAMEPAD_AXIS_RIGHTY},
+	{Joystick::GAMEPAD_AXIS_TRIGGERLEFT, SDL_GAMEPAD_AXIS_LEFT_TRIGGER},
+	{Joystick::GAMEPAD_AXIS_TRIGGERRIGHT, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER},
+};
+
+EnumMap<Joystick::GamepadAxis, SDL_GamepadAxis, Joystick::GAMEPAD_AXIS_MAX_ENUM> Joystick::gpAxes(Joystick::gpAxisEntries, sizeof(Joystick::gpAxisEntries));
+
+EnumMap<Joystick::GamepadButton, SDL_GamepadButton, Joystick::GAMEPAD_BUTTON_MAX_ENUM>::Entry Joystick::gpButtonEntries[] =
+{
+	{Joystick::GAMEPAD_BUTTON_A, SDL_GAMEPAD_BUTTON_SOUTH},
+	{Joystick::GAMEPAD_BUTTON_B, SDL_GAMEPAD_BUTTON_EAST},
+	{Joystick::GAMEPAD_BUTTON_X, SDL_GAMEPAD_BUTTON_WEST},
+	{Joystick::GAMEPAD_BUTTON_Y, SDL_GAMEPAD_BUTTON_NORTH},
+	{Joystick::GAMEPAD_BUTTON_BACK, SDL_GAMEPAD_BUTTON_BACK},
+	{Joystick::GAMEPAD_BUTTON_GUIDE, SDL_GAMEPAD_BUTTON_GUIDE},
+	{Joystick::GAMEPAD_BUTTON_START, SDL_GAMEPAD_BUTTON_START},
+	{Joystick::GAMEPAD_BUTTON_LEFTSTICK, SDL_GAMEPAD_BUTTON_LEFT_STICK},
+	{Joystick::GAMEPAD_BUTTON_RIGHTSTICK, SDL_GAMEPAD_BUTTON_RIGHT_STICK},
+	{Joystick::GAMEPAD_BUTTON_LEFTSHOULDER, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER},
+	{Joystick::GAMEPAD_BUTTON_RIGHTSHOULDER, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER},
+	{Joystick::GAMEPAD_BUTTON_DPAD_UP, SDL_GAMEPAD_BUTTON_DPAD_UP},
+	{Joystick::GAMEPAD_BUTTON_DPAD_DOWN, SDL_GAMEPAD_BUTTON_DPAD_DOWN},
+	{Joystick::GAMEPAD_BUTTON_DPAD_LEFT, SDL_GAMEPAD_BUTTON_DPAD_LEFT},
+	{Joystick::GAMEPAD_BUTTON_DPAD_RIGHT, SDL_GAMEPAD_BUTTON_DPAD_RIGHT},
+	{Joystick::GAMEPAD_BUTTON_MISC1, SDL_GAMEPAD_BUTTON_MISC1},
+	{Joystick::GAMEPAD_BUTTON_PADDLE1, SDL_GAMEPAD_BUTTON_LEFT_PADDLE1},
+	{Joystick::GAMEPAD_BUTTON_PADDLE2, SDL_GAMEPAD_BUTTON_LEFT_PADDLE2},
+	{Joystick::GAMEPAD_BUTTON_PADDLE3, SDL_GAMEPAD_BUTTON_RIGHT_PADDLE1},
+	{Joystick::GAMEPAD_BUTTON_PADDLE4, SDL_GAMEPAD_BUTTON_RIGHT_PADDLE2},
+	{Joystick::GAMEPAD_BUTTON_TOUCHPAD, SDL_GAMEPAD_BUTTON_TOUCHPAD},
+};
+
+EnumMap<Joystick::GamepadButton, SDL_GamepadButton, Joystick::GAMEPAD_BUTTON_MAX_ENUM> Joystick::gpButtons(Joystick::gpButtonEntries, sizeof(Joystick::gpButtonEntries));
+
+} // sdl
+} // joystick
+} // love

+ 141 - 0
src/modules/joystick/sdl/JoystickSDL3.h

@@ -0,0 +1,141 @@
+/**
+ * Copyright (c) 2006-2023 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.
+ **/
+
+#ifndef LOVE_JOYSTICK_SDL_JOYSTICK_SDL3_H
+#define LOVE_JOYSTICK_SDL_JOYSTICK_SDL3_H
+
+// LOVE
+#include "joystick/Joystick.h"
+#include "common/EnumMap.h"
+#include "common/int.h"
+
+// SDL
+#include <SDL.h>
+
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+
+namespace love
+{
+namespace joystick
+{
+namespace sdl
+{
+
+class Joystick : public love::joystick::Joystick
+{
+public:
+
+	Joystick(int id);
+
+	virtual ~Joystick();
+
+	bool open(int64 deviceid) override;
+	void close() override;
+
+	bool isConnected() const override;
+
+	const char *getName() const override;
+
+	JoystickType getJoystickType() const override;
+
+	int getAxisCount() const override;
+	int getButtonCount() const override;
+	int getHatCount() const override;
+
+	float getAxis(int axisindex) const override;
+	std::vector<float> getAxes() const override;
+	Hat getHat(int hatindex) const override;
+
+	bool isDown(const std::vector<int> &buttonlist) const override;
+
+	void setPlayerIndex(int index) override;
+	int getPlayerIndex() const override;
+
+	bool openGamepad(int64 deviceid) override;
+	bool isGamepad() const override;
+
+	GamepadType getGamepadType() const override;
+
+	float getGamepadAxis(GamepadAxis axis) const override;
+	bool isGamepadDown(const std::vector<GamepadButton> &blist) const override;
+
+	JoystickInput getGamepadMapping(const GamepadInput &input) const override;
+	std::string getGamepadMappingString() const override;
+
+	void *getHandle() const override;
+
+	std::string getGUID() const override;
+	int getInstanceID() const override;
+	int getID() const override;
+
+	void getDeviceInfo(int &vendorID, int &productID, int &productVersion) const override;
+
+	bool isVibrationSupported() override;
+	bool setVibration(float left, float right, float duration = -1.0f) override;
+	bool setVibration() override;
+	void getVibration(float &left, float &right) override;
+
+	bool hasSensor(Sensor::SensorType type) const override;
+	bool isSensorEnabled(Sensor::SensorType type) const override;
+	void setSensorEnabled(Sensor::SensorType type, bool enabled) override;
+	std::vector<float> getSensorData(Sensor::SensorType type) const override;
+
+	static bool getConstant(Hat in, Uint8 &out);
+	static bool getConstant(Uint8 in, Hat &out);
+
+	static bool getConstant(SDL_GamepadAxis in, GamepadAxis &out);
+	static bool getConstant(GamepadAxis in, SDL_GamepadAxis &out);
+
+	static bool getConstant(SDL_GamepadButton in, GamepadButton &out);
+	static bool getConstant(GamepadButton in, SDL_GamepadButton &out);
+
+private:
+
+	Joystick() {}
+
+	SDL_Joystick *joyhandle;
+	SDL_Gamepad *controller;
+
+	JoystickType joystickType;
+
+	SDL_JoystickID instanceid;
+	std::string pguid;
+	int id;
+
+	std::string name;
+
+	static EnumMap<Hat, Uint8, Joystick::HAT_MAX_ENUM>::Entry hatEntries[];
+	static EnumMap<Hat, Uint8, Joystick::HAT_MAX_ENUM> hats;
+
+	static EnumMap<GamepadAxis, SDL_GamepadAxis, GAMEPAD_AXIS_MAX_ENUM>::Entry gpAxisEntries[];
+	static EnumMap<GamepadAxis, SDL_GamepadAxis, GAMEPAD_AXIS_MAX_ENUM> gpAxes;
+
+	static EnumMap<GamepadButton, SDL_GamepadButton, GAMEPAD_BUTTON_MAX_ENUM>::Entry gpButtonEntries[];
+	static EnumMap<GamepadButton, SDL_GamepadButton, GAMEPAD_BUTTON_MAX_ENUM> gpButtons;
+
+};
+
+} // sdl
+} // joystick
+} // love
+
+#endif // SDL_VERSION_ATLEAST(3, 0, 0)
+
+#endif // LOVE_JOYSTICK_SDL_JOYSTICK_SDL3_H

+ 15 - 0
src/modules/keyboard/sdl/Keyboard.cpp

@@ -91,6 +91,16 @@ bool Keyboard::isModifierActive(ModifierKey key) const
 
 
 	switch (key)
 	switch (key)
 	{
 	{
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+	case MODKEY_NUMLOCK:
+		return (modstate & SDL_KMOD_NUM) != 0;
+	case MODKEY_CAPSLOCK:
+		return (modstate & SDL_KMOD_CAPS) != 0;
+	case MODKEY_SCROLLLOCK:
+		return (modstate & SDL_KMOD_SCROLL) != 0;
+	case MODKEY_MODE:
+		return (modstate & SDL_KMOD_MODE) != 0;
+#else
 	case MODKEY_NUMLOCK:
 	case MODKEY_NUMLOCK:
 		return (modstate & KMOD_NUM) != 0;
 		return (modstate & KMOD_NUM) != 0;
 	case MODKEY_CAPSLOCK:
 	case MODKEY_CAPSLOCK:
@@ -99,6 +109,7 @@ bool Keyboard::isModifierActive(ModifierKey key) const
 		return (modstate & KMOD_SCROLL) != 0;
 		return (modstate & KMOD_SCROLL) != 0;
 	case MODKEY_MODE:
 	case MODKEY_MODE:
 		return (modstate & KMOD_MODE) != 0;
 		return (modstate & KMOD_MODE) != 0;
+#endif
 	default:
 	default:
 		break;
 		break;
 	}
 	}
@@ -164,7 +175,11 @@ void Keyboard::setTextInput(bool enable, double x, double y, double w, double h)
 
 
 bool Keyboard::hasTextInput() const
 bool Keyboard::hasTextInput() const
 {
 {
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+	return SDL_TextInputActive();
+#else
 	return SDL_IsTextInputActive() != SDL_FALSE;
 	return SDL_IsTextInputActive() != SDL_FALSE;
+#endif
 }
 }
 
 
 bool Keyboard::hasScreenKeyboard() const
 bool Keyboard::hasScreenKeyboard() const

+ 20 - 4
src/modules/mouse/sdl/Cursor.cpp

@@ -22,6 +22,8 @@
 #include "Cursor.h"
 #include "Cursor.h"
 #include "common/config.h"
 #include "common/config.h"
 
 
+#include <SDL_version.h>
+
 namespace love
 namespace love
 {
 {
 namespace mouse
 namespace mouse
@@ -34,6 +36,13 @@ Cursor::Cursor(image::ImageData *data, int hotx, int hoty)
 	, type(CURSORTYPE_IMAGE)
 	, type(CURSORTYPE_IMAGE)
 	, systemType(CURSOR_MAX_ENUM)
 	, systemType(CURSOR_MAX_ENUM)
 {
 {
+	int w = data->getWidth();
+	int h = data->getHeight();
+	int pitch = w * 4;
+
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+	SDL_Surface *surface = SDL_CreateSurfaceFrom(data->getData(), w, h, pitch, SDL_PIXELFORMAT_RGBA8888);
+#else
 	Uint32 rmask, gmask, bmask, amask;
 	Uint32 rmask, gmask, bmask, amask;
 #ifdef LOVE_BIG_ENDIAN
 #ifdef LOVE_BIG_ENDIAN
 	rmask = 0xFF000000;
 	rmask = 0xFF000000;
@@ -47,17 +56,19 @@ Cursor::Cursor(image::ImageData *data, int hotx, int hoty)
 	amask = 0xFF000000;
 	amask = 0xFF000000;
 #endif
 #endif
 
 
-	int w = data->getWidth();
-	int h = data->getHeight();
-	int pitch = w * 4;
-
 	SDL_Surface *surface = SDL_CreateRGBSurfaceFrom(data->getData(), w, h, 32, pitch, rmask, gmask, bmask, amask);
 	SDL_Surface *surface = SDL_CreateRGBSurfaceFrom(data->getData(), w, h, 32, pitch, rmask, gmask, bmask, amask);
+#endif
 	if (!surface)
 	if (!surface)
 		throw love::Exception("Cannot create cursor: out of memory!");
 		throw love::Exception("Cannot create cursor: out of memory!");
 
 
 	cursor = SDL_CreateColorCursor(surface, hotx, hoty);
 	cursor = SDL_CreateColorCursor(surface, hotx, hoty);
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+	SDL_DestroySurface(surface);
+#else
 	SDL_FreeSurface(surface);
 	SDL_FreeSurface(surface);
 
 
+#endif
+
 	if (!cursor)
 	if (!cursor)
 		throw love::Exception("Cannot create cursor: %s", SDL_GetError());
 		throw love::Exception("Cannot create cursor: %s", SDL_GetError());
 }
 }
@@ -80,8 +91,13 @@ Cursor::Cursor(mouse::Cursor::SystemCursor cursortype)
 
 
 Cursor::~Cursor()
 Cursor::~Cursor()
 {
 {
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+	if (cursor)
+		SDL_DestroyCursor(cursor);
+#else
 	if (cursor)
 	if (cursor)
 		SDL_FreeCursor(cursor);
 		SDL_FreeCursor(cursor);
+#endif
 }
 }
 
 
 void *Cursor::getHandle() const
 void *Cursor::getHandle() const

+ 43 - 2
src/modules/mouse/sdl/Mouse.cpp

@@ -24,6 +24,7 @@
 
 
 // SDL
 // SDL
 #include <SDL_mouse.h>
 #include <SDL_mouse.h>
+#include <SDL_version.h>
 
 
 namespace love
 namespace love
 {
 {
@@ -126,8 +127,13 @@ bool Mouse::isCursorSupported() const
 
 
 void Mouse::getPosition(double &x, double &y) const
 void Mouse::getPosition(double &x, double &y) const
 {
 {
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+	float mx, my;
+	SDL_GetMouseState(&mx, &my);
+#else
 	int mx, my;
 	int mx, my;
 	SDL_GetMouseState(&mx, &my);
 	SDL_GetMouseState(&mx, &my);
+#endif
 
 
 	x = (double) mx;
 	x = (double) mx;
 	y = (double) my;
 	y = (double) my;
@@ -161,12 +167,35 @@ void Mouse::setPosition(double x, double y)
 
 
 void Mouse::getGlobalPosition(double &x, double &y, int &displayindex) const
 void Mouse::getGlobalPosition(double &x, double &y, int &displayindex) const
 {
 {
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+	float globalx, globaly;
+#else
 	int globalx, globaly;
 	int globalx, globaly;
+#endif
 	SDL_GetGlobalMouseState(&globalx, &globaly);
 	SDL_GetGlobalMouseState(&globalx, &globaly);
 
 
-	int mx = globalx;
-	int my = globaly;
+	auto mx = globalx;
+	auto my = globaly;
+
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+	int displaycount = 0;
+	SDL_DisplayID *displays = SDL_GetDisplays(&displaycount);
+
+	for (displayindex = 0; displayindex < displaycount; displayindex++)
+	{
+		SDL_Rect r = {};
+		SDL_GetDisplayBounds(displays[displayindex], &r);
+
+		SDL_FRect frect = {(float)r.x, (float)r.y, (float)r.w, (float)r.h};
+
+		mx -= frect.x;
+		my -= frect.y;
 
 
+		SDL_FPoint p = { globalx, globaly };
+		if (SDL_PointInRectFloat(&p, &frect))
+			break;
+	}
+#else
 	int displaycount = SDL_GetNumVideoDisplays();
 	int displaycount = SDL_GetNumVideoDisplays();
 
 
 	for (displayindex = 0; displayindex < displaycount; displayindex++)
 	for (displayindex = 0; displayindex < displaycount; displayindex++)
@@ -181,6 +210,7 @@ void Mouse::getGlobalPosition(double &x, double &y, int &displayindex) const
 		if (SDL_PointInRect(&p, &rect))
 		if (SDL_PointInRect(&p, &rect))
 			break;
 			break;
 	}
 	}
+#endif
 
 
 	if (displayindex >= displaycount)
 	if (displayindex >= displaycount)
 		displayindex = 0;
 		displayindex = 0;
@@ -191,7 +221,14 @@ void Mouse::getGlobalPosition(double &x, double &y, int &displayindex) const
 
 
 void Mouse::setVisible(bool visible)
 void Mouse::setVisible(bool visible)
 {
 {
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+	if (visible)
+		SDL_ShowCursor();
+	else
+		SDL_HideCursor();
+#else
 	SDL_ShowCursor(visible ? SDL_ENABLE : SDL_DISABLE);
 	SDL_ShowCursor(visible ? SDL_ENABLE : SDL_DISABLE);
+#endif
 }
 }
 
 
 bool Mouse::isDown(const std::vector<int> &buttons) const
 bool Mouse::isDown(const std::vector<int> &buttons) const
@@ -224,7 +261,11 @@ bool Mouse::isDown(const std::vector<int> &buttons) const
 
 
 bool Mouse::isVisible() const
 bool Mouse::isVisible() const
 {
 {
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+	return SDL_CursorVisible();
+#else
 	return SDL_ShowCursor(SDL_QUERY) == SDL_ENABLE;
 	return SDL_ShowCursor(SDL_QUERY) == SDL_ENABLE;
+#endif
 }
 }
 
 
 void Mouse::setGrabbed(bool grab)
 void Mouse::setGrabbed(bool grab)

+ 50 - 1
src/modules/sensor/sdl/Sensor.cpp

@@ -51,11 +51,25 @@ const char *Sensor::getName() const
 
 
 bool Sensor::hasSensor(SensorType type)
 bool Sensor::hasSensor(SensorType type)
 {
 {
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+	int count = 0;
+	SDL_SensorID *sensorIDs = SDL_GetSensors(&count);
+	for (int i = 0; i < count; i++)
+	{
+		if (convert(SDL_GetSensorInstanceType(sensorIDs[i])) == type)
+		{
+			SDL_free(sensorIDs);
+			return true;
+		}
+	}
+	SDL_free(sensorIDs);
+#else
 	for (int i = 0; i < SDL_NumSensors(); i++)
 	for (int i = 0; i < SDL_NumSensors(); i++)
 	{
 	{
 		if (convert(SDL_SensorGetDeviceType(i)) == type)
 		if (convert(SDL_SensorGetDeviceType(i)) == type)
 			return true;
 			return true;
 	}
 	}
+#endif
 
 
 	return false;
 	return false;
 }
 }
@@ -69,11 +83,38 @@ void Sensor::setEnabled(SensorType type, bool enable)
 {
 {
 	if (sensors[type] && !enable)
 	if (sensors[type] && !enable)
 	{
 	{
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+		SDL_CloseSensor(sensors[type]);
+#else
 		SDL_SensorClose(sensors[type]);
 		SDL_SensorClose(sensors[type]);
+#endif
 		sensors[type] = nullptr;
 		sensors[type] = nullptr;
 	}
 	}
 	else if (sensors[type] == nullptr && enable)
 	else if (sensors[type] == nullptr && enable)
 	{
 	{
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+		int count = 0;
+		SDL_SensorID *sensorIDs = SDL_GetSensors(&count);
+		for (int i = 0; i < count; i++)
+		{
+			if (convert(SDL_GetSensorInstanceType(sensorIDs[i])) == type)
+			{
+				SDL_Sensor *sensorHandle = SDL_OpenSensor(sensorIDs[i]);
+
+				if (sensorHandle == nullptr)
+				{
+					SDL_free(sensorIDs);
+
+					const char *name = nullptr;
+					getConstant(type, name);
+					throw love::Exception("Could not open \"%s\" SDL sensor (%s)", name, SDL_GetError());
+				}
+
+				sensors[type] = sensorHandle;
+			}
+		}
+		SDL_free(sensorIDs);
+#else
 		for (int i = 0; i < SDL_NumSensors(); i++)
 		for (int i = 0; i < SDL_NumSensors(); i++)
 		{
 		{
 			if (convert(SDL_SensorGetDeviceType(i)) == type)
 			if (convert(SDL_SensorGetDeviceType(i)) == type)
@@ -84,13 +125,13 @@ void Sensor::setEnabled(SensorType type, bool enable)
 				{
 				{
 					const char *name = nullptr;
 					const char *name = nullptr;
 					getConstant(type, name);
 					getConstant(type, name);
-
 					throw love::Exception("Could not open \"%s\" SDL sensor (%s)", name, SDL_GetError());
 					throw love::Exception("Could not open \"%s\" SDL sensor (%s)", name, SDL_GetError());
 				}
 				}
 
 
 				sensors[type] = sensorHandle;
 				sensors[type] = sensorHandle;
 			}
 			}
 		}
 		}
+#endif
 	}
 	}
 }
 }
 
 
@@ -106,7 +147,11 @@ std::vector<float> Sensor::getData(SensorType type)
 
 
 	std::vector<float> values(3);
 	std::vector<float> values(3);
 
 
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+	if (SDL_GetSensorData(sensors[type], values.data(), (int) values.size()) != 0)
+#else
 	if (SDL_SensorGetData(sensors[type], values.data(), (int) values.size()) != 0)
 	if (SDL_SensorGetData(sensors[type], values.data(), (int) values.size()) != 0)
+#endif
 	{
 	{
 		const char *name = nullptr;
 		const char *name = nullptr;
 		getConstant(type, name);
 		getConstant(type, name);
@@ -140,7 +185,11 @@ const char *Sensor::getSensorName(SensorType type)
 		throw love::Exception("\"%s\" sensor is not enabled", name);
 		throw love::Exception("\"%s\" sensor is not enabled", name);
 	}
 	}
 
 
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+	return SDL_GetSensorName(sensors[type]);
+#else
 	return SDL_SensorGetName(sensors[type]);
 	return SDL_SensorGetName(sensors[type]);
+#endif
 }
 }
 
 
 Sensor::SensorType Sensor::convert(SDL_SensorType type)
 Sensor::SensorType Sensor::convert(SDL_SensorType type)

+ 23 - 0
src/modules/thread/sdl/threads.cpp

@@ -50,22 +50,38 @@ void Mutex::unlock()
 
 
 Conditional::Conditional()
 Conditional::Conditional()
 {
 {
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+	cond = SDL_CreateCondition();
+#else
 	cond = SDL_CreateCond();
 	cond = SDL_CreateCond();
+#endif
 }
 }
 
 
 Conditional::~Conditional()
 Conditional::~Conditional()
 {
 {
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+	SDL_DestroyCondition(cond);
+#else
 	SDL_DestroyCond(cond);
 	SDL_DestroyCond(cond);
+#endif
 }
 }
 
 
 void Conditional::signal()
 void Conditional::signal()
 {
 {
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+	SDL_SignalCondition(cond);
+#else
 	SDL_CondSignal(cond);
 	SDL_CondSignal(cond);
+#endif
 }
 }
 
 
 void Conditional::broadcast()
 void Conditional::broadcast()
 {
 {
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+	SDL_BroadcastCondition(cond);
+#else
 	SDL_CondBroadcast(cond);
 	SDL_CondBroadcast(cond);
+#endif
 }
 }
 
 
 bool Conditional::wait(thread::Mutex *_mutex, int timeout)
 bool Conditional::wait(thread::Mutex *_mutex, int timeout)
@@ -74,10 +90,17 @@ bool Conditional::wait(thread::Mutex *_mutex, int timeout)
 	// however, you're asking for it if you're
 	// however, you're asking for it if you're
 	// mixing thread implementations.
 	// mixing thread implementations.
 	Mutex *mutex = (Mutex *) _mutex;
 	Mutex *mutex = (Mutex *) _mutex;
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+	if (timeout < 0)
+		return !SDL_WaitCondition(cond, mutex->mutex);
+	else
+		return (SDL_WaitConditionTimeout(cond, mutex->mutex, timeout) == 0);
+#else
 	if (timeout < 0)
 	if (timeout < 0)
 		return !SDL_CondWait(cond, mutex->mutex);
 		return !SDL_CondWait(cond, mutex->mutex);
 	else
 	else
 		return (SDL_CondWaitTimeout(cond, mutex->mutex, timeout) == 0);
 		return (SDL_CondWaitTimeout(cond, mutex->mutex, timeout) == 0);
+#endif
 }
 }
 
 
 } // sdl
 } // sdl

+ 9 - 0
src/modules/thread/sdl/threads.h

@@ -25,6 +25,7 @@
 #include "thread/threads.h"
 #include "thread/threads.h"
 
 
 #include <SDL_thread.h>
 #include <SDL_thread.h>
+#include <SDL_version.h>
 
 
 namespace love
 namespace love
 {
 {
@@ -47,7 +48,11 @@ public:
 
 
 private:
 private:
 
 
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+	SDL_Mutex *mutex;
+#else
 	SDL_mutex *mutex;
 	SDL_mutex *mutex;
+#endif
 	Mutex(const Mutex&/* mutex*/) {}
 	Mutex(const Mutex&/* mutex*/) {}
 
 
 	friend class Conditional;
 	friend class Conditional;
@@ -67,7 +72,11 @@ public:
 
 
 private:
 private:
 
 
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+	SDL_Condition *cond;
+#else
 	SDL_cond *cond;
 	SDL_cond *cond;
+#endif
 
 
 }; // Conditional
 }; // Conditional
 
 

+ 1 - 1
src/modules/timer/Timer.cpp

@@ -84,7 +84,7 @@ double Timer::step()
 void Timer::sleep(double seconds) const
 void Timer::sleep(double seconds) const
 {
 {
 	if (seconds >= 0)
 	if (seconds >= 0)
-		love::sleep((unsigned int)(seconds*1000));
+		love::sleep(seconds*1000);
 }
 }
 
 
 double Timer::getDelta() const
 double Timer::getDelta() const

+ 14 - 0
src/modules/touch/sdl/Touch.cpp

@@ -23,6 +23,8 @@
 #include "common/Exception.h"
 #include "common/Exception.h"
 #include "Touch.h"
 #include "Touch.h"
 
 
+#include <SDL_version.h>
+
 // C++
 // C++
 #include <algorithm>
 #include <algorithm>
 
 
@@ -63,11 +65,19 @@ void Touch::onEvent(Uint32 eventtype, const TouchInfo &info)
 
 
 	switch (eventtype)
 	switch (eventtype)
 	{
 	{
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+	case SDL_EVENT_FINGER_DOWN:
+#else
 	case SDL_FINGERDOWN:
 	case SDL_FINGERDOWN:
+#endif
 		touches.erase(std::remove_if(touches.begin(), touches.end(), compare), touches.end());
 		touches.erase(std::remove_if(touches.begin(), touches.end(), compare), touches.end());
 		touches.push_back(info);
 		touches.push_back(info);
 		break;
 		break;
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+	case SDL_EVENT_FINGER_MOTION:
+#else
 	case SDL_FINGERMOTION:
 	case SDL_FINGERMOTION:
+#endif
 	{
 	{
 		for (TouchInfo &touch : touches)
 		for (TouchInfo &touch : touches)
 		{
 		{
@@ -76,7 +86,11 @@ void Touch::onEvent(Uint32 eventtype, const TouchInfo &info)
 		}
 		}
 		break;
 		break;
 	}
 	}
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+	case SDL_EVENT_FINGER_UP:
+#else
 	case SDL_FINGERUP:
 	case SDL_FINGERUP:
+#endif
 		touches.erase(std::remove_if(touches.begin(), touches.end(), compare), touches.end());
 		touches.erase(std::remove_if(touches.begin(), touches.end(), compare), touches.end());
 		break;
 		break;
 	default:
 	default:

+ 260 - 20
src/modules/window/sdl/Window.cpp

@@ -44,7 +44,9 @@
 #include <cstdio>
 #include <cstdio>
 
 
 // SDL
 // SDL
+#if !SDL_VERSION_ATLEAST(3, 0, 0)
 #include <SDL_syswm.h>
 #include <SDL_syswm.h>
+#endif
 
 
 #ifdef LOVE_GRAPHICS_VULKAN
 #ifdef LOVE_GRAPHICS_VULKAN
 #include <SDL_vulkan.h>
 #include <SDL_vulkan.h>
@@ -333,11 +335,19 @@ bool Window::createWindowAndContext(int x, int y, int w, int h, Uint32 windowfla
 		if (window)
 		if (window)
 		{
 		{
 			SDL_DestroyWindow(window);
 			SDL_DestroyWindow(window);
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+			SDL_FlushEvents(SDL_EVENT_WINDOW_FIRST, SDL_EVENT_WINDOW_LAST);
+#else
 			SDL_FlushEvent(SDL_WINDOWEVENT);
 			SDL_FlushEvent(SDL_WINDOWEVENT);
+#endif
 			window = nullptr;
 			window = nullptr;
 		}
 		}
 
 
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+		window = SDL_CreateWindow(title.c_str(), w, h, windowflags);
+#else
 		window = SDL_CreateWindow(title.c_str(), x, y, w, h, windowflags);
 		window = SDL_CreateWindow(title.c_str(), x, y, w, h, windowflags);
+#endif
 
 
 		if (!window)
 		if (!window)
 		{
 		{
@@ -345,6 +355,10 @@ bool Window::createWindowAndContext(int x, int y, int w, int h, Uint32 windowfla
 			return false;
 			return false;
 		}
 		}
 
 
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+		SDL_SetWindowPosition(window, x, y);
+#endif
+
 		if (attribs != nullptr && renderer == love::graphics::Renderer::RENDERER_OPENGL)
 		if (attribs != nullptr && renderer == love::graphics::Renderer::RENDERER_OPENGL)
 		{
 		{
 #ifdef LOVE_MACOS
 #ifdef LOVE_MACOS
@@ -463,6 +477,33 @@ bool Window::createWindowAndContext(int x, int y, int w, int h, Uint32 windowfla
 	return true;
 	return true;
 }
 }
 
 
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+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];
+}
+#endif
+
 bool Window::setWindow(int width, int height, WindowSettings *settings)
 bool Window::setWindow(int width, int height, WindowSettings *settings)
 {
 {
 	if (!graphics.get())
 	if (!graphics.get())
@@ -484,15 +525,28 @@ bool Window::setWindow(int width, int height, WindowSettings *settings)
 	f.minwidth = std::max(f.minwidth, 1);
 	f.minwidth = std::max(f.minwidth, 1);
 	f.minheight = std::max(f.minheight, 1);
 	f.minheight = std::max(f.minheight, 1);
 
 
-	f.displayindex = std::min(std::max(f.displayindex, 0), getDisplayCount() - 1);
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+	SDLDisplayIDs displays;
+	int displaycount = displays.count;
+#else
+	int displaycount = getDisplayCount();
+#endif
+
+	f.displayindex = std::min(std::max(f.displayindex, 0), displaycount - 1);
 
 
 	// Use the desktop resolution if a width or height of 0 is specified.
 	// Use the desktop resolution if a width or height of 0 is specified.
 	if (width == 0 || height == 0)
 	if (width == 0 || height == 0)
 	{
 	{
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+		const SDL_DisplayMode *mode = SDL_GetDesktopDisplayMode(displays.ids[f.displayindex]);
+		width = mode->w;
+		height = mode->h;
+#else
 		SDL_DisplayMode mode = {};
 		SDL_DisplayMode mode = {};
 		SDL_GetDesktopDisplayMode(f.displayindex, &mode);
 		SDL_GetDesktopDisplayMode(f.displayindex, &mode);
 		width = mode.w;
 		width = mode.w;
 		height = mode.h;
 		height = mode.h;
+#endif
 	}
 	}
 
 
 	// On Android, disable fullscreen first on window creation so it's
 	// On Android, disable fullscreen first on window creation so it's
@@ -527,43 +581,72 @@ bool Window::setWindow(int width, int height, WindowSettings *settings)
 			x = y = SDL_WINDOWPOS_UNDEFINED_DISPLAY(f.displayindex);
 			x = y = SDL_WINDOWPOS_UNDEFINED_DISPLAY(f.displayindex);
 	}
 	}
 
 
-	SDL_DisplayMode fsmode = {0, width, height, 0, nullptr};
+	Uint32 sdlflags = 0;
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+	const SDL_DisplayMode *fsmode = nullptr;
+#endif
 
 
-	if (f.fullscreen && f.fstype == FULLSCREEN_EXCLUSIVE)
+	if (f.fullscreen)
 	{
 	{
-		// Fullscreen window creation will bug out if no mode can be used.
-		if (SDL_GetClosestDisplayMode(f.displayindex, &fsmode, &fsmode) == nullptr)
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+		sdlflags |= SDL_WINDOW_FULLSCREEN;
+
+		if (f.fstype == FULLSCREEN_EXCLUSIVE)
 		{
 		{
-			// 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.
-			if (SDL_GetDisplayMode(f.displayindex, 0, &fsmode) < 0)
-				return false;
+			SDL_DisplayID display = displays.ids[f.displayindex];
+			fsmode = SDL_GetClosestFullscreenDisplayMode(display, width, height, 0, isHighDPIAllowed());
+			if (fsmode == nullptr)
+			{
+				// 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;
+				const SDL_DisplayMode **modes = SDL_GetFullscreenDisplayModes(display, &modecount);
+				fsmode = modecount > 0 ? modes[0] : nullptr;
+				SDL_free(modes);
+				if (fsmode == nullptr)
+					return false;
+			}
 		}
 		}
-	}
-
-	bool needsetmode = false;
+#else
+		if (f.fstype == FULLSCREEN_EXCLUSIVE)
+		{
+			SDL_DisplayMode fsmode = {0, width, height, 0, nullptr};
 
 
-	Uint32 sdlflags = 0;
+			// Fullscreen window creation will bug out if no mode can be used.
+			if (SDL_GetClosestDisplayMode(f.displayindex, &fsmode, &fsmode) == nullptr)
+			{
+				// 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.
+				if (SDL_GetDisplayMode(f.displayindex, 0, &fsmode) < 0)
+					return false;
+			}
 
 
-	if (f.fullscreen)
-	{
-		if (f.fstype == FULLSCREEN_DESKTOP)
-			sdlflags |= SDL_WINDOW_FULLSCREEN_DESKTOP;
-		else
-		{
 			sdlflags |= SDL_WINDOW_FULLSCREEN;
 			sdlflags |= SDL_WINDOW_FULLSCREEN;
 			width = fsmode.w;
 			width = fsmode.w;
 			height = fsmode.h;
 			height = fsmode.h;
 		}
 		}
+		else
+		{
+			sdlflags |= SDL_WINDOW_FULLSCREEN_DESKTOP;
+		}
+#endif
 	}
 	}
 
 
+	bool needsetmode = false;
+
 	if (renderer != windowRenderer && isOpen())
 	if (renderer != windowRenderer && isOpen())
 		close();
 		close();
 
 
 	if (isOpen())
 	if (isOpen())
 	{
 	{
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+		SDL_SetWindowFullscreenMode(window, fsmode);
+		if (SDL_SetWindowFullscreen(window, (sdlflags & SDL_WINDOW_FULLSCREEN) != 0) == 0 && renderer == graphics::RENDERER_OPENGL)
+#else
 		if (SDL_SetWindowFullscreen(window, sdlflags) == 0 && renderer == graphics::RENDERER_OPENGL)
 		if (SDL_SetWindowFullscreen(window, sdlflags) == 0 && renderer == graphics::RENDERER_OPENGL)
+#endif
 			SDL_GL_MakeCurrent(window, glcontext);
 			SDL_GL_MakeCurrent(window, glcontext);
 
 
 		if (!f.fullscreen)
 		if (!f.fullscreen)
@@ -594,11 +677,29 @@ bool Window::setWindow(int width, int height, WindowSettings *settings)
 			 sdlflags |= SDL_WINDOW_BORDERLESS;
 			 sdlflags |= SDL_WINDOW_BORDERLESS;
 
 
 		// Note: this flag is ignored on Windows.
 		// Note: this flag is ignored on Windows.
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+		if (isHighDPIAllowed())
+			sdlflags |= SDL_WINDOW_HIGH_PIXEL_DENSITY;
+#else
 		 if (isHighDPIAllowed())
 		 if (isHighDPIAllowed())
 			 sdlflags |= SDL_WINDOW_ALLOW_HIGHDPI;
 			 sdlflags |= SDL_WINDOW_ALLOW_HIGHDPI;
+#endif
+
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+		Uint32 createflags = sdlflags & (~SDL_WINDOW_FULLSCREEN);
+
+		if (!createWindowAndContext(x, y, width, height, createflags, renderer))
+			return false;
 
 
+		if (f.fullscreen)
+		{
+			SDL_SetWindowFullscreenMode(window, fsmode);
+			SDL_SetWindowFullscreen(window, SDL_TRUE);
+		}
+#else
 		if (!createWindowAndContext(x, y, width, height, sdlflags, renderer))
 		if (!createWindowAndContext(x, y, width, height, sdlflags, renderer))
 			return false;
 			return false;
+#endif
 
 
 		needsetmode = true;
 		needsetmode = true;
 	}
 	}
@@ -662,9 +763,16 @@ bool Window::onSizeChanged(int width, int height)
 	if (!window)
 	if (!window)
 		return false;
 		return false;
 
 
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+	SDL_GetWindowSize(window, &windowWidth, &windowHeight);
+#else
 	windowWidth = width;
 	windowWidth = width;
 	windowHeight = height;
 	windowHeight = height;
+#endif
 
 
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+	if (SDL_GetWindowSizeInPixels(window, &pixelWidth, &pixelHeight) < 0)
+#else
 	// TODO: Use SDL_GetWindowSizeInPixels here when supported.
 	// TODO: Use SDL_GetWindowSizeInPixels here when supported.
 	if (glcontext != nullptr)
 	if (glcontext != nullptr)
 		SDL_GL_GetDrawableSize(window, &pixelWidth, &pixelHeight);
 		SDL_GL_GetDrawableSize(window, &pixelWidth, &pixelHeight);
@@ -677,6 +785,7 @@ bool Window::onSizeChanged(int width, int height)
 		SDL_Vulkan_GetDrawableSize(window, &pixelWidth, &pixelHeight);
 		SDL_Vulkan_GetDrawableSize(window, &pixelWidth, &pixelHeight);
 #endif
 #endif
 	else
 	else
+#endif
 	{
 	{
 		pixelWidth = width;
 		pixelWidth = width;
 		pixelHeight = height;
 		pixelHeight = height;
@@ -702,6 +811,9 @@ void Window::updateSettings(const WindowSettings &newsettings, bool updateGraphi
 	pixelWidth = windowWidth;
 	pixelWidth = windowWidth;
 	pixelHeight = windowHeight;
 	pixelHeight = windowHeight;
 
 
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+	SDL_GetWindowSizeInPixels(window, &pixelWidth, &pixelHeight);
+#else
 	// TODO: Use SDL_GetWindowSizeInPixels here when supported.
 	// TODO: Use SDL_GetWindowSizeInPixels here when supported.
 	if ((wflags & SDL_WINDOW_OPENGL) != 0)
 	if ((wflags & SDL_WINDOW_OPENGL) != 0)
 		SDL_GL_GetDrawableSize(window, &pixelWidth, &pixelHeight);
 		SDL_GL_GetDrawableSize(window, &pixelWidth, &pixelHeight);
@@ -713,8 +825,13 @@ void Window::updateSettings(const WindowSettings &newsettings, bool updateGraphi
 	else if ((wflags & SDL_WINDOW_VULKAN) != 0)
 	else if ((wflags & SDL_WINDOW_VULKAN) != 0)
 		SDL_Vulkan_GetDrawableSize(window, &pixelWidth, &pixelHeight);
 		SDL_Vulkan_GetDrawableSize(window, &pixelWidth, &pixelHeight);
 #endif
 #endif
+#endif
 
 
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+	if (((wflags & SDL_WINDOW_FULLSCREEN) == SDL_WINDOW_FULLSCREEN) && SDL_GetWindowFullscreenMode(window) == nullptr)
+#else
 	if ((wflags & SDL_WINDOW_FULLSCREEN_DESKTOP) == SDL_WINDOW_FULLSCREEN_DESKTOP)
 	if ((wflags & SDL_WINDOW_FULLSCREEN_DESKTOP) == SDL_WINDOW_FULLSCREEN_DESKTOP)
+#endif
 	{
 	{
 		settings.fullscreen = true;
 		settings.fullscreen = true;
 		settings.fstype = FULLSCREEN_DESKTOP;
 		settings.fstype = FULLSCREEN_DESKTOP;
@@ -744,7 +861,11 @@ void Window::updateSettings(const WindowSettings &newsettings, bool updateGraphi
 
 
 	getPosition(settings.x, settings.y, settings.displayindex);
 	getPosition(settings.x, settings.y, settings.displayindex);
 
 
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+	setHighDPIAllowed((wflags & SDL_WINDOW_HIGH_PIXEL_DENSITY) != 0);
+#else
 	setHighDPIAllowed((wflags & SDL_WINDOW_ALLOW_HIGHDPI) != 0);
 	setHighDPIAllowed((wflags & SDL_WINDOW_ALLOW_HIGHDPI) != 0);
+#endif
 
 
 	settings.usedpiscale = newsettings.usedpiscale;
 	settings.usedpiscale = newsettings.usedpiscale;
 
 
@@ -759,11 +880,19 @@ void Window::updateSettings(const WindowSettings &newsettings, bool updateGraphi
 	settings.stencil = newsettings.stencil;
 	settings.stencil = newsettings.stencil;
 	settings.depth = newsettings.depth;
 	settings.depth = newsettings.depth;
 
 
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+	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;
+#else
 	SDL_DisplayMode dmode = {};
 	SDL_DisplayMode dmode = {};
 	SDL_GetCurrentDisplayMode(settings.displayindex, &dmode);
 	SDL_GetCurrentDisplayMode(settings.displayindex, &dmode);
 
 
 	// May be 0 if the refresh rate can't be determined.
 	// May be 0 if the refresh rate can't be determined.
 	settings.refreshrate = (double) dmode.refresh_rate;
 	settings.refreshrate = (double) dmode.refresh_rate;
+#endif
 
 
 	// Update the viewport size now instead of waiting for event polling.
 	// Update the viewport size now instead of waiting for event polling.
 	if (updateGraphicsViewport && graphics.get())
 	if (updateGraphicsViewport && graphics.get())
@@ -821,7 +950,11 @@ void Window::close(bool allowExceptions)
 
 
 		// The old window may have generated pending events which are no longer
 		// The old window may have generated pending events which are no longer
 		// relevant. Destroy them all!
 		// relevant. Destroy them all!
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+		SDL_FlushEvents(SDL_EVENT_WINDOW_FIRST, SDL_EVENT_WINDOW_LAST);
+#else
 		SDL_FlushEvent(SDL_WINDOWEVENT);
 		SDL_FlushEvent(SDL_WINDOWEVENT);
+#endif
 	}
 	}
 
 
 	open = false;
 	open = false;
@@ -839,6 +972,21 @@ bool Window::setFullscreen(bool fullscreen, FullscreenType fstype)
 	newsettings.fullscreen = fullscreen;
 	newsettings.fullscreen = fullscreen;
 	newsettings.fstype = fstype;
 	newsettings.fstype = fstype;
 
 
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+	SDL_bool sdlflags = fullscreen;
+	if (fullscreen)
+	{
+		if (fstype == FULLSCREEN_DESKTOP)
+			SDL_SetWindowFullscreenMode(window, nullptr);
+		else
+		{
+			SDL_DisplayID displayid = SDL_GetDisplayForWindow(window);
+			const SDL_DisplayMode *mode = SDL_GetClosestFullscreenDisplayMode(displayid, windowWidth, windowHeight, 0, isHighDPIAllowed());
+			if (mode != nullptr)
+				SDL_SetWindowFullscreenMode(window, mode);
+		}
+	}
+#else
 	Uint32 sdlflags = 0;
 	Uint32 sdlflags = 0;
 
 
 	if (fullscreen)
 	if (fullscreen)
@@ -857,6 +1005,7 @@ bool Window::setFullscreen(bool fullscreen, FullscreenType fstype)
 			SDL_SetWindowDisplayMode(window, &mode);
 			SDL_SetWindowDisplayMode(window, &mode);
 		}
 		}
 	}
 	}
+#endif
 
 
 #ifdef LOVE_ANDROID
 #ifdef LOVE_ANDROID
 	love::android::setImmersive(fullscreen);
 	love::android::setImmersive(fullscreen);
@@ -881,12 +1030,21 @@ bool Window::setFullscreen(bool fullscreen)
 
 
 int Window::getDisplayCount() const
 int Window::getDisplayCount() const
 {
 {
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+	SDLDisplayIDs displayids;
+	return displayids.count;
+#else
 	return SDL_GetNumVideoDisplays();
 	return SDL_GetNumVideoDisplays();
+#endif
 }
 }
 
 
 const char *Window::getDisplayName(int displayindex) const
 const char *Window::getDisplayName(int displayindex) const
 {
 {
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+	const char *name = SDL_GetDisplayName(GetSDLDisplayIDForIndex(displayindex));
+#else
 	const char *name = SDL_GetDisplayName(displayindex);
 	const char *name = SDL_GetDisplayName(displayindex);
+#endif
 
 
 	if (name == nullptr)
 	if (name == nullptr)
 		throw love::Exception("Invalid display index: %d", displayindex + 1);
 		throw love::Exception("Invalid display index: %d", displayindex + 1);
@@ -896,7 +1054,11 @@ const char *Window::getDisplayName(int displayindex) const
 
 
 Window::DisplayOrientation Window::getDisplayOrientation(int displayindex) const
 Window::DisplayOrientation Window::getDisplayOrientation(int displayindex) const
 {
 {
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+	switch (SDL_GetCurrentDisplayOrientation(GetSDLDisplayIDForIndex(displayindex)))
+#else
 	switch (SDL_GetDisplayOrientation(displayindex))
 	switch (SDL_GetDisplayOrientation(displayindex))
+#endif
 	{
 	{
 		case SDL_ORIENTATION_UNKNOWN: return ORIENTATION_UNKNOWN;
 		case SDL_ORIENTATION_UNKNOWN: return ORIENTATION_UNKNOWN;
 		case SDL_ORIENTATION_LANDSCAPE: return ORIENTATION_LANDSCAPE;
 		case SDL_ORIENTATION_LANDSCAPE: return ORIENTATION_LANDSCAPE;
@@ -912,12 +1074,22 @@ std::vector<Window::WindowSize> Window::getFullscreenSizes(int displayindex) con
 {
 {
 	std::vector<WindowSize> sizes;
 	std::vector<WindowSize> sizes;
 
 
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+	int count = 0;
+	const SDL_DisplayMode **modes = SDL_GetFullscreenDisplayModes(GetSDLDisplayIDForIndex(displayindex), &count);
+
+	for (int i = 0; i < count; i++)
+	{
+		// TODO: other mode properties?
+		WindowSize w = {modes[i]->w, modes[i]->h};
+#else
 	for (int i = 0; i < SDL_GetNumDisplayModes(displayindex); i++)
 	for (int i = 0; i < SDL_GetNumDisplayModes(displayindex); i++)
 	{
 	{
 		SDL_DisplayMode mode = {};
 		SDL_DisplayMode mode = {};
 		SDL_GetDisplayMode(displayindex, i, &mode);
 		SDL_GetDisplayMode(displayindex, i, &mode);
 
 
 		WindowSize w = {mode.w, mode.h};
 		WindowSize w = {mode.w, mode.h};
+#endif
 
 
 		// SDL2's display mode list has multiple entries for modes of the same
 		// 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.
 		// size with different bits per pixel, so we need to filter those out.
@@ -925,11 +1097,24 @@ std::vector<Window::WindowSize> Window::getFullscreenSizes(int displayindex) con
 			sizes.push_back(w);
 			sizes.push_back(w);
 	}
 	}
 
 
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+	SDL_free(modes);
+#endif
+
 	return sizes;
 	return sizes;
 }
 }
 
 
 void Window::getDesktopDimensions(int displayindex, int &width, int &height) const
 void Window::getDesktopDimensions(int displayindex, int &width, int &height) const
 {
 {
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+	const SDL_DisplayMode *mode = SDL_GetDesktopDisplayMode(GetSDLDisplayIDForIndex(displayindex));
+	if (mode != nullptr)
+	{
+		// TODO: other properties?
+		width = mode->w;
+		height = mode->h;
+	}
+#else
 	if (displayindex >= 0 && displayindex < getDisplayCount())
 	if (displayindex >= 0 && displayindex < getDisplayCount())
 	{
 	{
 		SDL_DisplayMode mode = {};
 		SDL_DisplayMode mode = {};
@@ -937,6 +1122,7 @@ void Window::getDesktopDimensions(int displayindex, int &width, int &height) con
 		width = mode.w;
 		width = mode.w;
 		height = mode.h;
 		height = mode.h;
 	}
 	}
+#endif
 	else
 	else
 	{
 	{
 		width = 0;
 		width = 0;
@@ -972,7 +1158,21 @@ void Window::getPosition(int &x, int &y, int &displayindex)
 		return;
 		return;
 	}
 	}
 
 
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+	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;
+		}
+	}
+#else
 	displayindex = std::max(SDL_GetWindowDisplayIndex(window), 0);
 	displayindex = std::max(SDL_GetWindowDisplayIndex(window), 0);
+#endif
 
 
 	SDL_GetWindowPosition(window, &x, &y);
 	SDL_GetWindowPosition(window, &x, &y);
 
 
@@ -982,7 +1182,11 @@ void Window::getPosition(int &x, int &y, int &displayindex)
 	if (x != 0 || y != 0)
 	if (x != 0 || y != 0)
 	{
 	{
 		SDL_Rect displaybounds = {};
 		SDL_Rect displaybounds = {};
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+		SDL_GetDisplayBounds(displayid, &displaybounds);
+#else
 		SDL_GetDisplayBounds(displayindex, &displaybounds);
 		SDL_GetDisplayBounds(displayindex, &displaybounds);
+#endif
 
 
 		x -= displaybounds.x;
 		x -= displaybounds.x;
 		y -= displaybounds.y;
 		y -= displaybounds.y;
@@ -1047,6 +1251,7 @@ bool Window::setIcon(love::image::ImageData *imgd)
 	if (!window)
 	if (!window)
 		return false;
 		return false;
 
 
+#if !SDL_VERSION_ATLEAST(3, 0, 0)
 	Uint32 rmask, gmask, bmask, amask;
 	Uint32 rmask, gmask, bmask, amask;
 #ifdef LOVE_BIG_ENDIAN
 #ifdef LOVE_BIG_ENDIAN
 	rmask = 0xFF000000;
 	rmask = 0xFF000000;
@@ -1058,6 +1263,7 @@ bool Window::setIcon(love::image::ImageData *imgd)
 	gmask = 0x0000FF00;
 	gmask = 0x0000FF00;
 	bmask = 0x00FF0000;
 	bmask = 0x00FF0000;
 	amask = 0xFF000000;
 	amask = 0xFF000000;
+#endif
 #endif
 #endif
 
 
 	int w = imgd->getWidth();
 	int w = imgd->getWidth();
@@ -1070,14 +1276,22 @@ bool Window::setIcon(love::image::ImageData *imgd)
 	{
 	{
 		// We don't want another thread modifying the ImageData mid-copy.
 		// We don't want another thread modifying the ImageData mid-copy.
 		love::thread::Lock lock(imgd->getMutex());
 		love::thread::Lock lock(imgd->getMutex());
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+		sdlicon = SDL_CreateSurfaceFrom(imgd->getData(), w, h, pitch, SDL_PIXELFORMAT_RGBA8888);
+#else
 		sdlicon = SDL_CreateRGBSurfaceFrom(imgd->getData(), w, h, bytesperpixel * 8, pitch, rmask, gmask, bmask, amask);
 		sdlicon = SDL_CreateRGBSurfaceFrom(imgd->getData(), w, h, bytesperpixel * 8, pitch, rmask, gmask, bmask, amask);
+#endif
 	}
 	}
 
 
 	if (!sdlicon)
 	if (!sdlicon)
 		return false;
 		return false;
 
 
 	SDL_SetWindowIcon(window, sdlicon);
 	SDL_SetWindowIcon(window, sdlicon);
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+	SDL_DestroySurface(sdlicon);
+#else
 	SDL_FreeSurface(sdlicon);
 	SDL_FreeSurface(sdlicon);
+#endif
 
 
 	return true;
 	return true;
 }
 }
@@ -1095,8 +1309,18 @@ void Window::setVSync(int vsync)
 
 
 		// Check if adaptive vsync was requested but not supported, and fall
 		// Check if adaptive vsync was requested but not supported, and fall
 		// back to regular vsync if so.
 		// back to regular vsync if so.
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+		if (vsync == -1)
+		{
+			int actualvsync = 0;
+			SDL_GL_GetSwapInterval(&actualvsync);
+			if (actualvsync != -1)
+				SDL_GL_SetSwapInterval(1);
+		}
+#else
 		if (vsync == -1 && SDL_GL_GetSwapInterval() != -1)
 		if (vsync == -1 && SDL_GL_GetSwapInterval() != -1)
 			SDL_GL_SetSwapInterval(1);
 			SDL_GL_SetSwapInterval(1);
+#endif
 	}
 	}
 
 
 #ifdef LOVE_GRAPHICS_VULKAN
 #ifdef LOVE_GRAPHICS_VULKAN
@@ -1119,7 +1343,15 @@ void Window::setVSync(int vsync)
 int Window::getVSync() const
 int Window::getVSync() const
 {
 {
 	if (glcontext != nullptr)
 	if (glcontext != nullptr)
+	{
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+		int interval = 0;
+		SDL_GL_GetSwapInterval(&interval);
+		return interval;
+#else
 		return SDL_GL_GetSwapInterval();
 		return SDL_GL_GetSwapInterval();
+#endif
+	}
 
 
 #if defined(LOVE_GRAPHICS_METAL)
 #if defined(LOVE_GRAPHICS_METAL)
 	if (metalView != nullptr)
 	if (metalView != nullptr)
@@ -1154,7 +1386,11 @@ void Window::setDisplaySleepEnabled(bool enable)
 
 
 bool Window::isDisplaySleepEnabled() const
 bool Window::isDisplaySleepEnabled() const
 {
 {
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+	return SDL_ScreenSaverEnabled() != SDL_FALSE;
+#else
 	return SDL_IsScreenSaverEnabled() != SDL_FALSE;
 	return SDL_IsScreenSaverEnabled() != SDL_FALSE;
+#endif
 }
 }
 
 
 void Window::minimize()
 void Window::minimize()
@@ -1270,7 +1506,11 @@ bool Window::hasMouseFocus() const
 
 
 bool Window::isVisible() const
 bool Window::isVisible() const
 {
 {
+#if SDL_VERSION_ATLEAST(3, 0, 0)
+	return window && (SDL_GetWindowFlags(window) & SDL_WINDOW_HIDDEN) == 0;
+#else
 	return window && (SDL_GetWindowFlags(window) & SDL_WINDOW_SHOWN) != 0;
 	return window && (SDL_GetWindowFlags(window) & SDL_WINDOW_SHOWN) != 0;
+#endif
 }
 }
 
 
 void Window::setMouseGrab(bool grab)
 void Window::setMouseGrab(bool grab)