Browse Source

Added opt-in support for high-dpi mode in OS X when on a retina display (resolves issue #761).

Added a ‘highdpi’ boolean flag to t.window/love.window.setMode (defaults to false.)
When the window is actually in high-dpi mode on a supported display, the graphics width and height and the mouse position are in pixels, rather than ‘window coordinates’.

Added love.window.getPixelScale. Returns the scale factor of the window from user-space points to pixels (e.g. it will be 1 normally, and 2 on a retina display in OS X with high-dpi mode enabled.)
Alex Szpakowski 11 years ago
parent
commit
309193895a

+ 45 - 9
src/modules/event/sdl/Event.cpp

@@ -37,6 +37,24 @@ namespace event
 namespace sdl
 namespace sdl
 {
 {
 
 
+// SDL reports mouse coordinates in the window coordinate system in OS X, but
+// we want them in pixel coordinates (may be different with high-DPI enabled.)
+static void windowToPixelCoords(int *x, int *y)
+{
+	double scale = 1.0;
+
+	window::Window *window = (window::Window *) Module::findInstance("love.window.");
+	if (window != nullptr)
+		scale = window->getPixelScale();
+
+	if (x != nullptr)
+		*x = int(double(*x) * scale);
+
+	if (y != nullptr)
+		*y = int(double(*x) * scale);
+}
+
+
 const char *Event::getName() const
 const char *Event::getName() const
 {
 {
 	return "love.event.sdl";
 	return "love.event.sdl";
@@ -162,8 +180,11 @@ Message *Event::convert(const SDL_Event &e) const
 	case SDL_MOUSEBUTTONUP:
 	case SDL_MOUSEBUTTONUP:
 		if (buttons.find(e.button.button, button) && mouse::Mouse::getConstant(button, txt))
 		if (buttons.find(e.button.button, button) && mouse::Mouse::getConstant(button, txt))
 		{
 		{
-			arg1 = new Variant((double) e.button.x);
-			arg2 = new Variant((double) e.button.y);
+			int x = e.button.x;
+			int y = e.button.y;
+			windowToPixelCoords(&x, &y);
+			arg1 = new Variant((double) x);
+			arg2 = new Variant((double) y);
 			arg3 = new Variant(txt, strlen(txt));
 			arg3 = new Variant(txt, strlen(txt));
 			msg = new Message((e.type == SDL_MOUSEBUTTONDOWN) ?
 			msg = new Message((e.type == SDL_MOUSEBUTTONDOWN) ?
 							  "mousepressed" : "mousereleased",
 							  "mousepressed" : "mousereleased",
@@ -182,6 +203,7 @@ Message *Event::convert(const SDL_Event &e) const
 
 
 			int mx, my;
 			int mx, my;
 			SDL_GetMouseState(&mx, &my);
 			SDL_GetMouseState(&mx, &my);
+			windowToPixelCoords(&mx, &my);
 
 
 			arg1 = new Variant((double) mx);
 			arg1 = new Variant((double) mx);
 			arg2 = new Variant((double) my);
 			arg2 = new Variant((double) my);
@@ -366,7 +388,7 @@ Message *Event::convertJoystickEvent(const SDL_Event &e) const
 Message *Event::convertWindowEvent(const SDL_Event &e) const
 Message *Event::convertWindowEvent(const SDL_Event &e) const
 {
 {
 	Message *msg = 0;
 	Message *msg = 0;
-	Variant *arg1, *arg2;
+	Variant *arg1, *arg2, *arg3, *arg4;
 	window::Window *win = 0;
 	window::Window *win = 0;
 
 
 	if (e.type != SDL_WINDOWEVENT)
 	if (e.type != SDL_WINDOWEVENT)
@@ -402,17 +424,31 @@ Message *Event::convertWindowEvent(const SDL_Event &e) const
 		win = (window::Window *) Module::findInstance("love.window.");
 		win = (window::Window *) Module::findInstance("love.window.");
 		if (win)
 		if (win)
 		{
 		{
+			int px_w = e.window.data1;
+			int px_h = e.window.data2;
+
+#if SDL_VERSION_ATLEAST(2,0,1)
+			SDL_Window *sdlwin = SDL_GetWindowFromID(e.window.windowID);
+			if (sdlwin)
+				SDL_GL_GetDrawableSize(sdlwin, &px_w, &px_h);
+#endif
+
 			win->onWindowResize(e.window.data1, e.window.data2);
 			win->onWindowResize(e.window.data1, e.window.data2);
 
 
 			graphics::Graphics *gfx = (graphics::Graphics *) Module::findInstance("love.graphics.");
 			graphics::Graphics *gfx = (graphics::Graphics *) Module::findInstance("love.graphics.");
 			if (gfx)
 			if (gfx)
-				gfx->setViewportSize(e.window.data1, e.window.data2);
+				gfx->setViewportSize(px_w, px_h);
+
+			arg1 = new Variant((double) px_w);
+			arg2 = new Variant((double) px_h);
+			arg3 = new Variant((double) e.window.data1);
+			arg4 = new Variant((double) e.window.data2);
+			msg = new Message("resize", arg1, arg2, arg3, arg4);
+			arg1->release();
+			arg2->release();
+			arg3->release();
+			arg4->release();
 		}
 		}
-		arg1 = new Variant((double) e.window.data1);
-		arg2 = new Variant((double) e.window.data2);
-		msg = new Message("resize", arg1, arg2);
-		arg1->release();
-		arg2->release();
 		break;
 		break;
 	}
 	}
 
 

+ 47 - 4
src/modules/mouse/sdl/Mouse.cpp

@@ -32,6 +32,39 @@ namespace mouse
 namespace sdl
 namespace sdl
 {
 {
 
 
+// SDL reports mouse coordinates in the window coordinate system in OS X, but
+// we want them in pixel coordinates (may be different with high-DPI enabled.)
+static void windowToPixelCoords(int *x, int *y)
+{
+	double scale = 1.0;
+
+	love::window::Window *window = love::window::sdl::Window::getSingleton();
+	if (window != nullptr)
+		scale = window->getPixelScale();
+
+	if (x != nullptr)
+		*x = int(double(*x) * scale);
+
+	if (y != nullptr)
+		*y = int(double(*x) * scale);
+}
+
+// And vice versa for setting mouse coordinates.
+static void pixelToWindowCoords(int *x, int *y)
+{
+	double scale = 1.0;
+
+	love::window::Window *window = love::window::sdl::Window::getSingleton();
+	if (window != nullptr)
+		scale = window->getPixelScale();
+
+	if (x != nullptr)
+		*x = int(double(*x) / scale);
+
+	if (y != nullptr)
+		*y = int(double(*x) / scale);
+}
+
 const char *Mouse::getName() const
 const char *Mouse::getName() const
 {
 {
 	return "love.mouse.sdl";
 	return "love.mouse.sdl";
@@ -98,30 +131,40 @@ love::mouse::Cursor *Mouse::getCursor() const
 int Mouse::getX() const
 int Mouse::getX() const
 {
 {
 	int x;
 	int x;
-	SDL_GetMouseState(&x, 0);
+	SDL_GetMouseState(&x, nullptr);
+	windowToPixelCoords(&x, nullptr);
+
 	return x;
 	return x;
 }
 }
 
 
 int Mouse::getY() const
 int Mouse::getY() const
 {
 {
 	int y;
 	int y;
-	SDL_GetMouseState(0, &y);
+	SDL_GetMouseState(nullptr, &y);
+	windowToPixelCoords(nullptr, &y);
+
 	return y;
 	return y;
 }
 }
 
 
 void Mouse::getPosition(int &x, int &y) const
 void Mouse::getPosition(int &x, int &y) const
 {
 {
-	SDL_GetMouseState(&x, &y);
+	int mx, my;
+	SDL_GetMouseState(&mx, &my);
+	windowToPixelCoords(&mx, &my);
+
+	x = mx;
+	y = my;
 }
 }
 
 
 void Mouse::setPosition(int x, int y)
 void Mouse::setPosition(int x, int y)
 {
 {
 	love::window::Window *window = love::window::sdl::Window::getSingleton();
 	love::window::Window *window = love::window::sdl::Window::getSingleton();
 
 
-	SDL_Window *handle = NULL;
+	SDL_Window *handle = nullptr;
 	if (window)
 	if (window)
 		handle = (SDL_Window *) window->getHandle();
 		handle = (SDL_Window *) window->getHandle();
 
 
+	pixelToWindowCoords(&x, &y);
 	SDL_WarpMouseInWindow(handle, x, y);
 	SDL_WarpMouseInWindow(handle, x, y);
 }
 }
 
 

+ 21 - 19
src/modules/window/Window.cpp

@@ -26,19 +26,19 @@ namespace love
 namespace window
 namespace window
 {
 {
 
 
-Window *Window::singleton = NULL;
+Window *Window::singleton = nullptr;
 
 
 Window::~Window()
 Window::~Window()
 {
 {
 	if (singleton == this)
 	if (singleton == this)
-		singleton = NULL;
+		singleton = nullptr;
 }
 }
 
 
 void Window::swapBuffers()
 void Window::swapBuffers()
 {
 {
 }
 }
 
 
-WindowAttributes::WindowAttributes()
+WindowSettings::WindowSettings()
 	: fullscreen(false)
 	: fullscreen(false)
 	, fstype(Window::FULLSCREEN_TYPE_NORMAL)
 	, fstype(Window::FULLSCREEN_TYPE_NORMAL)
 	, vsync(true)
 	, vsync(true)
@@ -49,6 +49,7 @@ WindowAttributes::WindowAttributes()
 	, borderless(false)
 	, borderless(false)
 	, centered(true)
 	, centered(true)
 	, display(0)
 	, display(0)
+	, highdpi(false)
 {
 {
 }
 }
 
 
@@ -62,31 +63,32 @@ bool Window::getConstant(Window::FullscreenType in, const char *&out)
 	return fullscreenTypes.find(in, out);
 	return fullscreenTypes.find(in, out);
 }
 }
 
 
-bool Window::getConstant(const char *in, Window::Attribute &out)
+bool Window::getConstant(const char *in, Window::Setting &out)
 {
 {
-	return attributes.find(in, out);
+	return settings.find(in, out);
 }
 }
 
 
-bool Window::getConstant(Window::Attribute in, const char *&out)
+bool Window::getConstant(Window::Setting in, const char *&out)
 {
 {
-	return attributes.find(in, out);
+	return settings.find(in, out);
 }
 }
 
 
-StringMap<Window::Attribute, Window::ATTRIB_MAX_ENUM>::Entry Window::attributeEntries[] =
+StringMap<Window::Setting, Window::SETTING_MAX_ENUM>::Entry Window::settingEntries[] =
 {
 {
-	{"fullscreen", ATTRIB_FULLSCREEN},
-	{"fullscreentype", ATTRIB_FULLSCREEN_TYPE},
-	{"vsync", ATTRIB_VSYNC},
-	{"fsaa", ATTRIB_FSAA},
-	{"resizable", ATTRIB_RESIZABLE},
-	{"minwidth", ATTRIB_MIN_WIDTH},
-	{"minheight", ATTRIB_MIN_HEIGHT},
-	{"borderless", ATTRIB_BORDERLESS},
-	{"centered", ATTRIB_CENTERED},
-	{"display", ATTRIB_DISPLAY}
+	{"fullscreen", SETTING_FULLSCREEN},
+	{"fullscreentype", SETTING_FULLSCREEN_TYPE},
+	{"vsync", SETTING_VSYNC},
+	{"fsaa", SETTING_FSAA},
+	{"resizable", SETTING_RESIZABLE},
+	{"minwidth", SETTING_MIN_WIDTH},
+	{"minheight", SETTING_MIN_HEIGHT},
+	{"borderless", SETTING_BORDERLESS},
+	{"centered", SETTING_CENTERED},
+	{"display", SETTING_DISPLAY},
+	{"highdpi", SETTING_HIGHDPI},
 };
 };
 
 
-StringMap<Window::Attribute, Window::ATTRIB_MAX_ENUM> Window::attributes(Window::attributeEntries, sizeof(Window::attributeEntries));
+StringMap<Window::Setting, Window::SETTING_MAX_ENUM> Window::settings(Window::settingEntries, sizeof(Window::settingEntries));
 
 
 StringMap<Window::FullscreenType, Window::FULLSCREEN_TYPE_MAX_ENUM>::Entry Window::fullscreenTypeEntries[] =
 StringMap<Window::FullscreenType, Window::FULLSCREEN_TYPE_MAX_ENUM>::Entry Window::fullscreenTypeEntries[] =
 {
 {

+ 29 - 23
src/modules/window/Window.h

@@ -37,26 +37,27 @@ namespace window
 
 
 // Forward-declared so it can be used in the class methods. We can't define the
 // Forward-declared so it can be used in the class methods. We can't define the
 // whole thing here because it uses the Window::Type enum.
 // whole thing here because it uses the Window::Type enum.
-struct WindowAttributes;
+struct WindowSettings;
 
 
 class Window : public Module
 class Window : public Module
 {
 {
 public:
 public:
 
 
-	// Types of window attributes.
-	enum Attribute
+	// Different window settings.
+	enum Setting
 	{
 	{
-		ATTRIB_FULLSCREEN,
-		ATTRIB_FULLSCREEN_TYPE,
-		ATTRIB_VSYNC,
-		ATTRIB_FSAA,
-		ATTRIB_RESIZABLE,
-		ATTRIB_MIN_WIDTH,
-		ATTRIB_MIN_HEIGHT,
-		ATTRIB_BORDERLESS,
-		ATTRIB_CENTERED,
-		ATTRIB_DISPLAY,
-		ATTRIB_MAX_ENUM
+		SETTING_FULLSCREEN,
+		SETTING_FULLSCREEN_TYPE,
+		SETTING_VSYNC,
+		SETTING_FSAA,
+		SETTING_RESIZABLE,
+		SETTING_MIN_WIDTH,
+		SETTING_MIN_HEIGHT,
+		SETTING_BORDERLESS,
+		SETTING_CENTERED,
+		SETTING_DISPLAY,
+		SETTING_HIGHDPI,
+		SETTING_MAX_ENUM
 	};
 	};
 
 
 	enum FullscreenType
 	enum FullscreenType
@@ -74,8 +75,8 @@ public:
 
 
 	virtual ~Window();
 	virtual ~Window();
 
 
-	virtual bool setWindow(int width = 800, int height = 600, WindowAttributes *attribs = 0) = 0;
-	virtual void getWindow(int &width, int &height, WindowAttributes &attribs) = 0;
+	virtual bool setWindow(int width = 800, int height = 600, WindowSettings *settings = nullptr) = 0;
+	virtual void getWindow(int &width, int &height, WindowSettings &settings) = 0;
 
 
 	virtual bool setFullscreen(bool fullscreen, FullscreenType fstype) = 0;
 	virtual bool setFullscreen(bool fullscreen, FullscreenType fstype) = 0;
 	virtual bool setFullscreen(bool fullscreen) = 0;
 	virtual bool setFullscreen(bool fullscreen) = 0;
@@ -113,14 +114,16 @@ public:
 	virtual void setMouseGrab(bool grab) = 0;
 	virtual void setMouseGrab(bool grab) = 0;
 	virtual bool isMouseGrabbed() const = 0;
 	virtual bool isMouseGrabbed() const = 0;
 
 
+	virtual double getPixelScale() const = 0;
+
 	virtual const void *getHandle() const = 0;
 	virtual const void *getHandle() const = 0;
 
 
 	//virtual static Window *createSingleton() = 0;
 	//virtual static Window *createSingleton() = 0;
 	//virtual static Window *getSingleton() = 0;
 	//virtual static Window *getSingleton() = 0;
 	// No virtual statics, of course, but you are supposed to implement these statics.
 	// No virtual statics, of course, but you are supposed to implement these statics.
 
 
-	static bool getConstant(const char *in, Attribute &out);
-	static bool getConstant(Attribute in, const char *&out);
+	static bool getConstant(const char *in, Setting &out);
+	static bool getConstant(Setting in, const char *&out);
 
 
 	static bool getConstant(const char *in, FullscreenType &out);
 	static bool getConstant(const char *in, FullscreenType &out);
 	static bool getConstant(FullscreenType in, const char *&out);
 	static bool getConstant(FullscreenType in, const char *&out);
@@ -131,17 +134,18 @@ protected:
 
 
 private:
 private:
 
 
-	static StringMap<Attribute, ATTRIB_MAX_ENUM>::Entry attributeEntries[];
-	static StringMap<Attribute, ATTRIB_MAX_ENUM> attributes;
+	static StringMap<Setting, SETTING_MAX_ENUM>::Entry settingEntries[];
+	static StringMap<Setting, SETTING_MAX_ENUM> settings;
 
 
 	static StringMap<FullscreenType, FULLSCREEN_TYPE_MAX_ENUM>::Entry fullscreenTypeEntries[];
 	static StringMap<FullscreenType, FULLSCREEN_TYPE_MAX_ENUM>::Entry fullscreenTypeEntries[];
 	static StringMap<FullscreenType, FULLSCREEN_TYPE_MAX_ENUM> fullscreenTypes;
 	static StringMap<FullscreenType, FULLSCREEN_TYPE_MAX_ENUM> fullscreenTypes;
 
 
 }; // Window
 }; // Window
 
 
-struct WindowAttributes
+struct WindowSettings
 {
 {
-	WindowAttributes();
+	WindowSettings();
+
 	bool fullscreen; // = false
 	bool fullscreen; // = false
 	Window::FullscreenType fstype; // = FULLSCREEN_TYPE_NORMAL
 	Window::FullscreenType fstype; // = FULLSCREEN_TYPE_NORMAL
 	bool vsync; // = true
 	bool vsync; // = true
@@ -152,7 +156,9 @@ struct WindowAttributes
 	bool borderless; // = false
 	bool borderless; // = false
 	bool centered; // = true
 	bool centered; // = true
 	int display; // = 0
 	int display; // = 0
-}; // WindowFlags
+	bool highdpi; // false
+
+}; // WindowSettings
 
 
 } // window
 } // window
 } // love
 } // love

+ 96 - 39
src/modules/window/sdl/Window.cpp

@@ -63,21 +63,21 @@ Window::~Window()
 Window::_currentMode::_currentMode()
 Window::_currentMode::_currentMode()
 	: width(800)
 	: width(800)
 	, height(600)
 	, height(600)
-	, attribs()
+	, settings()
 	, icon(0)
 	, icon(0)
 {
 {
 }
 }
 
 
-bool Window::setWindow(int width, int height, WindowAttributes *attribs)
+bool Window::setWindow(int width, int height, WindowSettings *settings)
 {
 {
 	graphics::Graphics *gfx = (graphics::Graphics *) Module::findInstance("love.graphics.");
 	graphics::Graphics *gfx = (graphics::Graphics *) Module::findInstance("love.graphics.");
-	if (gfx)
+	if (gfx != nullptr)
 		gfx->unSetMode();
 		gfx->unSetMode();
 
 
-	WindowAttributes f;
+	WindowSettings f;
 
 
-	if (attribs)
-		f = *attribs;
+	if (settings)
+		f = *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);
@@ -116,15 +116,28 @@ bool Window::setWindow(int width, int height, WindowAttributes *attribs)
 	if (f.borderless)
 	if (f.borderless)
 		sdlflags |= SDL_WINDOW_BORDERLESS;
 		sdlflags |= SDL_WINDOW_BORDERLESS;
 
 
+#if SDL_VERSION_ATLEAST(2,0,1)
+	if (f.highdpi)
+		sdlflags |= SDL_WINDOW_ALLOW_HIGHDPI;
+#endif
+
 	// Destroy and recreate the window if the dimensions or flags have changed.
 	// Destroy and recreate the window if the dimensions or flags have changed.
 	if (window)
 	if (window)
 	{
 	{
 		int curdisplay = SDL_GetWindowDisplayIndex(window);
 		int curdisplay = SDL_GetWindowDisplayIndex(window);
 		Uint32 wflags = SDL_GetWindowFlags(window);
 		Uint32 wflags = SDL_GetWindowFlags(window);
-		wflags &= (SDL_WINDOW_OPENGL | SDL_WINDOW_FULLSCREEN_DESKTOP | SDL_WINDOW_RESIZABLE | SDL_WINDOW_BORDERLESS);
+
+		Uint32 testflags = SDL_WINDOW_OPENGL | SDL_WINDOW_FULLSCREEN_DESKTOP
+			| SDL_WINDOW_RESIZABLE | SDL_WINDOW_BORDERLESS;
+
+#if SDL_VERSION_ATLEAST(2,0,1)
+		testflags |= SDL_WINDOW_ALLOW_HIGHDPI;
+#endif
+
+		wflags &= testflags;
 
 
 		if (sdlflags != wflags || width != curMode.width || height != curMode.height
 		if (sdlflags != wflags || width != curMode.width || height != curMode.height
-			|| f.display != curdisplay || f.fsaa != curMode.attribs.fsaa)
+			|| f.display != curdisplay || f.fsaa != curMode.settings.fsaa)
 		{
 		{
 			SDL_DestroyWindow(window);
 			SDL_DestroyWindow(window);
 			window = 0;
 			window = 0;
@@ -182,10 +195,19 @@ bool Window::setWindow(int width, int height, WindowAttributes *attribs)
 
 
 	created = true;
 	created = true;
 
 
-	updateAttributes(f);
+	updateSettings(f);
 
 
-	if (gfx)
-		gfx->setMode(curMode.width, curMode.height);
+	if (gfx != nullptr)
+	{
+		int width = curMode.width;
+		int height = curMode.height;
+
+#if SDL_VERSION_ATLEAST(2,0,1)
+		SDL_GL_GetDrawableSize(window, &width, &height);
+#endif
+
+		gfx->setMode(width, height);
+	}
 
 
 	// Make sure the mouse keeps its previous grab setting.
 	// Make sure the mouse keeps its previous grab setting.
 	setMouseGrab(mouseGrabbed);
 	setMouseGrab(mouseGrabbed);
@@ -250,8 +272,8 @@ bool Window::setContext(int fsaa, bool vsync)
 		fsaa = (buffers > 0) ? samples : 0;
 		fsaa = (buffers > 0) ? samples : 0;
 	}
 	}
 
 
-	curMode.attribs.fsaa = fsaa;
-	curMode.attribs.vsync = SDL_GL_GetSwapInterval() != 0;
+	curMode.settings.fsaa = fsaa;
+	curMode.settings.vsync = SDL_GL_GetSwapInterval() != 0;
 
 
 	return true;
 	return true;
 }
 }
@@ -271,7 +293,7 @@ void Window::setWindowGLAttributes(int fsaa) const
 	SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, (fsaa > 0) ? fsaa : 0);
 	SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, (fsaa > 0) ? fsaa : 0);
 }
 }
 
 
-void Window::updateAttributes(const WindowAttributes &newattribs)
+void Window::updateSettings(const WindowSettings &newsettings)
 {
 {
 	Uint32 wflags = SDL_GetWindowFlags(window);
 	Uint32 wflags = SDL_GetWindowFlags(window);
 
 
@@ -280,54 +302,60 @@ void Window::updateAttributes(const WindowAttributes &newattribs)
 
 
 	if ((wflags & SDL_WINDOW_FULLSCREEN_DESKTOP) == SDL_WINDOW_FULLSCREEN_DESKTOP)
 	if ((wflags & SDL_WINDOW_FULLSCREEN_DESKTOP) == SDL_WINDOW_FULLSCREEN_DESKTOP)
 	{
 	{
-		curMode.attribs.fullscreen = true;
-		curMode.attribs.fstype = FULLSCREEN_TYPE_DESKTOP;
+		curMode.settings.fullscreen = true;
+		curMode.settings.fstype = FULLSCREEN_TYPE_DESKTOP;
 	}
 	}
 	else if ((wflags & SDL_WINDOW_FULLSCREEN) == SDL_WINDOW_FULLSCREEN)
 	else if ((wflags & SDL_WINDOW_FULLSCREEN) == SDL_WINDOW_FULLSCREEN)
 	{
 	{
-		curMode.attribs.fullscreen = true;
-		curMode.attribs.fstype = FULLSCREEN_TYPE_NORMAL;
+		curMode.settings.fullscreen = true;
+		curMode.settings.fstype = FULLSCREEN_TYPE_NORMAL;
 	}
 	}
 	else
 	else
 	{
 	{
-		curMode.attribs.fullscreen = false;
-		curMode.attribs.fstype = newattribs.fstype;
+		curMode.settings.fullscreen = false;
+		curMode.settings.fstype = newsettings.fstype;
 	}
 	}
 
 
 	// The min width/height is set to 0 internally in SDL when in fullscreen.
 	// The min width/height is set to 0 internally in SDL when in fullscreen.
-	if (curMode.attribs.fullscreen)
+	if (curMode.settings.fullscreen)
 	{
 	{
-		curMode.attribs.minwidth = newattribs.minwidth;
-		curMode.attribs.minheight = newattribs.minheight;
+		curMode.settings.minwidth = newsettings.minwidth;
+		curMode.settings.minheight = newsettings.minheight;
 	}
 	}
 	else
 	else
-		SDL_GetWindowMinimumSize(window, &curMode.attribs.minwidth, &curMode.attribs.minheight);
+		SDL_GetWindowMinimumSize(window, &curMode.settings.minwidth, &curMode.settings.minheight);
 
 
-	curMode.attribs.resizable = (wflags & SDL_WINDOW_RESIZABLE) != 0;
-	curMode.attribs.borderless = (wflags & SDL_WINDOW_BORDERLESS) != 0;
-	curMode.attribs.centered = newattribs.centered;
-	curMode.attribs.display = std::max(SDL_GetWindowDisplayIndex(window), 0);
+	curMode.settings.resizable = (wflags & SDL_WINDOW_RESIZABLE) != 0;
+	curMode.settings.borderless = (wflags & SDL_WINDOW_BORDERLESS) != 0;
+	curMode.settings.centered = newsettings.centered;
+	curMode.settings.display = std::max(SDL_GetWindowDisplayIndex(window), 0);
+
+#if SDL_VERSION_ATLEAST(2,0,1)
+	curMode.settings.highdpi = (wflags & SDL_WINDOW_ALLOW_HIGHDPI) != 0;
+#else
+	curMode.settings.highdpi = false;
+#endif
 
 
 	// Only minimize on focus loss if the window is in exclusive-fullscreen
 	// Only minimize on focus loss if the window is in exclusive-fullscreen
 	// mode (mimics behaviour of SDL 2.0.2+).
 	// mode (mimics behaviour of SDL 2.0.2+).
 	// In OS X we always disable this to prevent dock minimization weirdness.
 	// In OS X we always disable this to prevent dock minimization weirdness.
 #ifndef LOVE_MACOSX
 #ifndef LOVE_MACOSX
-	if (curMode.attribs.fullscreen && curMode.attribs.fstype == FULLSCREEN_TYPE_NORMAL)
+	if (curMode.settings.fullscreen && curMode.settings.fstype == FULLSCREEN_TYPE_NORMAL)
 		SDL_SetHint(SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS, "1");
 		SDL_SetHint(SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS, "1");
 	else
 	else
 #endif
 #endif
 		SDL_SetHint(SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS, "0");
 		SDL_SetHint(SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS, "0");
 }
 }
 
 
-void Window::getWindow(int &width, int &height, WindowAttributes &attribs)
+void Window::getWindow(int &width, int &height, WindowSettings &settings)
 {
 {
 	// Window position may be different from creation - update display index.
 	// Window position may be different from creation - update display index.
 	if (window)
 	if (window)
-		curMode.attribs.display = std::max(SDL_GetWindowDisplayIndex(window), 0);
+		curMode.settings.display = std::max(SDL_GetWindowDisplayIndex(window), 0);
 
 
 	width = curMode.width;
 	width = curMode.width;
 	height = curMode.height;
 	height = curMode.height;
-	attribs = curMode.attribs;
+	settings = curMode.settings;
 }
 }
 
 
 bool Window::setFullscreen(bool fullscreen, Window::FullscreenType fstype)
 bool Window::setFullscreen(bool fullscreen, Window::FullscreenType fstype)
@@ -335,9 +363,9 @@ bool Window::setFullscreen(bool fullscreen, Window::FullscreenType fstype)
 	if (!window)
 	if (!window)
 		return false;
 		return false;
 
 
-	WindowAttributes newattribs = curMode.attribs;
-	newattribs.fullscreen = fullscreen;
-	newattribs.fstype = fstype;
+	WindowSettings newsettings = curMode.settings;
+	newsettings.fullscreen = fullscreen;
+	newsettings.fstype = fstype;
 
 
 	Uint32 sdlflags = 0;
 	Uint32 sdlflags = 0;
 
 
@@ -361,12 +389,21 @@ bool Window::setFullscreen(bool fullscreen, Window::FullscreenType fstype)
 	if (SDL_SetWindowFullscreen(window, sdlflags) == 0)
 	if (SDL_SetWindowFullscreen(window, sdlflags) == 0)
 	{
 	{
 		SDL_GL_MakeCurrent(window, context);
 		SDL_GL_MakeCurrent(window, context);
-		updateAttributes(newattribs);
+		updateSettings(newsettings);
 
 
 		// Update the viewport size now instead of waiting for event polling.
 		// Update the viewport size now instead of waiting for event polling.
 		graphics::Graphics *gfx = (graphics::Graphics *) Module::findInstance("love.graphics.");
 		graphics::Graphics *gfx = (graphics::Graphics *) Module::findInstance("love.graphics.");
-		if (gfx)
-			gfx->setViewportSize(curMode.width, curMode.height);
+		if (gfx != nullptr)
+		{
+			int width = curMode.width;
+			int height = curMode.height;
+
+#if SDL_VERSION_ATLEAST(2,0,1)
+			SDL_GL_GetDrawableSize(window, &width, &height);
+#endif
+
+			gfx->setViewportSize(width, height);
+		}
 
 
 		return true;
 		return true;
 	}
 	}
@@ -376,7 +413,7 @@ bool Window::setFullscreen(bool fullscreen, Window::FullscreenType fstype)
 
 
 bool Window::setFullscreen(bool fullscreen)
 bool Window::setFullscreen(bool fullscreen)
 {
 {
-	return setFullscreen(fullscreen, curMode.attribs.fstype);
+	return setFullscreen(fullscreen, curMode.settings.fstype);
 }
 }
 
 
 int Window::getDisplayCount() const
 int Window::getDisplayCount() const
@@ -559,6 +596,26 @@ bool Window::isMouseGrabbed() const
 		return mouseGrabbed;
 		return mouseGrabbed;
 }
 }
 
 
+double Window::getPixelScale() const
+{
+	double scale = 1.0;
+
+#if SDL_VERSION_ATLEAST(2,0,1)
+	if (window)
+	{
+		int wheight;
+		SDL_GetWindowSize(window, nullptr, &wheight);
+
+		int dheight = wheight;
+		SDL_GL_GetDrawableSize(window, nullptr, &dheight);
+
+		scale = (double) dheight / wheight;
+	}
+#endif
+
+	return scale;
+}
+
 const void *Window::getHandle() const
 const void *Window::getHandle() const
 {
 {
 	return window;
 	return window;

+ 7 - 5
src/modules/window/sdl/Window.h

@@ -41,8 +41,8 @@ public:
 	Window();
 	Window();
 	~Window();
 	~Window();
 
 
-	bool setWindow(int width = 800, int height = 600, WindowAttributes *attribs = 0);
-	void getWindow(int &width, int &height, WindowAttributes &attribs);
+	bool setWindow(int width = 800, int height = 600, WindowSettings *settings = nullptr);
+	void getWindow(int &width, int &height, WindowSettings &settings);
 
 
 	bool setFullscreen(bool fullscreen, FullscreenType fstype);
 	bool setFullscreen(bool fullscreen, FullscreenType fstype);
 	bool setFullscreen(bool fullscreen);
 	bool setFullscreen(bool fullscreen);
@@ -79,6 +79,8 @@ public:
 	void setMouseGrab(bool grab);
 	void setMouseGrab(bool grab);
 	bool isMouseGrabbed() const;
 	bool isMouseGrabbed() const;
 
 
+	double getPixelScale() const;
+
 	const void *getHandle() const;
 	const void *getHandle() const;
 
 
 	static love::window::Window *createSingleton();
 	static love::window::Window *createSingleton();
@@ -91,8 +93,8 @@ private:
 	bool setContext(int fsaa, bool vsync);
 	bool setContext(int fsaa, bool vsync);
 	void setWindowGLAttributes(int fsaa) const;
 	void setWindowGLAttributes(int fsaa) const;
 
 
-	// Update the saved window attribs based on the window's actual state.
-	void updateAttributes(const WindowAttributes &newattribs);
+	// Update the saved window settings based on the window's actual state.
+	void updateSettings(const WindowSettings &newsettings);
 
 
 	std::string windowTitle;
 	std::string windowTitle;
 
 
@@ -102,7 +104,7 @@ private:
 
 
 		int width;
 		int width;
 		int height;
 		int height;
-		WindowAttributes attribs;
+		WindowSettings settings;
 		love::image::ImageData *icon;
 		love::image::ImageData *icon;
 
 
 	} curMode;
 	} curMode;

+ 58 - 48
src/modules/window/wrap_Window.cpp

@@ -26,7 +26,7 @@ namespace love
 namespace window
 namespace window
 {
 {
 
 
-static Window *instance = 0;
+static Window *instance = nullptr;
 
 
 int w_getDisplayCount(lua_State *L)
 int w_getDisplayCount(lua_State *L)
 {
 {
@@ -34,10 +34,10 @@ int w_getDisplayCount(lua_State *L)
 	return 1;
 	return 1;
 }
 }
 
 
-static const char *attribName(Window::Attribute attrib)
+static const char *settingName(Window::Setting setting)
 {
 {
 	const char *name = nullptr;
 	const char *name = nullptr;
-	Window::getConstant(attrib, name);
+	Window::getConstant(setting, name);
 	return name;
 	return name;
 }
 }
 
 
@@ -62,91 +62,94 @@ int w_setMode(lua_State *L)
 			return luax_typerror(L, -2, "string");
 			return luax_typerror(L, -2, "string");
 
 
 		const char *key = luaL_checkstring(L, -2);
 		const char *key = luaL_checkstring(L, -2);
-		Window::Attribute attrib;
+		Window::Setting setting;
 
 
-		if (!Window::getConstant(key, attrib))
-			return luaL_error(L, "Invalid window attribute: %s", key);
+		if (!Window::getConstant(key, setting))
+			return luaL_error(L, "Invalid window setting: %s", key);
 
 
 		lua_pop(L, 1);
 		lua_pop(L, 1);
 	}
 	}
 
 
-	WindowAttributes attribs;
+	WindowSettings settings;
 
 
-	lua_getfield(L, 3, attribName(Window::ATTRIB_FULLSCREEN_TYPE));
+	lua_getfield(L, 3, settingName(Window::SETTING_FULLSCREEN_TYPE));
 	if (!lua_isnoneornil(L, -1))
 	if (!lua_isnoneornil(L, -1))
 	{
 	{
 		const char *typestr = luaL_checkstring(L, -1);
 		const char *typestr = luaL_checkstring(L, -1);
-
-		if (!Window::getConstant(typestr, attribs.fstype))
+		if (!Window::getConstant(typestr, settings.fstype))
 			return luaL_error(L, "Invalid fullscreen type: %s", typestr);
 			return luaL_error(L, "Invalid fullscreen type: %s", typestr);
 	}
 	}
 	else
 	else
 	{
 	{
 		// Default to "normal" fullscreen.
 		// Default to "normal" fullscreen.
-		attribs.fstype = Window::FULLSCREEN_TYPE_NORMAL;
+		settings.fstype = Window::FULLSCREEN_TYPE_NORMAL;
 	}
 	}
 	lua_pop(L, 1);
 	lua_pop(L, 1);
 
 
-	attribs.fullscreen = luax_boolflag(L, 3, attribName(Window::ATTRIB_FULLSCREEN), false);
-	attribs.vsync = luax_boolflag(L, 3, attribName(Window::ATTRIB_VSYNC), true);
-	attribs.fsaa = luax_intflag(L, 3, attribName(Window::ATTRIB_FSAA), 0);
-	attribs.resizable = luax_boolflag(L, 3, attribName(Window::ATTRIB_RESIZABLE), false);
-	attribs.minwidth = luax_intflag(L, 3, attribName(Window::ATTRIB_MIN_WIDTH), 1);
-	attribs.minheight = luax_intflag(L, 3, attribName(Window::ATTRIB_MIN_HEIGHT), 1);
-	attribs.borderless = luax_boolflag(L, 3, attribName(Window::ATTRIB_BORDERLESS), false);
-	attribs.centered = luax_boolflag(L, 3, attribName(Window::ATTRIB_CENTERED), true);
-	attribs.display = luax_intflag(L, 3, attribName(Window::ATTRIB_DISPLAY), 1);
+	settings.fullscreen = luax_boolflag(L, 3, settingName(Window::SETTING_FULLSCREEN), false);
+	settings.vsync = luax_boolflag(L, 3, settingName(Window::SETTING_VSYNC), true);
+	settings.fsaa = luax_intflag(L, 3, settingName(Window::SETTING_FSAA), 0);
+	settings.resizable = luax_boolflag(L, 3, settingName(Window::SETTING_RESIZABLE), false);
+	settings.minwidth = luax_intflag(L, 3, settingName(Window::SETTING_MIN_WIDTH), 1);
+	settings.minheight = luax_intflag(L, 3, settingName(Window::SETTING_MIN_HEIGHT), 1);
+	settings.borderless = luax_boolflag(L, 3, settingName(Window::SETTING_BORDERLESS), false);
+	settings.centered = luax_boolflag(L, 3, settingName(Window::SETTING_CENTERED), true);
+	settings.display = luax_intflag(L, 3, settingName(Window::SETTING_DISPLAY), 1);
+	settings.highdpi = luax_boolflag(L, 3, settingName(Window::SETTING_HIGHDPI), false);
 
 
 	// Display index is 1-based in Lua and 0-based internally.
 	// Display index is 1-based in Lua and 0-based internally.
-	attribs.display--;
+	settings.display--;
 
 
-	EXCEPT_GUARD(luax_pushboolean(L, instance->setWindow(w, h, &attribs));)
+	EXCEPT_GUARD(luax_pushboolean(L, instance->setWindow(w, h, &settings));)
 	return 1;
 	return 1;
 }
 }
 
 
 int w_getMode(lua_State *L)
 int w_getMode(lua_State *L)
 {
 {
 	int w, h;
 	int w, h;
-	WindowAttributes attribs;
-	instance->getWindow(w, h, attribs);
+	WindowSettings settings;
+	instance->getWindow(w, h, settings);
 	lua_pushnumber(L, w);
 	lua_pushnumber(L, w);
 	lua_pushnumber(L, h);
 	lua_pushnumber(L, h);
 
 
 	lua_newtable(L);
 	lua_newtable(L);
 
 
 	const char *fstypestr = "normal";
 	const char *fstypestr = "normal";
-	Window::getConstant(attribs.fstype, fstypestr);
+	Window::getConstant(settings.fstype, fstypestr);
 
 
 	lua_pushstring(L, fstypestr);
 	lua_pushstring(L, fstypestr);
-	lua_setfield(L, -2, attribName(Window::ATTRIB_FULLSCREEN_TYPE));
+	lua_setfield(L, -2, settingName(Window::SETTING_FULLSCREEN_TYPE));
 
 
-	luax_pushboolean(L, attribs.fullscreen);
-	lua_setfield(L, -2, attribName(Window::ATTRIB_FULLSCREEN));
+	luax_pushboolean(L, settings.fullscreen);
+	lua_setfield(L, -2, settingName(Window::SETTING_FULLSCREEN));
 
 
-	luax_pushboolean(L, attribs.vsync);
-	lua_setfield(L, -2, attribName(Window::ATTRIB_VSYNC));
+	luax_pushboolean(L, settings.vsync);
+	lua_setfield(L, -2, settingName(Window::SETTING_VSYNC));
 
 
-	lua_pushinteger(L, attribs.fsaa);
-	lua_setfield(L, -2, attribName(Window::ATTRIB_FSAA));
+	lua_pushinteger(L, settings.fsaa);
+	lua_setfield(L, -2, settingName(Window::SETTING_FSAA));
 
 
-	luax_pushboolean(L, attribs.resizable);
-	lua_setfield(L, -2, attribName(Window::ATTRIB_RESIZABLE));
+	luax_pushboolean(L, settings.resizable);
+	lua_setfield(L, -2, settingName(Window::SETTING_RESIZABLE));
 
 
-	lua_pushinteger(L, attribs.minwidth);
-	lua_setfield(L, -2, attribName(Window::ATTRIB_MIN_WIDTH));
+	lua_pushinteger(L, settings.minwidth);
+	lua_setfield(L, -2, settingName(Window::SETTING_MIN_WIDTH));
 
 
-	lua_pushinteger(L, attribs.minheight);
-	lua_setfield(L, -2, attribName(Window::ATTRIB_MIN_HEIGHT));
+	lua_pushinteger(L, settings.minheight);
+	lua_setfield(L, -2, settingName(Window::SETTING_MIN_HEIGHT));
 
 
-	luax_pushboolean(L, attribs.borderless);
-	lua_setfield(L, -2, attribName(Window::ATTRIB_BORDERLESS));
+	luax_pushboolean(L, settings.borderless);
+	lua_setfield(L, -2, settingName(Window::SETTING_BORDERLESS));
 
 
-	luax_pushboolean(L, attribs.centered);
-	lua_setfield(L, -2, attribName(Window::ATTRIB_CENTERED));
+	luax_pushboolean(L, settings.centered);
+	lua_setfield(L, -2, settingName(Window::SETTING_CENTERED));
 
 
 	// Display index is 0-based internally and 1-based in Lua.
 	// Display index is 0-based internally and 1-based in Lua.
-	lua_pushinteger(L, attribs.display + 1);
-	lua_setfield(L, -2, attribName(Window::ATTRIB_DISPLAY));
+	lua_pushinteger(L, settings.display + 1);
+	lua_setfield(L, -2, settingName(Window::SETTING_DISPLAY));
+
+	luax_pushboolean(L, settings.highdpi);
+	lua_setfield(L, -2, settingName(Window::SETTING_HIGHDPI));
 
 
 	return 3;
 	return 3;
 }
 }
@@ -202,14 +205,14 @@ int w_setFullscreen(lua_State *L)
 int w_getFullscreen(lua_State *L)
 int w_getFullscreen(lua_State *L)
 {
 {
 	int w, h;
 	int w, h;
-	WindowAttributes attribs;
-	instance->getWindow(w, h, attribs);
+	WindowSettings settings;
+	instance->getWindow(w, h, settings);
 
 
 	const char *typestr;
 	const char *typestr;
-	if (!Window::getConstant(attribs.fstype, typestr))
+	if (!Window::getConstant(settings.fstype, typestr))
 		luaL_error(L, "Unknown fullscreen type.");
 		luaL_error(L, "Unknown fullscreen type.");
 
 
-	luax_pushboolean(L, attribs.fullscreen);
+	luax_pushboolean(L, settings.fullscreen);
 	lua_pushstring(L, typestr);
 	lua_pushstring(L, typestr);
 	return 2;
 	return 2;
 }
 }
@@ -300,6 +303,12 @@ int w_isVisible(lua_State *L)
 	return 1;
 	return 1;
 }
 }
 
 
+int w_getPixelScale(lua_State *L)
+{
+	lua_pushnumber(L, instance->getPixelScale());
+	return 1;
+}
+
 static const luaL_Reg functions[] =
 static const luaL_Reg functions[] =
 {
 {
 	{ "getDisplayCount", w_getDisplayCount },
 	{ "getDisplayCount", w_getDisplayCount },
@@ -320,6 +329,7 @@ static const luaL_Reg functions[] =
 	{ "hasFocus", w_hasFocus },
 	{ "hasFocus", w_hasFocus },
 	{ "hasMouseFocus", w_hasMouseFocus },
 	{ "hasMouseFocus", w_hasMouseFocus },
 	{ "isVisible", w_isVisible },
 	{ "isVisible", w_isVisible },
+	{ "getPixelScale", w_getPixelScale },
 	{ 0, 0 }
 	{ 0, 0 }
 };
 };
 
 

+ 1 - 0
src/modules/window/wrap_Window.h

@@ -47,6 +47,7 @@ int w_getTitle(lua_State *L);
 int w_hasFocus(lua_State *L);
 int w_hasFocus(lua_State *L);
 int w_hasMouseFocus(lua_State *L);
 int w_hasMouseFocus(lua_State *L);
 int w_isVisible(lua_State *L);
 int w_isVisible(lua_State *L);
+int w_getPixelScale(lua_State *L);
 extern "C" LOVE_EXPORT int luaopen_love_window(lua_State *L);
 extern "C" LOVE_EXPORT int luaopen_love_window(lua_State *L);
 
 
 } // window
 } // window

+ 1 - 0
src/scripts/boot.lua

@@ -300,6 +300,7 @@ function love.init()
 			borderless = false,
 			borderless = false,
 			resizable = false,
 			resizable = false,
 			centered = true,
 			centered = true,
+			highdpi = false,
 		},
 		},
 		modules = {
 		modules = {
 			event = true,
 			event = true,

+ 3 - 1
src/scripts/boot.lua.h

@@ -27,7 +27,7 @@ const unsigned char boot_lua[] =
 
 
 	0x2d, 0x2d, 0x5b, 0x5b, 0x0a,
 	0x2d, 0x2d, 0x5b, 0x5b, 0x0a,
 	0x43, 0x6f, 0x70, 0x79, 0x72, 0x69, 0x67, 0x68, 0x74, 0x20, 0x28, 0x63, 0x29, 0x20, 0x32, 0x30, 0x30, 0x36, 
 	0x43, 0x6f, 0x70, 0x79, 0x72, 0x69, 0x67, 0x68, 0x74, 0x20, 0x28, 0x63, 0x29, 0x20, 0x32, 0x30, 0x30, 0x36, 
-	0x2d, 0x32, 0x30, 0x31, 0x33, 0x20, 0x4c, 0x4f, 0x56, 0x45, 0x20, 0x44, 0x65, 0x76, 0x65, 0x6c, 0x6f, 0x70, 
+	0x2d, 0x32, 0x30, 0x31, 0x34, 0x20, 0x4c, 0x4f, 0x56, 0x45, 0x20, 0x44, 0x65, 0x76, 0x65, 0x6c, 0x6f, 0x70, 
 	0x6d, 0x65, 0x6e, 0x74, 0x20, 0x54, 0x65, 0x61, 0x6d, 0x0a,
 	0x6d, 0x65, 0x6e, 0x74, 0x20, 0x54, 0x65, 0x61, 0x6d, 0x0a,
 	0x54, 0x68, 0x69, 0x73, 0x20, 0x73, 0x6f, 0x66, 0x74, 0x77, 0x61, 0x72, 0x65, 0x20, 0x69, 0x73, 0x20, 0x70, 
 	0x54, 0x68, 0x69, 0x73, 0x20, 0x73, 0x6f, 0x66, 0x74, 0x77, 0x61, 0x72, 0x65, 0x20, 0x69, 0x73, 0x20, 0x70, 
 	0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x64, 0x20, 0x27, 0x61, 0x73, 0x2d, 0x69, 0x73, 0x27, 0x2c, 0x20, 0x77, 
 	0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x64, 0x20, 0x27, 0x61, 0x73, 0x2d, 0x69, 0x73, 0x27, 0x2c, 0x20, 0x77, 
@@ -537,6 +537,8 @@ const unsigned char boot_lua[] =
 	0x73, 0x65, 0x2c, 0x0a,
 	0x73, 0x65, 0x2c, 0x0a,
 	0x09, 0x09, 0x09, 0x63, 0x65, 0x6e, 0x74, 0x65, 0x72, 0x65, 0x64, 0x20, 0x3d, 0x20, 0x74, 0x72, 0x75, 0x65, 
 	0x09, 0x09, 0x09, 0x63, 0x65, 0x6e, 0x74, 0x65, 0x72, 0x65, 0x64, 0x20, 0x3d, 0x20, 0x74, 0x72, 0x75, 0x65, 
 	0x2c, 0x0a,
 	0x2c, 0x0a,
+	0x09, 0x09, 0x09, 0x68, 0x69, 0x67, 0x68, 0x64, 0x70, 0x69, 0x20, 0x3d, 0x20, 0x66, 0x61, 0x6c, 0x73, 0x65, 
+	0x2c, 0x0a,
 	0x09, 0x09, 0x7d, 0x2c, 0x0a,
 	0x09, 0x09, 0x7d, 0x2c, 0x0a,
 	0x09, 0x09, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x73, 0x20, 0x3d, 0x20, 0x7b, 0x0a,
 	0x09, 0x09, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x73, 0x20, 0x3d, 0x20, 0x7b, 0x0a,
 	0x09, 0x09, 0x09, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x20, 0x3d, 0x20, 0x74, 0x72, 0x75, 0x65, 0x2c, 0x0a,
 	0x09, 0x09, 0x09, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x20, 0x3d, 0x20, 0x74, 0x72, 0x75, 0x65, 0x2c, 0x0a,