Index: src/Cube.mm
==================================================================
--- src/Cube.mm
+++ src/Cube.mm
@@ -2,103 +2,31 @@
 
 #include "cube.h"
 
 OF_APPLICATION_DELEGATE(Cube)
 
-@implementation Cube
-- (void)showMessage:(OFString *)msg
-{
-#ifdef _WIN32
-	MessageBoxW(
-	    NULL, msg.UTF16String, L"cube fatal error", MB_OK | MB_SYSTEMMODAL);
-#else
-	[OFStdOut writeString:msg];
-#endif
-}
-
-- (void)applicationWillTerminate:(OFNotification *)notification
-{
-	stop();
-	disconnect(true);
-	writecfg();
-	cleangl();
-	cleansound();
-	cleanupserver();
-	SDL_ShowCursor(1);
-	SDL_Quit();
-}
-
-void
-quit() // normal exit
-{
-	writeservercfg();
-	[OFApplication.sharedApplication terminateWithStatus:0];
-}
-
-void
-fatal(OFString *s, OFString *o) // failure exit
-{
-	OFString *msg =
-	    [OFString stringWithFormat:@"%@%@ (%s)\n", s, o, SDL_GetError()];
-
-	OFApplication *app = OFApplication.sharedApplication;
-	[(Cube *)app.delegate showMessage:msg];
-	[app terminateWithStatus:1];
-}
-
-void *
-alloc(int s) // for some big chunks... most other allocs use the memory pool
-{
-	void *b = calloc(1, s);
-	if (!b)
-		fatal(@"out of memory!");
-	return b;
-}
-
-SDL_Window *window;
-int scr_w = 640;
-int scr_h = 480;
-
-void
-screenshot()
-{
-	SDL_Surface *image;
-	SDL_Surface *temp;
-	int idx;
-	if (image = SDL_CreateRGBSurface(SDL_SWSURFACE, scr_w, scr_h, 24,
-	        0x0000FF, 0x00FF00, 0xFF0000, 0)) {
-		if (temp = SDL_CreateRGBSurface(SDL_SWSURFACE, scr_w, scr_h, 24,
-		        0x0000FF, 0x00FF00, 0xFF0000, 0)) {
-			glReadPixels(0, 0, scr_w, scr_h, GL_RGB,
-			    GL_UNSIGNED_BYTE, image->pixels);
-			for (idx = 0; idx < scr_h; idx++) {
-				char *dest =
-				    (char *)temp->pixels + 3 * scr_w * idx;
-				memcpy(dest,
-				    (char *)image->pixels +
-				        3 * scr_w * (scr_h - 1 - idx),
-				    3 * scr_w);
-				endianswap(dest, 3, scr_w);
-			};
-			sprintf_sd(buf)(
-			    "screenshots/screenshot_%d.bmp", lastmillis);
-			SDL_SaveBMP(temp, path(buf));
-			SDL_FreeSurface(temp);
-		};
-		SDL_FreeSurface(image);
-	};
-}
-
-COMMAND(screenshot, ARG_NONE)
-COMMAND(quit, ARG_NONE)
-
-bool keyrepeat = false;
-
 VARF(gamespeed, 10, 100, 1000, if (multiplayer()) gamespeed = 100);
 VARP(minmillis, 0, 5, 1000);
 
-int framesinmap = 0;
+@implementation Cube {
+	int _width, _height;
+}
+
++ (Cube *)sharedInstance
+{
+	return (Cube *)OFApplication.sharedApplication.delegate;
+}
+
+- (instancetype)init
+{
+	self = [super init];
+
+	_width = 1920;
+	_height = 1080;
+
+	return self;
+}
 
 - (void)applicationDidFinishLaunching:(OFNotification *)notification
 {
 	bool dedicated, windowed;
 	int par = 0, uprate = 0, maxcl = 4;
@@ -123,20 +51,20 @@
 	    [OFOptionsParser parserWithOptions:options];
 	OFUnichar option;
 	while ((option = [optionsParser nextOption]) != '\0') {
 		switch (option) {
 		case 'w':
-			scr_w = optionsParser.argument.longLongValue;
+			_width = (int)optionsParser.argument.longLongValue;
 			break;
 		case 'h':
-			scr_h = optionsParser.argument.longLongValue;
+			_height = (int)optionsParser.argument.longLongValue;
 			break;
 		case 'u':
-			uprate = optionsParser.argument.longLongValue;
+			uprate = (int)optionsParser.argument.longLongValue;
 			break;
 		case 'c':
-			maxcl = optionsParser.argument.longLongValue;
+			maxcl = (int)optionsParser.argument.longLongValue;
 			break;
 		case ':':
 		case '=':
 		case '?':
 			conoutf(@"unknown commandline option");
@@ -170,25 +98,24 @@
 	if (SDL_InitSubSystem(SDL_INIT_VIDEO) < 0)
 		fatal(@"Unable to initialize SDL Video");
 
 	log("video: mode");
 	SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
-	if ((window = SDL_CreateWindow("cube engine", SDL_WINDOWPOS_UNDEFINED,
-	         SDL_WINDOWPOS_UNDEFINED, scr_w, scr_h,
+	if ((_window = SDL_CreateWindow("cube engine", SDL_WINDOWPOS_UNDEFINED,
+	         SDL_WINDOWPOS_UNDEFINED, _width, _height,
 	         SDL_WINDOW_SHOWN | SDL_WINDOW_OPENGL |
 	             (!windowed ? SDL_WINDOW_FULLSCREEN : 0))) == NULL ||
-	    SDL_GL_CreateContext(window) == NULL)
+	    SDL_GL_CreateContext(_window) == NULL)
 		fatal(@"Unable to create OpenGL screen");
 
 	log("video: misc");
-	SDL_SetWindowGrab(window, SDL_TRUE);
+	SDL_SetWindowGrab(_window, SDL_TRUE);
 	SDL_SetRelativeMouseMode(SDL_TRUE);
-	keyrepeat = false;
 	SDL_ShowCursor(0);
 
 	log("gl");
-	gl_init(scr_w, scr_h);
+	gl_init(_width, _height);
 
 	log("basetex");
 	int xs, ys;
 	if (!installtex(2, path(newstring("data/newchars.png")), xs, ys) ||
 	    !installtex(3, path(newstring("data/martin/base.png")), xs, ys) ||
@@ -230,66 +157,164 @@
 			lastmillis = millis - 200;
 		else if (millis - lastmillis < 1)
 			lastmillis = millis - 1;
 		if (millis - lastmillis < minmillis)
 			SDL_Delay(minmillis - (millis - lastmillis));
+
 		cleardlights();
 		updateworld(millis);
+
 		if (!demoplayback)
 			serverslice((int)time(NULL), 0);
+
 		static float fps = 30.0f;
 		fps = (1000.0f / curtime + fps * 50) / 51;
+
 		computeraytable(player1->o.x, player1->o.y);
-		readdepth(scr_w, scr_h);
-		SDL_GL_SwapWindow(window);
+		readdepth(_width, _height);
+		SDL_GL_SwapWindow(_window);
 		extern void updatevol();
 		updatevol();
-		if (framesinmap++ <
-		    5) // cheap hack to get rid of initial sparklies, even when
-		       // triple buffering etc.
-		{
+
+		// cheap hack to get rid of initial sparklies, even when triple
+		// buffering etc.
+		if (_framesInMap++ < 5) {
 			player1->yaw += 5;
-			gl_drawframe(scr_w, scr_h, fps);
+			gl_drawframe(_width, _height, fps);
 			player1->yaw -= 5;
 		}
-		gl_drawframe(scr_w, scr_h, fps);
+
+		gl_drawframe(_width, _height, fps);
+
 		SDL_Event event;
 		int lasttype = 0, lastbut = 0;
 		while (SDL_PollEvent(&event)) {
 			switch (event.type) {
 			case SDL_QUIT:
-				quit();
+				[self quit];
 				break;
-
 			case SDL_KEYDOWN:
 			case SDL_KEYUP:
-				if (keyrepeat || event.key.repeat == 0)
+				if (_repeatsKeys || event.key.repeat == 0)
 					keypress(event.key.keysym.sym,
 					    event.key.state == SDL_PRESSED,
 					    event.key.keysym.sym);
 				break;
-
 			case SDL_MOUSEMOTION:
 				if (ignore) {
 					ignore--;
 					break;
 				}
 				mousemove(event.motion.xrel, event.motion.yrel);
 				break;
-
 			case SDL_MOUSEBUTTONDOWN:
 			case SDL_MOUSEBUTTONUP:
 				if (lasttype == event.type &&
 				    lastbut == event.button.button)
-					break; // why?? get event twice without
-					       // it
+					// why?? get event twice without it
+					break;
+
 				keypress(-event.button.button,
 				    event.button.state != 0, 0);
 				lasttype = event.type;
 				lastbut = event.button.button;
 				break;
 			}
 		}
 	}
-	quit();
+
+	[self quit];
+}
+
+- (void)applicationWillTerminate:(OFNotification *)notification
+{
+	stop();
+	disconnect(true);
+	writecfg();
+	cleangl();
+	cleansound();
+	cleanupserver();
+	SDL_ShowCursor(1);
+	SDL_Quit();
+}
+
+- (void)showMessage:(OFString *)msg
+{
+#ifdef _WIN32
+	MessageBoxW(
+	    NULL, msg.UTF16String, L"cube fatal error", MB_OK | MB_SYSTEMMODAL);
+#else
+	[OFStdOut writeString:msg];
+#endif
+}
+
+- (void)screenshot
+{
+	SDL_Surface *image;
+	SDL_Surface *temp;
+
+	if ((image = SDL_CreateRGBSurface(SDL_SWSURFACE, _width, _height, 24,
+	         0x0000FF, 0x00FF00, 0xFF0000, 0)) != NULL) {
+		if ((temp = SDL_CreateRGBSurface(SDL_SWSURFACE, _width, _height,
+		         24, 0x0000FF, 0x00FF00, 0xFF0000, 0)) != NULL) {
+			glReadPixels(0, 0, _width, _height, GL_RGB,
+			    GL_UNSIGNED_BYTE, image->pixels);
+
+			for (int idx = 0; idx < _height; idx++) {
+				char *dest =
+				    (char *)temp->pixels + 3 * _width * idx;
+				memcpy(dest,
+				    (char *)image->pixels +
+				        3 * _width * (_height - 1 - idx),
+				    3 * _width);
+				endianswap(dest, 3, _width);
+			}
+
+			sprintf_sd(buf)(
+			    "screenshots/screenshot_%d.bmp", lastmillis);
+			SDL_SaveBMP(temp, path(buf));
+			SDL_FreeSurface(temp);
+		}
+
+		SDL_FreeSurface(image);
+	}
+}
+
+- (void)quit
+{
+	writeservercfg();
+	[OFApplication terminateWithStatus:0];
 }
 @end
+
+void
+fatal(OFString *s, OFString *o) // failure exit
+{
+	OFString *msg =
+	    [OFString stringWithFormat:@"%@%@ (%s)\n", s, o, SDL_GetError()];
+
+	[Cube.sharedInstance showMessage:msg];
+	[OFApplication terminateWithStatus:1];
+}
+
+void *
+alloc(int s) // for some big chunks... most other allocs use the memory pool
+{
+	void *b = calloc(1, s);
+	if (!b)
+		fatal(@"out of memory!");
+	return b;
+}
+
+void
+quit() // normal exit
+{
+	[Cube.sharedInstance quit];
+}
+COMMAND(quit, ARG_NONE)
+
+void
+screenshot()
+{
+	[Cube.sharedInstance screenshot];
+}
+COMMAND(screenshot, ARG_NONE)

Index: src/clientgame.mm
==================================================================
--- src/clientgame.mm
+++ src/clientgame.mm
@@ -25,12 +25,10 @@
 
 int lastmillis = 0;
 int curtime = 10;
 OFString *clientmap;
 
-extern int framesinmap;
-
 OFString *
 getclientmap()
 {
 	return clientmap;
 }
@@ -517,10 +515,10 @@
 	setvar(@"gamespeed", 100);
 	setvar(@"fog", 180);
 	setvar(@"fogcolour", 0x8099B3);
 	showscores(false);
 	intermission = false;
-	framesinmap = 0;
+	Cube.sharedInstance.framesInMap = 0;
 	conoutf(@"game mode is %s", modestr(gamemode));
 }
 
 COMMANDN(map, changemap, ARG_1STR)

Index: src/console.mm
==================================================================
--- src/console.mm
+++ src/console.mm
@@ -130,11 +130,11 @@
 	if (saycommandon)
 		SDL_StartTextInput();
 	else
 		SDL_StopTextInput();
 	if (!editmode)
-		keyrepeat = saycommandon;
+		Cube.sharedInstance.repeatsKeys = saycommandon;
 	if (!init)
 		init = "";
 	strcpy_s(commandbuf, init);
 }
 COMMAND(saycommand, ARG_VARI)

Index: src/cube.h
==================================================================
--- src/cube.h
+++ src/cube.h
@@ -1,12 +1,20 @@
 // one big bad include file for the whole engine... nasty!
 
 #import <ObjFW/ObjFW.h>
+
+#define gamma gamma__
+#include <SDL2/SDL.h>
+#undef gamma
 
 #include "tools.h"
 
 @interface Cube : OFObject <OFApplicationDelegate>
+@property (class, readonly, nonatomic) Cube *sharedInstance;
+@property (readonly, nonatomic) SDL_Window *window;
+@property (nonatomic) bool repeatsKeys;
+@property (nonatomic) int framesInMap;
 @end
 
 enum // block types, order matters!
 {
 	SOLID = 0, // entirely solid cube [only specifies wtex]

Index: src/editing.mm
==================================================================
--- src/editing.mm
+++ src/editing.mm
@@ -67,11 +67,11 @@
 		if (m_classicsp)
 			monsterclear(); // all monsters back at their spawns for
 			                // editing
 		projreset();
 	}
-	keyrepeat = editmode;
+	Cube.sharedInstance.repeatsKeys = editmode;
 	selset = false;
 	editing = editmode;
 }
 COMMANDN(edittoggle, toggleedit, ARG_NONE)
 

Index: src/protos.h
==================================================================
--- src/protos.h
+++ src/protos.h
@@ -140,12 +140,10 @@
 extern int isoccluded(float vx, float vy, float cx, float cy, float csize);
 
 // main
 extern void fatal(OFString *s, OFString *o = @"");
 extern void *alloc(int s);
-extern SDL_Window *window;
-extern bool keyrepeat;
 
 // rendertext
 extern void draw_text(char *str, int left, int top, int gl_num);
 extern void draw_textf(char *fstr, int left, int top, int gl_num, ...);
 extern int text_width(char *str);

Index: src/rendergl.mm
==================================================================
--- src/rendergl.mm
+++ src/rendergl.mm
@@ -275,11 +275,12 @@
 	float f = gamma / 100.0f;
 	Uint16 ramp[256];
 
 	SDL_CalculateGammaRamp(f, ramp);
 
-	if (SDL_SetWindowGammaRamp(window, ramp, ramp, ramp) == -1) {
+	if (SDL_SetWindowGammaRamp(
+	        Cube.sharedInstance.window, ramp, ramp, ramp) == -1) {
 		conoutf(
 		    @"Could not set gamma (card/driver doesn't support it?)");
 		conoutf(@"sdl: %s", SDL_GetError());
 	}
 })