From 91f83d4c555f28fe3c16c967b97e9c5aa9e4f67c Mon Sep 17 00:00:00 2001
From: Christoph Oelckers <coelckers@users.noreply.github.com>
Date: Sat, 14 Dec 2019 17:15:17 +0100
Subject: [PATCH] - input code cleanup and addition of ZDoom's joystick code,
 which is not fully connected yet.

---
 source/CMakeLists.txt              |   6 +
 source/build/src/sdlayer.cpp       | 557 +--------------------
 source/build/src/sdlkeytrans.cpp   | 263 ----------
 source/common/gameconfigfile.cpp   |   1 -
 source/common/gamecontrol.cpp      | 285 +----------
 source/common/gamecontrol.h        |   1 -
 source/common/gamecvars.cpp        |   5 +-
 source/common/input/i_gui.cpp      |  87 ++++
 source/common/input/i_input.cpp    | 750 +++++++++++++++++++++++++++++
 source/common/input/i_joystick.cpp | 342 +++++++++++++
 source/common/input/m_joy.cpp      | 331 +++++++++++++
 source/common/input/m_joy.h        |  65 +++
 source/common/inputstate.cpp       |   2 +
 source/common/inputstate.h         |   2 +
 source/mact/src/control.cpp        |   6 +-
 15 files changed, 1611 insertions(+), 1092 deletions(-)
 delete mode 100644 source/build/src/sdlkeytrans.cpp
 create mode 100644 source/common/input/i_gui.cpp
 create mode 100644 source/common/input/i_input.cpp
 create mode 100644 source/common/input/i_joystick.cpp
 create mode 100644 source/common/input/m_joy.cpp
 create mode 100644 source/common/input/m_joy.h

diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt
index 676b71140..e0a045216 100644
--- a/source/CMakeLists.txt
+++ b/source/CMakeLists.txt
@@ -624,6 +624,7 @@ file( GLOB HEADER_FILES
 	common/filesystem/*.h
 	common/music/*.h
 	common/menu/*.h
+	common/input/*.h
 
 	build/src/*.h
 	thirdparty/include/*.h
@@ -837,6 +838,11 @@ set (PCH_SOURCES
 	common/menu/menuinput.cpp
 	common/menu/messagebox.cpp
 	common/menu/optionmenu.cpp
+	
+	common/input/i_gui.cpp
+	common/input/i_joystick.cpp
+	common/input/i_input.cpp
+	common/input/m_joy.cpp
 )
 
 if( MSVC )
diff --git a/source/build/src/sdlayer.cpp b/source/build/src/sdlayer.cpp
index 9cc9992d9..16f27e654 100644
--- a/source/build/src/sdlayer.cpp
+++ b/source/build/src/sdlayer.cpp
@@ -78,6 +78,8 @@ double g_beforeSwapTime;
 GameInterface* gi;
 FArgs* Args;
 
+void buildkeytranslationtable();;
+
 #if !defined STARTUP_SETUP_WINDOW
 int32_t startwin_open(void) { return 0; }
 int32_t startwin_close(void) { return 0; }
@@ -143,7 +145,6 @@ static int32_t vsync_renderlayer;
 
 //#define KEY_PRINT_DEBUG
 
-#include "sdlkeytrans.cpp"
 
 static SDL_Surface *appicon = NULL;
 #if !defined __APPLE__ && !defined EDUKE32_TOUCH_DEVICES
@@ -377,7 +378,7 @@ void wm_setapptitle(const char *name)
 # include <execinfo.h>
 #endif
 
-static inline char grabmouse_low(char a);
+char grabmouse_low(char a);
 
 #ifndef __ANDROID__
 static void attach_debugger_here(void) {}
@@ -924,57 +925,6 @@ const char *joyGetName(int32_t what, int32_t num)
 }
 
 
-//
-// initmouse() -- init mouse input
-//
-void mouseInit(void)
-{
-    mouseGrabInput(g_mouseEnabled = g_mouseLockedToWindow);  // FIXME - SA
-}
-
-//
-// uninitmouse() -- uninit mouse input
-//
-void mouseUninit(void)
-{
-    mouseGrabInput(0);
-    g_mouseEnabled = 0;
-}
-
-
-//
-// grabmouse_low() -- show/hide mouse cursor, lower level (doesn't check state).
-//                    furthermore return 0 if successful.
-//
-
-static inline char grabmouse_low(char a)
-{
-    /* FIXME: Maybe it's better to make sure that grabmouse_low
-       is called only when a window is ready?                */
-    if (sdl_window)
-        SDL_SetWindowGrab(sdl_window, a ? SDL_TRUE : SDL_FALSE);
-    return SDL_SetRelativeMouseMode(a ? SDL_TRUE : SDL_FALSE);
-}
-
-//
-// grabmouse() -- show/hide mouse cursor
-//
-void mouseGrabInput(bool grab)
-{
-    if (appactive && g_mouseEnabled)
-    {
-        if ((grab != g_mouseGrabbed) && !grabmouse_low(grab))
-            g_mouseGrabbed = grab;
-    }
-    else
-        g_mouseGrabbed = grab;
-
-	inputState.MouseSetPos(0, 0);
-	SDL_ShowCursor(!grab ? SDL_ENABLE : SDL_DISABLE);
-	if (grab) GUICapture &= ~1;
-	else GUICapture |= 1;
-}
-
 //
 // setjoydeadzone() -- sets the dead and saturation zones for the joystick
 //
@@ -1026,6 +976,20 @@ static int sortmodes(const void *a_, const void *b_)
 
 static char modeschecked=0;
 
+void WindowMoved(int x, int y)
+{
+    if (windowpos)
+    {
+        windowx = x;
+        windowy = y;
+    }
+
+    r_displayindex = SDL_GetWindowDisplayIndex(sdl_window);
+    modeschecked = 0;
+    videoGetModes();
+}
+
+
 #if SDL_MAJOR_VERSION != 1
 void videoGetModes(void)
 {
@@ -1606,493 +1570,6 @@ static inline SDL_Surface *loadappicon(void)
 //
 //
 
-int32_t handleevents_peekkeys(void)
-{
-    SDL_PumpEvents();
-
-    return SDL_PeepEvents(NULL, 1, SDL_PEEKEVENT, SDL_KEYDOWN, SDL_KEYDOWN);
-}
-
-static void PostMouseMove(int x, int y)
-{
-	static int lastx = 0, lasty = 0;
-	event_t ev = { 0,0,0,0,0,0,0 };
-
-	ev.x = x;
-	ev.y = y;
-	lastx = x;
-	lasty = y;
-	if (ev.x | ev.y)
-	{
-		ev.type = EV_Mouse;
-		D_PostEvent(&ev);
-	}
-}
-
-CVAR(Bool, m_noprescale, false, CVAR_ARCHIVE | CVAR_GLOBALCONFIG)
-
-static void MouseRead()
-{
-	int x, y;
-
-#if 0
-	if (NativeMouse)
-	{
-		return;
-	}
-#endif
-
-	SDL_GetRelativeMouseState(&x, &y);
-	if (!m_noprescale)
-	{
-		x *= 3;
-		y *= 2;
-	}
-	if (x | y)
-	{
-		PostMouseMove(x, -y);
-	}
-}
-
-
-//
-// handleevents() -- process the SDL message queue
-//   returns !0 if there was an important event worth checking (like quitting)
-//
-
-int32_t handleevents_sdlcommon(SDL_Event *ev)
-{
-    switch (ev->type)
-    {
-        case SDL_MOUSEMOTION:
-        //case SDL_JOYBALLMOTION:
-		{
-			// The menus need this, even in non GUI-capture mode
-			event_t event;
-			event.data1 = ev->motion.x;
-			event.data2 = ev->motion.y;
-
-			//screen->ScaleCoordsFromWindow(event.data1, event.data2);
-
-			event.type = EV_GUI_Event;
-			event.subtype = EV_GUI_MouseMove;
-
-			SDL_Keymod kmod = SDL_GetModState();
-			event.data3 = ((kmod & KMOD_SHIFT) ? GKM_SHIFT : 0) |
-				((kmod & KMOD_CTRL) ? GKM_CTRL : 0) |
-				((kmod & KMOD_ALT) ? GKM_ALT : 0);
-
-			D_PostEvent(&event);
-            break;
-		}
-
-        case SDL_MOUSEBUTTONDOWN:
-        case SDL_MOUSEBUTTONUP:
-        {
-            int32_t j;
-
-            // some of these get reordered to match winlayer
-            switch (ev->button.button)
-            {
-                default: j = -1; break;
-                case SDL_BUTTON_LEFT: j = KEY_MOUSE1; break;
-                case SDL_BUTTON_RIGHT: j = KEY_MOUSE2; break;
-                case SDL_BUTTON_MIDDLE: j = KEY_MOUSE3; break;
-
-                /* Thumb buttons. */
-                // On SDL2/Windows and SDL >= 2.0.?/Linux, everything is as it should be.
-                // If anyone cares about old versions of SDL2 on Linux, patches welcome.
-                case SDL_BUTTON_X1: j = KEY_MOUSE4; break;
-                case SDL_BUTTON_X2: j = KEY_MOUSE5; break;
-            }
-
-            if (j < 0)
-                break;
-
-			if (!(GUICapture & 1))
-			{
-				event_t evt = { uint8_t((ev->button.state == SDL_PRESSED) ? EV_KeyDown : EV_KeyUp), 0, (int16_t)j };
-				D_PostEvent(&evt);
-			}
-			else
-			{
-				event_t evt;
-				evt.type = EV_GUI_Event;
-				evt.subtype = uint8_t((ev->button.state == SDL_PRESSED) ? EV_GUI_LButtonDown : EV_GUI_LButtonUp);
-				evt.data1 = ev->motion.x;
-				evt.data2 = ev->motion.y;
-
-				SDL_Keymod kmod = SDL_GetModState();
-				evt.data3 = ((kmod & KMOD_SHIFT) ? GKM_SHIFT : 0) |
-					((kmod & KMOD_CTRL) ? GKM_CTRL : 0) |
-					((kmod & KMOD_ALT) ? GKM_ALT : 0);
-
-				D_PostEvent(&evt);
-
-			}
-            break;
-        }
-
-        case SDL_JOYAXISMOTION:
-#if SDL_MAJOR_VERSION >= 2
-            if (joystick.isGameController)
-                break;
-            fallthrough__;
-        case SDL_CONTROLLERAXISMOTION:
-#endif
-            if (appactive && ev->jaxis.axis < joystick.numAxes)
-            {
-                joystick.pAxis[ev->jaxis.axis] = ev->jaxis.value;
-                int32_t const scaledValue = ev->jaxis.value * 10000 / 32767;
-                if ((scaledValue < joydead[ev->jaxis.axis]) &&
-                    (scaledValue > -joydead[ev->jaxis.axis]))
-                    joystick.pAxis[ev->jaxis.axis] = 0;
-                else if (scaledValue >= joysatur[ev->jaxis.axis])
-                    joystick.pAxis[ev->jaxis.axis] = 32767;
-                else if (scaledValue <= -joysatur[ev->jaxis.axis])
-                    joystick.pAxis[ev->jaxis.axis] = -32767;
-                else
-                    joystick.pAxis[ev->jaxis.axis] = joystick.pAxis[ev->jaxis.axis] * 10000 / joysatur[ev->jaxis.axis];
-            }
-            break;
-
-        case SDL_JOYHATMOTION:
-        {
-            int32_t hatvals[16] = {
-                -1,     // centre
-                0,      // up 1
-                9000,   // right 2
-                4500,   // up+right 3
-                18000,  // down 4
-                -1,     // down+up!! 5
-                13500,  // down+right 6
-                -1,     // down+right+up!! 7
-                27000,  // left 8
-                27500,  // left+up 9
-                -1,     // left+right!! 10
-                -1,     // left+right+up!! 11
-                22500,  // left+down 12
-                -1,     // left+down+up!! 13
-                -1,     // left+down+right!! 14
-                -1,     // left+down+right+up!! 15
-            };
-            if (appactive && ev->jhat.hat < joystick.numHats)
-                joystick.pHat[ev->jhat.hat] = hatvals[ev->jhat.value & 15];
-            break;
-        }
-
-        case SDL_JOYBUTTONDOWN:
-        case SDL_JOYBUTTONUP:
-#if SDL_MAJOR_VERSION >= 2
-            if (joystick.isGameController)
-                break;
-            fallthrough__;
-        case SDL_CONTROLLERBUTTONDOWN:
-        case SDL_CONTROLLERBUTTONUP:
-#endif
-            if (appactive && ev->jbutton.button < joystick.numButtons)
-            {
-                if (ev->jbutton.state == SDL_PRESSED)
-                    joystick.bits |= 1 << ev->jbutton.button;
-                else
-                    joystick.bits &= ~(1 << ev->jbutton.button);
-
-            }
-            break;
-
-        case SDL_QUIT:
-			throw ExitEvent(0);	// completely bypass the hackery in the games to block Alt-F4.
-            return -1;
-    }
-
-    return 0;
-}
-
-// So this is how the engine handles text input?
-// Argh. This is just gross.
-int scancodetoasciihack(SDL_Event &ev)
-{
-	int sc = ev.key.keysym.scancode;
-	SDL_Keycode keyvalue = ev.key.keysym.sym;
-	int code = keytranslation[sc];
-	// Modifiers that have to be held down to be effective
-	// (excludes KMOD_NUM, for example).
-	static const int MODIFIERS =
-		KMOD_LSHIFT|KMOD_RSHIFT|KMOD_LCTRL|KMOD_RCTRL|
-		KMOD_LALT|KMOD_RALT|KMOD_LGUI|KMOD_RGUI;
-
-	// XXX: see osd.c, OSD_HandleChar(), there are more...
-	if (
-		(sc == SDL_SCANCODE_RETURN || sc == SDL_SCANCODE_KP_ENTER ||
-		 sc == SDL_SCANCODE_ESCAPE ||
-		 sc == SDL_SCANCODE_BACKSPACE ||
-		 sc == SDL_SCANCODE_TAB ||
-		 (((ev.key.keysym.mod) & MODIFIERS) == KMOD_LCTRL &&
-		  (sc >= SDL_SCANCODE_A && sc <= SDL_SCANCODE_Z))))
-	{
-		char keyvalue;
-		switch (sc)
-		{
-			case SDL_SCANCODE_RETURN: case SDL_SCANCODE_KP_ENTER: keyvalue = '\r'; break;
-			case SDL_SCANCODE_ESCAPE: keyvalue = 27; break;
-			case SDL_SCANCODE_BACKSPACE: keyvalue = '\b'; break;
-			case SDL_SCANCODE_TAB: keyvalue = '\t'; break;
-			default: keyvalue = sc - SDL_SCANCODE_A + 1; break;  // Ctrl+A --> 1, etc.
-		}
-	}
-	else
-	{
-		/*
-		Necessary for Duke 3D's method of entering cheats to work without showing IMEs.
-		SDL_TEXTINPUT is preferable overall, but with bitmap fonts it has no advantage.
-		*/
-		// Note that this is not how text input is supposed to be handled!
-
-		if ('a' <= keyvalue && keyvalue <= 'z')
-		{
-			if (!!(ev.key.keysym.mod & KMOD_SHIFT) ^ !!(ev.key.keysym.mod & KMOD_CAPS))
-				keyvalue -= 'a'-'A';
-		}
-		else if (ev.key.keysym.mod & KMOD_SHIFT)
-		{
-			keyvalue = g_keyAsciiTableShift[code];
-		}
-		else if (ev.key.keysym.mod & KMOD_NUM) // && !(ev.key.keysym.mod & KMOD_SHIFT)
-		{
-			switch (keyvalue)
-			{
-				case SDLK_KP_1: keyvalue = '1'; break;
-				case SDLK_KP_2: keyvalue = '2'; break;
-				case SDLK_KP_3: keyvalue = '3'; break;
-				case SDLK_KP_4: keyvalue = '4'; break;
-				case SDLK_KP_5: keyvalue = '5'; break;
-				case SDLK_KP_6: keyvalue = '6'; break;
-				case SDLK_KP_7: keyvalue = '7'; break;
-				case SDLK_KP_8: keyvalue = '8'; break;
-				case SDLK_KP_9: keyvalue = '9'; break;
-				case SDLK_KP_0: keyvalue = '0'; break;
-				case SDLK_KP_PERIOD: keyvalue = '.'; break;
-				case SDLK_KP_COMMA: keyvalue = ','; break;
-			}
-		}
-
-		switch (keyvalue)
-		{
-			case SDLK_KP_DIVIDE: keyvalue = '/'; break;
-			case SDLK_KP_MULTIPLY: keyvalue = '*'; break;
-			case SDLK_KP_MINUS: keyvalue = '-'; break;
-			case SDLK_KP_PLUS: keyvalue = '+'; break;
-		}
-	}
-	if (keyvalue >= 0x80) keyvalue = 0; // Sadly ASCII only...
-	return keyvalue;
-}
-
-int32_t handleevents_pollsdl(void);
-#if SDL_MAJOR_VERSION != 1
-// SDL 2.0 specific event handling
-int32_t handleevents_pollsdl(void)
-{
-    int32_t code, rv=0, j;
-    SDL_Event ev;
-
-    while (SDL_PollEvent(&ev))
-    {
-		if ((GUICapture & 10) == 2)
-		{
-			if (ImGui_ImplSDL2_ProcessEvent(&ev)) return 0;
-		}
-        switch (ev.type)
-        {
-            case SDL_TEXTINPUT:
-			{
-				j = 0;
-				const uint8_t* text = (uint8_t*)ev.text.text;
-				while ((j =  GetCharFromString(text)))
-				{
-					code = ev.text.text[j];
-					// Fixme: Send an EV_GUI_Event instead and properly deal with Unicode.
-					if ((GUICapture & 1) && menuactive != MENU_WaitKey)
-					{
-						event_t ev = { EV_GUI_Event, EV_GUI_Char, int16_t(j), !!(SDL_GetModState() & KMOD_ALT) };
-						D_PostEvent(&ev);
-					}
-				}
-				break;
-			}
-
-            case SDL_KEYDOWN:
-            case SDL_KEYUP:
-            {
-				if ((GUICapture & 1) && menuactive != MENU_WaitKey)
-				{
-					event_t event = {};
-					event.type = EV_GUI_Event;
-					event.subtype = ev.type == SDL_KEYDOWN ? EV_GUI_KeyDown : EV_GUI_KeyUp;
-					SDL_Keymod kmod = SDL_GetModState();
-					event.data3 = ((kmod & KMOD_SHIFT) ? GKM_SHIFT : 0) |
-						((kmod & KMOD_CTRL) ? GKM_CTRL : 0) |
-						((kmod & KMOD_ALT) ? GKM_ALT : 0);
-
-					if (event.subtype == EV_GUI_KeyDown && ev.key.repeat)
-					{
-						event.subtype = EV_GUI_KeyRepeat;
-					}
-
-					switch (ev.key.keysym.sym)
-					{
-					case SDLK_KP_ENTER:	event.data1 = GK_RETURN;	break;
-					case SDLK_PAGEUP:	event.data1 = GK_PGUP;		break;
-					case SDLK_PAGEDOWN:	event.data1 = GK_PGDN;		break;
-					case SDLK_END:		event.data1 = GK_END;		break;
-					case SDLK_HOME:		event.data1 = GK_HOME;		break;
-					case SDLK_LEFT:		event.data1 = GK_LEFT;		break;
-					case SDLK_RIGHT:	event.data1 = GK_RIGHT;		break;
-					case SDLK_UP:		event.data1 = GK_UP;		break;
-					case SDLK_DOWN:		event.data1 = GK_DOWN;		break;
-					case SDLK_DELETE:	event.data1 = GK_DEL;		break;
-					case SDLK_ESCAPE:	event.data1 = GK_ESCAPE;	break;
-					case SDLK_F1:		event.data1 = GK_F1;		break;
-					case SDLK_F2:		event.data1 = GK_F2;		break;
-					case SDLK_F3:		event.data1 = GK_F3;		break;
-					case SDLK_F4:		event.data1 = GK_F4;		break;
-					case SDLK_F5:		event.data1 = GK_F5;		break;
-					case SDLK_F6:		event.data1 = GK_F6;		break;
-					case SDLK_F7:		event.data1 = GK_F7;		break;
-					case SDLK_F8:		event.data1 = GK_F8;		break;
-					case SDLK_F9:		event.data1 = GK_F9;		break;
-					case SDLK_F10:		event.data1 = GK_F10;		break;
-					case SDLK_F11:		event.data1 = GK_F11;		break;
-					case SDLK_F12:		event.data1 = GK_F12;		break;
-					default:
-						if (ev.key.keysym.sym < 256)
-						{
-							event.data1 = ev.key.keysym.sym;
-						}
-						break;
-					}
-					if (event.data1 < 128)
-					{
-						event.data1 = toupper(event.data1);
-						D_PostEvent(&event);
-					}
-				}
-				else
-				{
-					auto const& sc = ev.key.keysym.scancode;
-					code = keytranslation[sc];
-
-					// The pause key generates a release event right after
-					// the pressing one. As a result, it gets unseen
-					// by the game most of the time.
-					if (code == 0x59 && ev.type == SDL_KEYUP)  // pause
-						break;
-
-					int keyvalue = ev.type == SDL_KEYDOWN ? scancodetoasciihack(ev) : 0;
-					event_t evt = { (uint8_t)(ev.type == SDL_KEYUP ? EV_KeyUp : EV_KeyDown), 0, (int16_t)code, (int16_t)keyvalue };
-					D_PostEvent(&evt);
-				}
-
-                break;
-            }
-
-            case SDL_MOUSEWHEEL:
-                // initprintf("wheel y %d\n",ev.wheel.y);
-	
-				// This never sends keyup events. For the current code that should suffice
-                if (ev.wheel.y > 0)
-                {
-					event_t evt = { EV_KeyDown, 0, (int16_t)KEY_MWHEELUP };
-					D_PostEvent(&evt);
-                }
-                if (ev.wheel.y < 0)
-                {
-					event_t evt = { EV_KeyDown, 0, (int16_t)KEY_MWHEELDOWN };
-					D_PostEvent(&evt);
-                }
-                break;
-
-            case SDL_WINDOWEVENT:
-                switch (ev.window.event)
-                {
-                    case SDL_WINDOWEVENT_FOCUS_GAINED:
-                    case SDL_WINDOWEVENT_FOCUS_LOST:
-                        appactive = (ev.window.event == SDL_WINDOWEVENT_FOCUS_GAINED);
-                        if (g_mouseGrabbed && g_mouseEnabled)
-                            grabmouse_low(appactive);
-                        break;
-
-                    case SDL_WINDOWEVENT_MOVED:
-                    {
-                        if (windowpos)
-                        {
-                            windowx = ev.window.data1;
-                            windowy = ev.window.data2;
-                        }
-
-                        r_displayindex = SDL_GetWindowDisplayIndex(sdl_window);
-                        modeschecked = 0;
-                        videoGetModes();
-                        break;
-                    }
-                    case SDL_WINDOWEVENT_ENTER:
-                        g_mouseInsideWindow = 1;
-                        break;
-                    case SDL_WINDOWEVENT_LEAVE:
-                        g_mouseInsideWindow = 0;
-                        break;
-                }
-
-                break;
-
-            default:
-                rv = handleevents_sdlcommon(&ev);
-                break;
-        }
-    }
-	MouseRead();
-
-    return rv;
-}
-#endif
-
-int32_t handleevents(void)
-{
-    int32_t rv;
-	
-	if (inputchecked && g_mouseEnabled)
-	{
-		// This is a horrible crutch
-		if (inputState.mouseReadButtons() & WHEELUP_MOUSE)
-		{
-			event_t ev = { EV_KeyUp, 0, (int16_t)KEY_MWHEELUP };
-			D_PostEvent(&ev);
-		}
-		if (inputState.mouseReadButtons() & WHEELDOWN_MOUSE)
-		{
-			event_t ev = { EV_KeyUp, 0, (int16_t)KEY_MWHEELDOWN };
-			D_PostEvent(&ev);
-		}
-    }
-
-    rv = handleevents_pollsdl();
-
-    inputchecked = 0;
-    timerUpdateClock();
-    return rv;
-}
-
-void I_SetMouseCapture()
-{
-	// Clear out any mouse movement.
-	SDL_CaptureMouse(SDL_TRUE);
-}
-
-void I_ReleaseMouseCapture()
-{
-	SDL_CaptureMouse(SDL_FALSE);
-}
-
 auto vsnprintfptr = vsnprintf;	// This is an inline in Visual Studio but we need an address for it to satisfy the MinGW compiled libraries.
 
 //
diff --git a/source/build/src/sdlkeytrans.cpp b/source/build/src/sdlkeytrans.cpp
deleted file mode 100644
index 9cdd18ce7..000000000
--- a/source/build/src/sdlkeytrans.cpp
+++ /dev/null
@@ -1,263 +0,0 @@
-
-#if (SDL_MAJOR_VERSION == 1 && SDL_MINOR_VERSION < 3)
-static uint8_t keytranslation[SDLK_LAST];
-#else
-static uint8_t keytranslation[SDL_NUM_SCANCODES];
-#endif
-static int32_t buildkeytranslationtable(void);
-
-#if (SDL_MAJOR_VERSION == 1 && SDL_MINOR_VERSION < 3) // SDL 1.2
-static int32_t buildkeytranslationtable(void)
-{
-    memset(keytranslation,0,sizeof(keytranslation));
-
-#define MAP(x,y) keytranslation[x] = y
-    MAP(SDLK_BACKSPACE,	0xe);
-    MAP(SDLK_TAB,		0xf);
-    MAP(SDLK_RETURN,	0x1c);
-    MAP(SDLK_PAUSE,		0x59);	// 0x1d + 0x45 + 0x9d + 0xc5
-    MAP(SDLK_ESCAPE,	0x1);
-    MAP(SDLK_SPACE,		0x39);
-    MAP(SDLK_EXCLAIM,	0x2);	// '1'
-    MAP(SDLK_QUOTEDBL,	0x28);	// '''
-    MAP(SDLK_HASH,		0x4);	// '3'
-    MAP(SDLK_DOLLAR,	0x5);	// '4'
-    MAP(37,			0x6);	// '5' <-- where's the keysym SDL guys?
-    MAP(SDLK_AMPERSAND,	0x8);	// '7'
-    MAP(SDLK_QUOTE,		0x28);	// '''
-    MAP(SDLK_LEFTPAREN,	0xa);	// '9'
-    MAP(SDLK_RIGHTPAREN,	0xb);	// '0'
-    MAP(SDLK_ASTERISK,	0x9);	// '8'
-    MAP(SDLK_PLUS,		0xd);	// '='
-    MAP(SDLK_COMMA,		0x33);
-    MAP(SDLK_MINUS,		0xc);
-    MAP(SDLK_PERIOD,	0x34);
-    MAP(SDLK_SLASH,		0x35);
-    MAP(SDLK_0,		0xb);
-    MAP(SDLK_1,		0x2);
-    MAP(SDLK_2,		0x3);
-    MAP(SDLK_3,		0x4);
-    MAP(SDLK_4,		0x5);
-    MAP(SDLK_5,		0x6);
-    MAP(SDLK_6,		0x7);
-    MAP(SDLK_7,		0x8);
-    MAP(SDLK_8,		0x9);
-    MAP(SDLK_9,		0xa);
-    MAP(SDLK_COLON,		0x27);
-    MAP(SDLK_SEMICOLON,	0x27);
-    MAP(SDLK_LESS,		0x56);
-    MAP(SDLK_EQUALS,	0xd);
-    MAP(SDLK_GREATER,	0x34);
-    MAP(SDLK_QUESTION,	0x35);
-    MAP(SDLK_AT,		0x3);	// '2'
-    MAP(SDLK_LEFTBRACKET,	0x1a);
-    MAP(SDLK_BACKSLASH,	0x2b);
-    MAP(SDLK_RIGHTBRACKET,	0x1b);
-    MAP(SDLK_CARET,		0x7);	// '7'
-    MAP(SDLK_UNDERSCORE,	0xc);
-    MAP(SDLK_BACKQUOTE,	0x29);
-    MAP(SDLK_a,		0x1e);
-    MAP(SDLK_b,		0x30);
-    MAP(SDLK_c,		0x2e);
-    MAP(SDLK_d,		0x20);
-    MAP(SDLK_e,		0x12);
-    MAP(SDLK_f,		0x21);
-    MAP(SDLK_g,		0x22);
-    MAP(SDLK_h,		0x23);
-    MAP(SDLK_i,		0x17);
-    MAP(SDLK_j,		0x24);
-    MAP(SDLK_k,		0x25);
-    MAP(SDLK_l,		0x26);
-    MAP(SDLK_m,		0x32);
-    MAP(SDLK_n,		0x31);
-    MAP(SDLK_o,		0x18);
-    MAP(SDLK_p,		0x19);
-    MAP(SDLK_q,		0x10);
-    MAP(SDLK_r,		0x13);
-    MAP(SDLK_s,		0x1f);
-    MAP(SDLK_t,		0x14);
-    MAP(SDLK_u,		0x16);
-    MAP(SDLK_v,		0x2f);
-    MAP(SDLK_w,		0x11);
-    MAP(SDLK_x,		0x2d);
-    MAP(SDLK_y,		0x15);
-    MAP(SDLK_z,		0x2c);
-    MAP(SDLK_DELETE,	0xd3);
-    MAP(SDLK_KP0,		0x52);
-    MAP(SDLK_KP1,		0x4f);
-    MAP(SDLK_KP2,		0x50);
-    MAP(SDLK_KP3,		0x51);
-    MAP(SDLK_KP4,		0x4b);
-    MAP(SDLK_KP5,		0x4c);
-    MAP(SDLK_KP6,		0x4d);
-    MAP(SDLK_KP7,		0x47);
-    MAP(SDLK_KP8,		0x48);
-    MAP(SDLK_KP9,		0x49);
-    MAP(SDLK_KP_PERIOD,	0x53);
-    MAP(SDLK_KP_DIVIDE,	0xb5);
-    MAP(SDLK_KP_MULTIPLY,	0x37);
-    MAP(SDLK_KP_MINUS,	0x4a);
-    MAP(SDLK_KP_PLUS,	0x4e);
-    MAP(SDLK_KP_ENTER,	0x9c);
-    //MAP(SDLK_KP_EQUALS,	);
-    MAP(SDLK_UP,		0xc8);
-    MAP(SDLK_DOWN,		0xd0);
-    MAP(SDLK_RIGHT,		0xcd);
-    MAP(SDLK_LEFT,		0xcb);
-    MAP(SDLK_INSERT,	0xd2);
-    MAP(SDLK_HOME,		0xc7);
-    MAP(SDLK_END,		0xcf);
-    MAP(SDLK_PAGEUP,	0xc9);
-    MAP(SDLK_PAGEDOWN,	0xd1);
-    MAP(SDLK_F1,		0x3b);
-    MAP(SDLK_F2,		0x3c);
-    MAP(SDLK_F3,		0x3d);
-    MAP(SDLK_F4,		0x3e);
-    MAP(SDLK_F5,		0x3f);
-    MAP(SDLK_F6,		0x40);
-    MAP(SDLK_F7,		0x41);
-    MAP(SDLK_F8,		0x42);
-    MAP(SDLK_F9,		0x43);
-    MAP(SDLK_F10,		0x44);
-    MAP(SDLK_F11,		0x57);
-    MAP(SDLK_F12,		0x58);
-    MAP(SDLK_NUMLOCK,	0x45);
-    MAP(SDLK_CAPSLOCK,	0x3a);
-    MAP(SDLK_SCROLLOCK,	0x46);
-    MAP(SDLK_RSHIFT,	0x36);
-    MAP(SDLK_LSHIFT,	0x2a);
-    MAP(SDLK_RCTRL,		0x9d);
-    MAP(SDLK_LCTRL,		0x1d);
-    MAP(SDLK_RALT,		0xb8);
-    MAP(SDLK_LALT,		0x38);
-    MAP(SDLK_LSUPER,	0xdb);	// win l
-    MAP(SDLK_RSUPER,	0xdc);	// win r
-    MAP(SDLK_PRINT,		-2);	// 0xaa + 0xb7
-    MAP(SDLK_SYSREQ,	0x54);	// alt+printscr
-    MAP(SDLK_BREAK,		0xb7);	// ctrl+pause
-    MAP(SDLK_MENU,		0xdd);	// win menu?
-#undef MAP
-
-    return 0;
-}
-#else // if SDL 1.3+
-static int32_t buildkeytranslationtable(void)
-{
-    memset(keytranslation,0,sizeof(keytranslation));
-
-#define MAP(x,y) keytranslation[x] = y
-    MAP(SDL_SCANCODE_BACKSPACE,	0xe);
-    MAP(SDL_SCANCODE_TAB,		0xf);
-    MAP(SDL_SCANCODE_RETURN,	0x1c);
-    MAP(SDL_SCANCODE_PAUSE,		0x59);	// 0x1d + 0x45 + 0x9d + 0xc5
-    MAP(SDL_SCANCODE_ESCAPE,	0x1);
-    MAP(SDL_SCANCODE_SPACE,		0x39);
-    MAP(SDL_SCANCODE_COMMA,		0x33);
-    MAP(SDL_SCANCODE_NONUSBACKSLASH, 0x56);
-    MAP(SDL_SCANCODE_MINUS,		0xc);
-    MAP(SDL_SCANCODE_PERIOD,	0x34);
-    MAP(SDL_SCANCODE_SLASH,		0x35);
-    MAP(SDL_SCANCODE_0,		0xb);
-    MAP(SDL_SCANCODE_1,		0x2);
-    MAP(SDL_SCANCODE_2,		0x3);
-    MAP(SDL_SCANCODE_3,		0x4);
-    MAP(SDL_SCANCODE_4,		0x5);
-    MAP(SDL_SCANCODE_5,		0x6);
-    MAP(SDL_SCANCODE_6,		0x7);
-    MAP(SDL_SCANCODE_7,		0x8);
-    MAP(SDL_SCANCODE_8,		0x9);
-    MAP(SDL_SCANCODE_9,		0xa);
-    MAP(SDL_SCANCODE_SEMICOLON,	0x27);
-    MAP(SDL_SCANCODE_APOSTROPHE, 0x28);
-    MAP(SDL_SCANCODE_EQUALS,	0xd);
-    MAP(SDL_SCANCODE_LEFTBRACKET,	0x1a);
-    MAP(SDL_SCANCODE_BACKSLASH,	0x2b);
-    MAP(SDL_SCANCODE_RIGHTBRACKET,	0x1b);
-    MAP(SDL_SCANCODE_A,		0x1e);
-    MAP(SDL_SCANCODE_B,		0x30);
-    MAP(SDL_SCANCODE_C,		0x2e);
-    MAP(SDL_SCANCODE_D,		0x20);
-    MAP(SDL_SCANCODE_E,		0x12);
-    MAP(SDL_SCANCODE_F,		0x21);
-    MAP(SDL_SCANCODE_G,		0x22);
-    MAP(SDL_SCANCODE_H,		0x23);
-    MAP(SDL_SCANCODE_I,		0x17);
-    MAP(SDL_SCANCODE_J,		0x24);
-    MAP(SDL_SCANCODE_K,		0x25);
-    MAP(SDL_SCANCODE_L,		0x26);
-    MAP(SDL_SCANCODE_M,		0x32);
-    MAP(SDL_SCANCODE_N,		0x31);
-    MAP(SDL_SCANCODE_O,		0x18);
-    MAP(SDL_SCANCODE_P,		0x19);
-    MAP(SDL_SCANCODE_Q,		0x10);
-    MAP(SDL_SCANCODE_R,		0x13);
-    MAP(SDL_SCANCODE_S,		0x1f);
-    MAP(SDL_SCANCODE_T,		0x14);
-    MAP(SDL_SCANCODE_U,		0x16);
-    MAP(SDL_SCANCODE_V,		0x2f);
-    MAP(SDL_SCANCODE_W,		0x11);
-    MAP(SDL_SCANCODE_X,		0x2d);
-    MAP(SDL_SCANCODE_Y,		0x15);
-    MAP(SDL_SCANCODE_Z,		0x2c);
-    MAP(SDL_SCANCODE_DELETE,	0xd3);
-    MAP(SDL_SCANCODE_KP_0,		0x52);
-    MAP(SDL_SCANCODE_KP_1,		0x4f);
-    MAP(SDL_SCANCODE_KP_2,		0x50);
-    MAP(SDL_SCANCODE_KP_3,		0x51);
-    MAP(SDL_SCANCODE_KP_4,		0x4b);
-    MAP(SDL_SCANCODE_KP_5,		0x4c);
-    MAP(SDL_SCANCODE_KP_CLEAR,		0x4c);
-    MAP(SDL_SCANCODE_CLEAR,		0x4c);
-    MAP(SDL_SCANCODE_KP_6,		0x4d);
-    MAP(SDL_SCANCODE_KP_7,		0x47);
-    MAP(SDL_SCANCODE_KP_8,		0x48);
-    MAP(SDL_SCANCODE_KP_9,		0x49);
-    MAP(SDL_SCANCODE_KP_PERIOD,	0x53);
-    MAP(SDL_SCANCODE_KP_DIVIDE,	0xb5);
-    MAP(SDL_SCANCODE_KP_MULTIPLY,	0x37);
-    MAP(SDL_SCANCODE_KP_MINUS,	0x4a);
-    MAP(SDL_SCANCODE_KP_PLUS,	0x4e);
-    MAP(SDL_SCANCODE_KP_ENTER,	0x9c);
-    //MAP(SDL_SCANCODE_KP_EQUALS,	);
-    MAP(SDL_SCANCODE_UP,		0xc8);
-    MAP(SDL_SCANCODE_DOWN,		0xd0);
-    MAP(SDL_SCANCODE_RIGHT,		0xcd);
-    MAP(SDL_SCANCODE_LEFT,		0xcb);
-    MAP(SDL_SCANCODE_INSERT,	0xd2);
-    MAP(SDL_SCANCODE_HOME,		0xc7);
-    MAP(SDL_SCANCODE_END,		0xcf);
-    MAP(SDL_SCANCODE_PAGEUP,	0xc9);
-    MAP(SDL_SCANCODE_PAGEDOWN,	0xd1);
-    MAP(SDL_SCANCODE_F1,		0x3b);
-    MAP(SDL_SCANCODE_F2,		0x3c);
-    MAP(SDL_SCANCODE_F3,		0x3d);
-    MAP(SDL_SCANCODE_F4,		0x3e);
-    MAP(SDL_SCANCODE_F5,		0x3f);
-    MAP(SDL_SCANCODE_F6,		0x40);
-    MAP(SDL_SCANCODE_F7,		0x41);
-    MAP(SDL_SCANCODE_F8,		0x42);
-    MAP(SDL_SCANCODE_F9,		0x43);
-    MAP(SDL_SCANCODE_F10,		0x44);
-    MAP(SDL_SCANCODE_F11,		0x57);
-    MAP(SDL_SCANCODE_F12,		0x58);
-    MAP(SDL_SCANCODE_NUMLOCKCLEAR,	0x45);
-    MAP(SDL_SCANCODE_CAPSLOCK,	0x3a);
-    MAP(SDL_SCANCODE_SCROLLLOCK,	0x46);
-    MAP(SDL_SCANCODE_RSHIFT,	0x36);
-    MAP(SDL_SCANCODE_LSHIFT,	0x2a);
-    MAP(SDL_SCANCODE_RCTRL,		0x9d);
-    MAP(SDL_SCANCODE_LCTRL,		0x1d);
-    MAP(SDL_SCANCODE_RALT,		0xb8);
-    MAP(SDL_SCANCODE_LALT,		0x38);
-    MAP(SDL_SCANCODE_LGUI,	0xdb);	// win l
-    MAP(SDL_SCANCODE_RGUI,	0xdc);	// win r
-//    MAP(SDL_SCANCODE_PRINTSCREEN,		-2);	// 0xaa + 0xb7
-    MAP(SDL_SCANCODE_SYSREQ,	0x54);	// alt+printscr
-//    MAP(SDL_SCANCODE_PAUSE,		0xb7);	// ctrl+pause
-    MAP(SDL_SCANCODE_MENU,		0xdd);	// win menu?
-    MAP(SDL_SCANCODE_GRAVE,     0x29);  // tilde
-#undef MAP
-
-    return 0;
-}
-#endif
diff --git a/source/common/gameconfigfile.cpp b/source/common/gameconfigfile.cpp
index e045c2645..c97a5765a 100644
--- a/source/common/gameconfigfile.cpp
+++ b/source/common/gameconfigfile.cpp
@@ -531,7 +531,6 @@ void G_SaveConfig()
 {
 	GameConfig->ArchiveGlobalData();
 	GameConfig->ArchiveGameData(GameName);
-	CONFIG_WriteControllerSettings();
 	GameConfig->WriteConfigFile();
 	delete GameConfig;
 	GameConfig = nullptr;
diff --git a/source/common/gamecontrol.cpp b/source/common/gamecontrol.cpp
index 5164b3aea..6a959e78b 100644
--- a/source/common/gamecontrol.cpp
+++ b/source/common/gamecontrol.cpp
@@ -250,6 +250,9 @@ void CheckFrontend(int flags)
 	}
 }
 
+void I_StartupJoysticks();
+void I_ShutdownInput();
+int RunGame();
 
 int GameMain()
 {
@@ -257,6 +260,7 @@ int GameMain()
 	C_InitConsole(1024, 768, true);
 	FStringf logpath("logfile %sdemolition.log", M_GetDocumentsPath().GetChars());
 	C_DoCommand(logpath);
+	I_StartupJoysticks();
 
 #ifndef NETCODE_DISABLE
 	gHaveNetworking = !enet_initialize();
@@ -267,7 +271,7 @@ int GameMain()
 	int r;
 	try
 	{
-		r = CONFIG_Init();
+		r = RunGame();
 	}
 	catch (const std::runtime_error & err)
 	{
@@ -283,6 +287,7 @@ int GameMain()
 #ifndef NETCODE_DISABLE
 	if (gHaveNetworking) enet_deinitialize();
 #endif
+	I_ShutdownInput();
 	return r;
 }
 
@@ -328,7 +333,7 @@ void SetDefaultStrings()
 //
 //==========================================================================
 
-int CONFIG_Init()
+int RunGame()
 {
 	SetClipshapes();
 
@@ -526,288 +531,12 @@ int CONFIG_SetMapBestTime(uint8_t const* const mapmd4, int32_t tm)
 	}
 	return 0;
 }
-//==========================================================================
-//
-// 
-//
-//==========================================================================
-
-int32_t MouseAnalogueAxes[MAXMOUSEAXES];
-int32_t JoystickFunctions[MAXJOYBUTTONSANDHATS][2];
-int32_t JoystickDigitalFunctions[MAXJOYAXES][2];
-int32_t JoystickAnalogueAxes[MAXJOYAXES];
-int32_t JoystickAnalogueScale[MAXJOYAXES];
-int32_t JoystickAnalogueDead[MAXJOYAXES];
-int32_t JoystickAnalogueSaturate[MAXJOYAXES];
-int32_t JoystickAnalogueInvert[MAXJOYAXES];
-
-static const char* mouseanalogdefaults[MAXMOUSEAXES] =
-{
-"analog_turning",
-"analog_moving",
-};
-
-
-static const char* joystickclickeddefaults[MAXJOYBUTTONSANDHATS] =
-{
-"",
-"Inventory",
-"Jump",
-"Crouch",
-};
-
-
-static const char* joystickanalogdefaults[MAXJOYAXES] =
-{
-"analog_turning",
-"analog_moving",
-"analog_strafing",
-};
-
-
-//==========================================================================
-//
-//
-//
-//==========================================================================
-
-int32_t CONFIG_AnalogNameToNum(const char* func)
-{
-	if (!func)
-		return -1;
-
-	if (!Bstrcasecmp(func, "analog_turning"))
-	{
-		return analog_turning;
-	}
-	if (!Bstrcasecmp(func, "analog_strafing"))
-	{
-		return analog_strafing;
-	}
-	if (!Bstrcasecmp(func, "analog_moving"))
-	{
-		return analog_moving;
-	}
-	if (!Bstrcasecmp(func, "analog_lookingupanddown"))
-	{
-		return analog_lookingupanddown;
-	}
-
-	return -1;
-}
-
-
-//==========================================================================
-//
-//
-//
-//==========================================================================
-
-const char* CONFIG_AnalogNumToName(int32_t func)
-{
-	switch (func)
-	{
-	case analog_turning:
-		return "analog_turning";
-	case analog_strafing:
-		return "analog_strafing";
-	case analog_moving:
-		return "analog_moving";
-	case analog_lookingupanddown:
-		return "analog_lookingupanddown";
-	}
-
-	return NULL;
-}
-
-void CONFIG_SetupMouse(void)
-{
-	CONTROL_MouseEnabled    = (in_mouse && CONTROL_MousePresent);
-}
-
-
-void CONFIG_SetupJoystick(void)
-{
-	for (int i = 0; i < MAXJOYAXES; i++)
-	{
-		CONTROL_MapAnalogAxis(i, JoystickAnalogueAxes[i], controldevice_joystick);
-		CONTROL_MapDigitalAxis(i, JoystickDigitalFunctions[i][0], 0, controldevice_joystick);
-		CONTROL_MapDigitalAxis(i, JoystickDigitalFunctions[i][1], 1, controldevice_joystick);
-		CONTROL_SetAnalogAxisScale(i, JoystickAnalogueScale[i], controldevice_joystick);
-		CONTROL_SetAnalogAxisInvert(i, JoystickAnalogueInvert[i], controldevice_joystick);
-	}
-	
-	CONTROL_JoystickEnabled = (in_joystick && CONTROL_JoyPresent);
-
-	// JBF 20040215: evil and nasty place to do this, but joysticks are evil and nasty too
-	for (int i=0; i<joystick.numAxes; i++)
-		joySetDeadZone(i,JoystickAnalogueDead[i],JoystickAnalogueSaturate[i]);
-
-}
-
-static void CONFIG_SetJoystickButtonFunction(int i, int j, int function)
-{
-	JoystickFunctions[i][j] = function;
-	//CONTROL_MapButton(function, i, j, controldevice_joystick);
-}
-static void CONFIG_SetJoystickAnalogAxisScale(int i, int scale)
-{
-	JoystickAnalogueScale[i] = scale;
-	CONTROL_SetAnalogAxisScale(i, scale, controldevice_joystick);
-}
-static void CONFIG_SetJoystickAnalogAxisInvert(int i, int invert)
-{
-	JoystickAnalogueInvert[i] = invert;
-	CONTROL_SetAnalogAxisInvert(i, invert, controldevice_joystick);
-}
-static void CONFIG_SetJoystickAnalogAxisDeadSaturate(int i, int dead, int saturate)
-{
-	JoystickAnalogueDead[i] = dead;
-	JoystickAnalogueSaturate[i] = saturate;
-	joySetDeadZone(i, dead, saturate);
-}
-static void CONFIG_SetJoystickDigitalAxisFunction(int i, int j, int function)
-{
-	JoystickDigitalFunctions[i][j] = function;
-	CONTROL_MapDigitalAxis(i, function, j, controldevice_joystick);
-}
-static void CONFIG_SetJoystickAnalogAxisFunction(int i, int function)
-{
-	JoystickAnalogueAxes[i] = function;
-	CONTROL_MapAnalogAxis(i, function, controldevice_joystick);
-}
-
-struct GameControllerButtonSetting
-{
-	GameControllerButton button;
-	int function;
-
-	void apply() const
-	{
-		CONFIG_SetJoystickButtonFunction(button, 0, function);
-	}
-};
-struct GameControllerAnalogAxisSetting
-{
-	GameControllerAxis axis;
-	int function;
-
-	void apply() const
-	{
-		CONFIG_SetJoystickAnalogAxisFunction(axis, function);
-	}
-};
-struct GameControllerDigitalAxisSetting
-{
-	GameControllerAxis axis;
-	int polarity;
-	int function;
-
-	void apply() const
-	{
-		CONFIG_SetJoystickDigitalAxisFunction(axis, polarity, function);
-	}
-};
-
-
-void CONFIG_SetGameControllerDefaultsClear()
-{
-	for (int i = 0; i < MAXJOYBUTTONSANDHATS; i++)
-	{
-		CONFIG_SetJoystickButtonFunction(i, 0, -1);
-		CONFIG_SetJoystickButtonFunction(i, 1, -1);
-	}
-
-	for (int i = 0; i < MAXJOYAXES; i++)
-	{
-		CONFIG_SetJoystickAnalogAxisScale(i, DEFAULTJOYSTICKANALOGUESCALE);
-		CONFIG_SetJoystickAnalogAxisInvert(i, 0);
-		CONFIG_SetJoystickAnalogAxisDeadSaturate(i, DEFAULTJOYSTICKANALOGUEDEAD, DEFAULTJOYSTICKANALOGUESATURATE);
-
-		CONFIG_SetJoystickDigitalAxisFunction(i, 0, -1);
-		CONFIG_SetJoystickDigitalAxisFunction(i, 1, -1);
-
-		CONFIG_SetJoystickAnalogAxisFunction(i, -1);
-	}
-}
-
-static void CONFIG_SetGameControllerAxesModern()
-{
-	static GameControllerAnalogAxisSetting const analogAxes[] =
-	{
-		{ GAMECONTROLLER_AXIS_LEFTX, analog_strafing },
-		{ GAMECONTROLLER_AXIS_LEFTY, analog_moving },
-		{ GAMECONTROLLER_AXIS_RIGHTX, analog_turning },
-		{ GAMECONTROLLER_AXIS_RIGHTY, analog_lookingupanddown },
-	};
-
-	CONFIG_SetJoystickAnalogAxisScale(GAMECONTROLLER_AXIS_RIGHTX, 32768 + 16384);
-	CONFIG_SetJoystickAnalogAxisScale(GAMECONTROLLER_AXIS_RIGHTY, 32768 + 16384);
-
-	for (auto const& analogAxis : analogAxes)
-		analogAxis.apply();
-}
-
 
 
 void CONFIG_InitMouseAndController()
 {
-	memset(JoystickFunctions, -1, sizeof(JoystickFunctions));
-	memset(JoystickDigitalFunctions, -1, sizeof(JoystickDigitalFunctions));
-
-	for (int i = 0; i < MAXMOUSEAXES; i++)
-	{
-		MouseAnalogueAxes[i] = CONFIG_AnalogNameToNum(mouseanalogdefaults[i]);
-		CONTROL_MapAnalogAxis(i, MouseAnalogueAxes[i], controldevice_mouse);
-	}
-	CONFIG_SetupMouse();
-	CONFIG_SetupJoystick();
 	inputState.ClearKeysDown();
 	inputState.keyFlushChars();
 	inputState.keyFlushScans();
 }
 
-
-void CONFIG_PutNumber(const char* key, int number)
-{
-	FStringf str("%d", number);
-	GameConfig->SetValueForKey(key, str);
-}
-
-void CONFIG_WriteControllerSettings()
-{
-	FString buf;
-
-
-	if (in_joystick)
-	{
-		FString section = currentGame + ".ControllerSettings";
-		GameConfig->SetSection(section);
-		for (int dummy = 0; dummy < MAXJOYAXES; dummy++)
-		{
-			if (CONFIG_AnalogNumToName(JoystickAnalogueAxes[dummy]))
-			{
-				buf.Format("ControllerAnalogAxes%d", dummy);
-				GameConfig->SetValueForKey(buf, CONFIG_AnalogNumToName(JoystickAnalogueAxes[dummy]));
-			}
-
-			if (buttonMap.GetButtonName(JoystickDigitalFunctions[dummy][1]))
-			{
-				buf.Format("ControllerDigitalAxes%d_1", dummy);
-				GameConfig->SetValueForKey(buf, buttonMap.GetButtonName(JoystickDigitalFunctions[dummy][1]));
-			}
-
-			buf.Format("ControllerAnalogScale%d", dummy);
-			CONFIG_PutNumber(buf, JoystickAnalogueScale[dummy]);
-
-			buf.Format("ControllerAnalogInvert%d", dummy);
-			CONFIG_PutNumber(buf, JoystickAnalogueInvert[dummy]);
-
-			buf.Format("ControllerAnalogDead%d", dummy);
-			CONFIG_PutNumber(buf, JoystickAnalogueDead[dummy]);
-
-			buf.Format("ControllerAnalogSaturate%d", dummy);
-			CONFIG_PutNumber(buf, JoystickAnalogueSaturate[dummy]);
-		}
-	}
-}
diff --git a/source/common/gamecontrol.h b/source/common/gamecontrol.h
index b699693d8..155d86338 100644
--- a/source/common/gamecontrol.h
+++ b/source/common/gamecontrol.h
@@ -39,7 +39,6 @@ void CONFIG_SetDefaultKeys(const char *defbinds);
 
 
 void CONFIG_SetupJoystick(void);
-void CONFIG_WriteControllerSettings();
 void CONFIG_InitMouseAndController();
 
 void CONFIG_SetGameControllerDefaultsClear();
diff --git a/source/common/gamecvars.cpp b/source/common/gamecvars.cpp
index 5a4674b13..e6d7d2e93 100644
--- a/source/common/gamecvars.cpp
+++ b/source/common/gamecvars.cpp
@@ -297,10 +297,7 @@ CUSTOM_CVARD(Bool, in_joystick, false, CVAR_ARCHIVE||CVAR_GLOBALCONFIG|CVAR_NOIN
 	CONTROL_JoystickEnabled = (self && CONTROL_JoyPresent);
 }
 
-CUSTOM_CVARD(Bool, in_mouse, true, CVAR_ARCHIVE|CVAR_GLOBALCONFIG|CVAR_NOINITCALL, "enables input from the mouse if it is present")
-{
-	CONTROL_MouseEnabled = (self && CONTROL_MousePresent);
-}
+CVARD(Bool, in_mouse, true, CVAR_ARCHIVE|CVAR_GLOBALCONFIG|CVAR_NOINITCALL, "enables input from the mouse if it is present")
 
 CVARD(Bool, in_mousemode, true, CVAR_ARCHIVE|CVAR_GLOBALCONFIG, "toggles vertical mouse view")
 
diff --git a/source/common/input/i_gui.cpp b/source/common/input/i_gui.cpp
new file mode 100644
index 000000000..01a2362ca
--- /dev/null
+++ b/source/common/input/i_gui.cpp
@@ -0,0 +1,87 @@
+/*
+** i_gui.cpp
+**
+**---------------------------------------------------------------------------
+** Copyright 2008 Randy Heit
+** All rights reserved.
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions
+** are met:
+**
+** 1. Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+** 2. Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in the
+**    documentation and/or other materials provided with the distribution.
+** 3. The name of the author may not be used to endorse or promote products
+**    derived from this software without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+**---------------------------------------------------------------------------
+**
+*/
+
+#include <string.h>
+
+#include <SDL.h>
+
+#include "bitmap.h"
+//#include "v_palette.h"
+#include "textures.h"
+
+bool I_SetCursor(FTexture *cursorpic)
+{
+	static SDL_Cursor *cursor;
+	static SDL_Surface *cursorSurface;
+
+	if (cursorpic != NULL)
+	{
+		auto src = cursorpic->GetBgraBitmap(nullptr);
+		// Must be no larger than 32x32.
+		if (src.GetWidth() > 32 || src.GetHeight() > 32)
+		{
+			return false;
+		}
+
+		if (cursorSurface == NULL)
+			cursorSurface = SDL_CreateRGBSurface (0, 32, 32, 32, MAKEARGB(0,255,0,0), MAKEARGB(0,0,255,0), MAKEARGB(0,0,0,255), MAKEARGB(255,0,0,0));
+
+		SDL_LockSurface(cursorSurface);
+		uint8_t buffer[32*32*4];
+		memset(buffer, 0, 32*32*4);
+		FBitmap bmp(buffer, 32*4, 32, 32);
+		bmp.Blit(0, 0, src);	// expand to 32*32
+		memcpy(cursorSurface->pixels, bmp.GetPixels(), 32*32*4);
+		SDL_UnlockSurface(cursorSurface);
+
+		if (cursor)
+			SDL_FreeCursor (cursor);
+		cursor = SDL_CreateColorCursor (cursorSurface, 0, 0);
+		SDL_SetCursor (cursor);
+	}
+	else
+	{
+		if (cursor)
+		{
+			SDL_SetCursor (NULL);
+			SDL_FreeCursor (cursor);
+			cursor = NULL;
+		}
+		if (cursorSurface != NULL)
+		{
+			SDL_FreeSurface(cursorSurface);
+			cursorSurface = NULL;
+		}
+	}
+	return true;
+}
diff --git a/source/common/input/i_input.cpp b/source/common/input/i_input.cpp
new file mode 100644
index 000000000..fa3a94b87
--- /dev/null
+++ b/source/common/input/i_input.cpp
@@ -0,0 +1,750 @@
+/*
+** This code is partially original EDuke32 code, partially taken from ZDoom
+**
+** For the portions taken from ZDoom the following applies:
+**
+**---------------------------------------------------------------------------
+** Copyright 2005-2016 Randy Heit
+** All rights reserved.
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions
+** are met:
+**
+** 1. Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+** 2. Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in the
+**    documentation and/or other materials provided with the distribution.
+** 3. The name of the author may not be used to endorse or promote products
+**    derived from this software without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+**---------------------------------------------------------------------------
+**
+*/
+
+#include <SDL.h>
+#include "compat.h"
+
+//#include "doomtype.h"
+//#include "doomdef.h"
+//#include "doomstat.h"
+#include "m_argv.h"
+//#include "v_video.h"
+
+#include "c_buttons.h"
+#include "d_event.h"
+#include "d_gui.h"
+#include "c_console.h"
+#include "c_dispatch.h"
+#include "c_cvars.h"
+#include "keydef.h"
+//#include "dikeys.h"
+//#include "events.h"
+//#include "g_game.h"
+//#include "g_levellocals.h"
+#include "utf8.h"
+#include "menu/menu.h"
+
+
+char grabmouse_low(char a);
+void WindowMoved(int x, int y);
+extern SDL_Window* sdl_window;
+
+
+static uint8_t keytranslation[SDL_NUM_SCANCODES];
+
+void buildkeytranslationtable(void)
+{
+    memset(keytranslation, 0, sizeof(keytranslation));
+
+#define MAP(x,y) keytranslation[x] = y
+    MAP(SDL_SCANCODE_BACKSPACE, 0xe);
+    MAP(SDL_SCANCODE_TAB, 0xf);
+    MAP(SDL_SCANCODE_RETURN, 0x1c);
+    MAP(SDL_SCANCODE_PAUSE, 0x59);	// 0x1d + 0x45 + 0x9d + 0xc5
+    MAP(SDL_SCANCODE_ESCAPE, 0x1);
+    MAP(SDL_SCANCODE_SPACE, 0x39);
+    MAP(SDL_SCANCODE_COMMA, 0x33);
+    MAP(SDL_SCANCODE_NONUSBACKSLASH, 0x56);
+    MAP(SDL_SCANCODE_MINUS, 0xc);
+    MAP(SDL_SCANCODE_PERIOD, 0x34);
+    MAP(SDL_SCANCODE_SLASH, 0x35);
+    MAP(SDL_SCANCODE_0, 0xb);
+    MAP(SDL_SCANCODE_1, 0x2);
+    MAP(SDL_SCANCODE_2, 0x3);
+    MAP(SDL_SCANCODE_3, 0x4);
+    MAP(SDL_SCANCODE_4, 0x5);
+    MAP(SDL_SCANCODE_5, 0x6);
+    MAP(SDL_SCANCODE_6, 0x7);
+    MAP(SDL_SCANCODE_7, 0x8);
+    MAP(SDL_SCANCODE_8, 0x9);
+    MAP(SDL_SCANCODE_9, 0xa);
+    MAP(SDL_SCANCODE_SEMICOLON, 0x27);
+    MAP(SDL_SCANCODE_APOSTROPHE, 0x28);
+    MAP(SDL_SCANCODE_EQUALS, 0xd);
+    MAP(SDL_SCANCODE_LEFTBRACKET, 0x1a);
+    MAP(SDL_SCANCODE_BACKSLASH, 0x2b);
+    MAP(SDL_SCANCODE_RIGHTBRACKET, 0x1b);
+    MAP(SDL_SCANCODE_A, 0x1e);
+    MAP(SDL_SCANCODE_B, 0x30);
+    MAP(SDL_SCANCODE_C, 0x2e);
+    MAP(SDL_SCANCODE_D, 0x20);
+    MAP(SDL_SCANCODE_E, 0x12);
+    MAP(SDL_SCANCODE_F, 0x21);
+    MAP(SDL_SCANCODE_G, 0x22);
+    MAP(SDL_SCANCODE_H, 0x23);
+    MAP(SDL_SCANCODE_I, 0x17);
+    MAP(SDL_SCANCODE_J, 0x24);
+    MAP(SDL_SCANCODE_K, 0x25);
+    MAP(SDL_SCANCODE_L, 0x26);
+    MAP(SDL_SCANCODE_M, 0x32);
+    MAP(SDL_SCANCODE_N, 0x31);
+    MAP(SDL_SCANCODE_O, 0x18);
+    MAP(SDL_SCANCODE_P, 0x19);
+    MAP(SDL_SCANCODE_Q, 0x10);
+    MAP(SDL_SCANCODE_R, 0x13);
+    MAP(SDL_SCANCODE_S, 0x1f);
+    MAP(SDL_SCANCODE_T, 0x14);
+    MAP(SDL_SCANCODE_U, 0x16);
+    MAP(SDL_SCANCODE_V, 0x2f);
+    MAP(SDL_SCANCODE_W, 0x11);
+    MAP(SDL_SCANCODE_X, 0x2d);
+    MAP(SDL_SCANCODE_Y, 0x15);
+    MAP(SDL_SCANCODE_Z, 0x2c);
+    MAP(SDL_SCANCODE_DELETE, 0xd3);
+    MAP(SDL_SCANCODE_KP_0, 0x52);
+    MAP(SDL_SCANCODE_KP_1, 0x4f);
+    MAP(SDL_SCANCODE_KP_2, 0x50);
+    MAP(SDL_SCANCODE_KP_3, 0x51);
+    MAP(SDL_SCANCODE_KP_4, 0x4b);
+    MAP(SDL_SCANCODE_KP_5, 0x4c);
+    MAP(SDL_SCANCODE_KP_CLEAR, 0x4c);
+    MAP(SDL_SCANCODE_CLEAR, 0x4c);
+    MAP(SDL_SCANCODE_KP_6, 0x4d);
+    MAP(SDL_SCANCODE_KP_7, 0x47);
+    MAP(SDL_SCANCODE_KP_8, 0x48);
+    MAP(SDL_SCANCODE_KP_9, 0x49);
+    MAP(SDL_SCANCODE_KP_PERIOD, 0x53);
+    MAP(SDL_SCANCODE_KP_DIVIDE, 0xb5);
+    MAP(SDL_SCANCODE_KP_MULTIPLY, 0x37);
+    MAP(SDL_SCANCODE_KP_MINUS, 0x4a);
+    MAP(SDL_SCANCODE_KP_PLUS, 0x4e);
+    MAP(SDL_SCANCODE_KP_ENTER, 0x9c);
+    //MAP(SDL_SCANCODE_KP_EQUALS,	);
+    MAP(SDL_SCANCODE_UP, 0xc8);
+    MAP(SDL_SCANCODE_DOWN, 0xd0);
+    MAP(SDL_SCANCODE_RIGHT, 0xcd);
+    MAP(SDL_SCANCODE_LEFT, 0xcb);
+    MAP(SDL_SCANCODE_INSERT, 0xd2);
+    MAP(SDL_SCANCODE_HOME, 0xc7);
+    MAP(SDL_SCANCODE_END, 0xcf);
+    MAP(SDL_SCANCODE_PAGEUP, 0xc9);
+    MAP(SDL_SCANCODE_PAGEDOWN, 0xd1);
+    MAP(SDL_SCANCODE_F1, 0x3b);
+    MAP(SDL_SCANCODE_F2, 0x3c);
+    MAP(SDL_SCANCODE_F3, 0x3d);
+    MAP(SDL_SCANCODE_F4, 0x3e);
+    MAP(SDL_SCANCODE_F5, 0x3f);
+    MAP(SDL_SCANCODE_F6, 0x40);
+    MAP(SDL_SCANCODE_F7, 0x41);
+    MAP(SDL_SCANCODE_F8, 0x42);
+    MAP(SDL_SCANCODE_F9, 0x43);
+    MAP(SDL_SCANCODE_F10, 0x44);
+    MAP(SDL_SCANCODE_F11, 0x57);
+    MAP(SDL_SCANCODE_F12, 0x58);
+    MAP(SDL_SCANCODE_NUMLOCKCLEAR, 0x45);
+    MAP(SDL_SCANCODE_CAPSLOCK, 0x3a);
+    MAP(SDL_SCANCODE_SCROLLLOCK, 0x46);
+    MAP(SDL_SCANCODE_RSHIFT, 0x36);
+    MAP(SDL_SCANCODE_LSHIFT, 0x2a);
+    MAP(SDL_SCANCODE_RCTRL, 0x9d);
+    MAP(SDL_SCANCODE_LCTRL, 0x1d);
+    MAP(SDL_SCANCODE_RALT, 0xb8);
+    MAP(SDL_SCANCODE_LALT, 0x38);
+    MAP(SDL_SCANCODE_LGUI, 0xdb);	// win l
+    MAP(SDL_SCANCODE_RGUI, 0xdc);	// win r
+//    MAP(SDL_SCANCODE_PRINTSCREEN,		-2);	// 0xaa + 0xb7
+    MAP(SDL_SCANCODE_SYSREQ, 0x54);	// alt+printscr
+//    MAP(SDL_SCANCODE_PAUSE,		0xb7);	// ctrl+pause
+    MAP(SDL_SCANCODE_MENU, 0xdd);	// win menu?
+    MAP(SDL_SCANCODE_GRAVE, 0x29);  // tilde
+#undef MAP
+}
+
+
+
+//
+// initmouse() -- init mouse input
+//
+void mouseInit(void)
+{
+    mouseGrabInput(g_mouseEnabled = g_mouseLockedToWindow);  // FIXME - SA
+}
+
+//
+// uninitmouse() -- uninit mouse input
+//
+void mouseUninit(void)
+{
+    mouseGrabInput(0);
+    g_mouseEnabled = 0;
+}
+
+
+//
+// grabmouse_low() -- show/hide mouse cursor, lower level (doesn't check state).
+//                    furthermore return 0 if successful.
+//
+
+char grabmouse_low(char a)
+{
+    /* FIXME: Maybe it's better to make sure that grabmouse_low
+       is called only when a window is ready?                */
+    if (sdl_window)
+        SDL_SetWindowGrab(sdl_window, a ? SDL_TRUE : SDL_FALSE);
+    return SDL_SetRelativeMouseMode(a ? SDL_TRUE : SDL_FALSE);
+}
+
+//
+// grabmouse() -- show/hide mouse cursor
+//
+void mouseGrabInput(bool grab)
+{
+    if (appactive && g_mouseEnabled)
+    {
+        if ((grab != g_mouseGrabbed) && !grabmouse_low(grab))
+            g_mouseGrabbed = grab;
+    }
+    else
+        g_mouseGrabbed = grab;
+
+    inputState.MouseSetPos(0, 0);
+    SDL_ShowCursor(!grab ? SDL_ENABLE : SDL_DISABLE);
+    if (grab) GUICapture &= ~1;
+    else GUICapture |= 1;
+}
+
+
+// So this is how the engine handles text input?
+// Argh. This is just gross.
+int scancodetoasciihack(SDL_Event& ev)
+{
+    int sc = ev.key.keysym.scancode;
+    SDL_Keycode keyvalue = ev.key.keysym.sym;
+    int code = keytranslation[sc];
+    // Modifiers that have to be held down to be effective
+    // (excludes KMOD_NUM, for example).
+    static const int MODIFIERS =
+        KMOD_LSHIFT | KMOD_RSHIFT | KMOD_LCTRL | KMOD_RCTRL |
+        KMOD_LALT | KMOD_RALT | KMOD_LGUI | KMOD_RGUI;
+
+    // XXX: see osd.c, OSD_HandleChar(), there are more...
+    if (
+        (sc == SDL_SCANCODE_RETURN || sc == SDL_SCANCODE_KP_ENTER ||
+            sc == SDL_SCANCODE_ESCAPE ||
+            sc == SDL_SCANCODE_BACKSPACE ||
+            sc == SDL_SCANCODE_TAB ||
+            (((ev.key.keysym.mod) & MODIFIERS) == KMOD_LCTRL &&
+            (sc >= SDL_SCANCODE_A && sc <= SDL_SCANCODE_Z))))
+    {
+        char keyvalue;
+        switch (sc)
+        {
+        case SDL_SCANCODE_RETURN: case SDL_SCANCODE_KP_ENTER: keyvalue = '\r'; break;
+        case SDL_SCANCODE_ESCAPE: keyvalue = 27; break;
+        case SDL_SCANCODE_BACKSPACE: keyvalue = '\b'; break;
+        case SDL_SCANCODE_TAB: keyvalue = '\t'; break;
+        default: keyvalue = sc - SDL_SCANCODE_A + 1; break;  // Ctrl+A --> 1, etc.
+        }
+    }
+    else
+    {
+        /*
+        Necessary for Duke 3D's method of entering cheats to work without showing IMEs.
+        SDL_TEXTINPUT is preferable overall, but with bitmap fonts it has no advantage.
+        */
+        // Note that this is not how text input is supposed to be handled!
+
+        if ('a' <= keyvalue && keyvalue <= 'z')
+        {
+            if (!!(ev.key.keysym.mod & KMOD_SHIFT) ^ !!(ev.key.keysym.mod & KMOD_CAPS))
+                keyvalue -= 'a' - 'A';
+        }
+        else if (ev.key.keysym.mod & KMOD_SHIFT)
+        {
+            keyvalue = g_keyAsciiTableShift[code];
+        }
+        else if (ev.key.keysym.mod & KMOD_NUM) // && !(ev.key.keysym.mod & KMOD_SHIFT)
+        {
+            switch (keyvalue)
+            {
+            case SDLK_KP_1: keyvalue = '1'; break;
+            case SDLK_KP_2: keyvalue = '2'; break;
+            case SDLK_KP_3: keyvalue = '3'; break;
+            case SDLK_KP_4: keyvalue = '4'; break;
+            case SDLK_KP_5: keyvalue = '5'; break;
+            case SDLK_KP_6: keyvalue = '6'; break;
+            case SDLK_KP_7: keyvalue = '7'; break;
+            case SDLK_KP_8: keyvalue = '8'; break;
+            case SDLK_KP_9: keyvalue = '9'; break;
+            case SDLK_KP_0: keyvalue = '0'; break;
+            case SDLK_KP_PERIOD: keyvalue = '.'; break;
+            case SDLK_KP_COMMA: keyvalue = ','; break;
+            }
+        }
+
+        switch (keyvalue)
+        {
+        case SDLK_KP_DIVIDE: keyvalue = '/'; break;
+        case SDLK_KP_MULTIPLY: keyvalue = '*'; break;
+        case SDLK_KP_MINUS: keyvalue = '-'; break;
+        case SDLK_KP_PLUS: keyvalue = '+'; break;
+        }
+    }
+    if (keyvalue >= 0x80) keyvalue = 0; // Sadly ASCII only...
+    return keyvalue;
+}
+
+static void PostMouseMove(int x, int y)
+{
+    static int lastx = 0, lasty = 0;
+    event_t ev = { 0,0,0,0,0,0,0 };
+
+    ev.x = x;
+    ev.y = y;
+    lastx = x;
+    lasty = y;
+    if (ev.x | ev.y)
+    {
+        ev.type = EV_Mouse;
+        D_PostEvent(&ev);
+    }
+}
+
+CVAR(Bool, m_noprescale, false, CVAR_ARCHIVE | CVAR_GLOBALCONFIG)
+
+static void MouseRead()
+{
+    int x, y;
+
+#if 0
+    if (NativeMouse)
+    {
+        return;
+    }
+#endif
+
+    SDL_GetRelativeMouseState(&x, &y);
+    if (!m_noprescale)
+    {
+        x *= 3;
+        y *= 2;
+    }
+    if (x | y)
+    {
+        PostMouseMove(x, -y);
+    }
+}
+
+int32_t handleevents_pollsdl(void)
+{
+    int32_t code, rv = 0, j;
+    SDL_Event ev;
+    event_t evt;
+
+    while (SDL_PollEvent(&ev))
+    {
+        switch (ev.type)
+        {
+        case SDL_TEXTINPUT:
+        {
+            j = 0;
+            const uint8_t* text = (uint8_t*)ev.text.text;
+            while ((j = GetCharFromString(text)))
+            {
+                code = ev.text.text[j];
+                // Fixme: Send an EV_GUI_Event instead and properly deal with Unicode.
+                if ((GUICapture & 1) && menuactive != MENU_WaitKey)
+                {
+                    evt = { EV_GUI_Event, EV_GUI_Char, int16_t(j), !!(SDL_GetModState() & KMOD_ALT) };
+                    D_PostEvent(&evt);
+                }
+            }
+            break;
+        }
+
+        case SDL_KEYDOWN:
+        case SDL_KEYUP:
+        {
+            if ((GUICapture & 1) && menuactive != MENU_WaitKey)
+            {
+                evt = {};
+                evt.type = EV_GUI_Event;
+                evt.subtype = ev.type == SDL_KEYDOWN ? EV_GUI_KeyDown : EV_GUI_KeyUp;
+                SDL_Keymod kmod = SDL_GetModState();
+                evt.data3 = ((kmod & KMOD_SHIFT) ? GKM_SHIFT : 0) |
+                    ((kmod & KMOD_CTRL) ? GKM_CTRL : 0) |
+                    ((kmod & KMOD_ALT) ? GKM_ALT : 0);
+
+                if (evt.subtype == EV_GUI_KeyDown && ev.key.repeat)
+                {
+                    evt.subtype = EV_GUI_KeyRepeat;
+                }
+
+                switch (ev.key.keysym.sym)
+                {
+                case SDLK_KP_ENTER:	evt.data1 = GK_RETURN;	break;
+                case SDLK_PAGEUP:	evt.data1 = GK_PGUP;		break;
+                case SDLK_PAGEDOWN:	evt.data1 = GK_PGDN;		break;
+                case SDLK_END:		evt.data1 = GK_END;		break;
+                case SDLK_HOME:		evt.data1 = GK_HOME;		break;
+                case SDLK_LEFT:		evt.data1 = GK_LEFT;		break;
+                case SDLK_RIGHT:	evt.data1 = GK_RIGHT;		break;
+                case SDLK_UP:		evt.data1 = GK_UP;		break;
+                case SDLK_DOWN:		evt.data1 = GK_DOWN;		break;
+                case SDLK_DELETE:	evt.data1 = GK_DEL;		break;
+                case SDLK_ESCAPE:	evt.data1 = GK_ESCAPE;	break;
+                case SDLK_F1:		evt.data1 = GK_F1;		break;
+                case SDLK_F2:		evt.data1 = GK_F2;		break;
+                case SDLK_F3:		evt.data1 = GK_F3;		break;
+                case SDLK_F4:		evt.data1 = GK_F4;		break;
+                case SDLK_F5:		evt.data1 = GK_F5;		break;
+                case SDLK_F6:		evt.data1 = GK_F6;		break;
+                case SDLK_F7:		evt.data1 = GK_F7;		break;
+                case SDLK_F8:		evt.data1 = GK_F8;		break;
+                case SDLK_F9:		evt.data1 = GK_F9;		break;
+                case SDLK_F10:		evt.data1 = GK_F10;		break;
+                case SDLK_F11:		evt.data1 = GK_F11;		break;
+                case SDLK_F12:		evt.data1 = GK_F12;		break;
+                default:
+                    if (ev.key.keysym.sym < 256)
+                    {
+                        evt.data1 = ev.key.keysym.sym;
+                    }
+                    break;
+                }
+                if (evt.data1 < 128)
+                {
+                    evt.data1 = toupper(evt.data1);
+                    D_PostEvent(&evt);
+                }
+                }
+            else
+            {
+                auto const& sc = ev.key.keysym.scancode;
+                code = keytranslation[sc];
+
+                // The pause key generates a release event right after
+                // the pressing one. As a result, it gets unseen
+                // by the game most of the time.
+                if (code == 0x59 && ev.type == SDL_KEYUP)  // pause
+                    break;
+
+                int keyvalue = ev.type == SDL_KEYDOWN ? scancodetoasciihack(ev) : 0;
+                event_t evt = { (uint8_t)(ev.type == SDL_KEYUP ? EV_KeyUp : EV_KeyDown), 0, (int16_t)code, (int16_t)keyvalue };
+                D_PostEvent(&evt);
+            }
+
+            break;
+            }
+
+        case SDL_MOUSEWHEEL:
+            if (GUICapture)
+            {
+                evt.type = EV_GUI_Event;
+
+                if (ev.wheel.y == 0)
+                    evt.subtype = ev.wheel.x > 0 ? EV_GUI_WheelRight : EV_GUI_WheelLeft;
+                else
+                    evt.subtype = ev.wheel.y > 0 ? EV_GUI_WheelUp : EV_GUI_WheelDown;
+
+                SDL_Keymod kmod = SDL_GetModState();
+                evt.data3 = ((kmod & KMOD_SHIFT) ? GKM_SHIFT : 0) |
+                    ((kmod & KMOD_CTRL) ? GKM_CTRL : 0) |
+                    ((kmod & KMOD_ALT) ? GKM_ALT : 0);
+
+                D_PostEvent(&evt);
+            }
+            else
+            {
+                // This never sends keyup events. They must be delayed for this to work with game buttons.
+                if (ev.wheel.y > 0)
+                {
+                    evt = { EV_KeyDown, 0, (int16_t)KEY_MWHEELUP };
+                    D_PostEvent(&evt);
+                }
+                if (ev.wheel.y < 0)
+                {
+                    evt = { EV_KeyDown, 0, (int16_t)KEY_MWHEELDOWN };
+                    D_PostEvent(&evt);
+                }
+                if (ev.wheel.x > 0)
+                {
+                    evt = { EV_KeyDown, 0, (int16_t)KEY_MWHEELRIGHT };
+                    D_PostEvent(&evt);
+                }
+                if (ev.wheel.y < 0)
+                {
+                    evt = { EV_KeyDown, 0, (int16_t)KEY_MWHEELLEFT };
+                    D_PostEvent(&evt);
+                }
+            }
+            break;
+
+        case SDL_WINDOWEVENT:
+            switch (ev.window.event)
+            {
+            case SDL_WINDOWEVENT_FOCUS_GAINED:
+            case SDL_WINDOWEVENT_FOCUS_LOST:
+                appactive = (ev.window.event == SDL_WINDOWEVENT_FOCUS_GAINED);
+                if (g_mouseGrabbed && g_mouseEnabled)
+                    grabmouse_low(appactive);
+                break;
+
+            case SDL_WINDOWEVENT_MOVED:
+            {
+                WindowMoved(ev.window.data1, ev.window.data2);
+                break;
+            }
+            case SDL_WINDOWEVENT_ENTER:
+                g_mouseInsideWindow = 1;
+                break;
+            case SDL_WINDOWEVENT_LEAVE:
+                g_mouseInsideWindow = 0;
+                break;
+            }
+
+            break;
+
+        case SDL_MOUSEMOTION:
+            //case SDL_JOYBALLMOTION:
+        {
+            // The menus need this, even in non GUI-capture mode
+            event_t event;
+            event.data1 = ev.motion.x;
+            event.data2 = ev.motion.y;
+
+            //screen->ScaleCoordsFromWindow(event.data1, event.data2);
+
+            event.type = EV_GUI_Event;
+            event.subtype = EV_GUI_MouseMove;
+
+            SDL_Keymod kmod = SDL_GetModState();
+            event.data3 = ((kmod & KMOD_SHIFT) ? GKM_SHIFT : 0) |
+                ((kmod & KMOD_CTRL) ? GKM_CTRL : 0) |
+                ((kmod & KMOD_ALT) ? GKM_ALT : 0);
+
+            D_PostEvent(&event);
+            break;
+        }
+
+        case SDL_MOUSEBUTTONDOWN:
+        case SDL_MOUSEBUTTONUP:
+
+        if (!GUICapture)
+        {
+            evt.type = ev.type == SDL_MOUSEBUTTONDOWN ? EV_KeyDown : EV_KeyUp;
+
+            switch (ev.button.button)
+            {
+            case SDL_BUTTON_LEFT:	evt.data1 = KEY_MOUSE1;		break;
+            case SDL_BUTTON_MIDDLE:	evt.data1 = KEY_MOUSE3;		break;
+            case SDL_BUTTON_RIGHT:	evt.data1 = KEY_MOUSE2;		break;
+            case SDL_BUTTON_X1:		evt.data1 = KEY_MOUSE4;		break;
+            case SDL_BUTTON_X2:		evt.data1 = KEY_MOUSE5;		break;
+            case 6:		evt.data1 = KEY_MOUSE6;		break;
+            case 7:		evt.data1 = KEY_MOUSE7;		break;
+            case 8:		evt.data1 = KEY_MOUSE8;		break;
+                //default:	printf("SDL mouse button %s %d\n", sev.type == SDL_MOUSEBUTTONDOWN ? "down" : "up", sev.button.button);	break;
+            }
+
+            if (evt.data1 != 0)
+            {
+                D_PostEvent(&evt);
+            }
+        }
+        else if ((ev.button.button >= SDL_BUTTON_LEFT && ev.button.button <= SDL_BUTTON_X2))
+        {
+            int x, y;
+            SDL_GetMouseState(&x, &y);
+
+            evt.type = EV_GUI_Event;
+            evt.data1 = x;
+            evt.data2 = y;
+
+            //screen->ScaleCoordsFromWindow(event.data1, event.data2);
+
+            if (ev.type == SDL_MOUSEBUTTONDOWN)
+            {
+                switch (ev.button.button)
+                {
+                case SDL_BUTTON_LEFT:   evt.subtype = EV_GUI_LButtonDown;    break;
+                case SDL_BUTTON_MIDDLE: evt.subtype = EV_GUI_MButtonDown;    break;
+                case SDL_BUTTON_RIGHT:  evt.subtype = EV_GUI_RButtonDown;    break;
+                case SDL_BUTTON_X1:     evt.subtype = EV_GUI_BackButtonDown; break;
+                case SDL_BUTTON_X2:     evt.subtype = EV_GUI_FwdButtonDown;  break;
+                default: assert(false); evt.subtype = EV_GUI_None;           break;
+                }
+            }
+            else
+            {
+                switch (ev.button.button)
+                {
+                case SDL_BUTTON_LEFT:   evt.subtype = EV_GUI_LButtonUp;    break;
+                case SDL_BUTTON_MIDDLE: evt.subtype = EV_GUI_MButtonUp;    break;
+                case SDL_BUTTON_RIGHT:  evt.subtype = EV_GUI_RButtonUp;    break;
+                case SDL_BUTTON_X1:     evt.subtype = EV_GUI_BackButtonUp; break;
+                case SDL_BUTTON_X2:     evt.subtype = EV_GUI_FwdButtonUp;  break;
+                default: assert(false); evt.subtype = EV_GUI_None;         break;
+                }
+            }
+
+            SDL_Keymod kmod = SDL_GetModState();
+            evt.data3 = ((kmod & KMOD_SHIFT) ? GKM_SHIFT : 0) |
+                ((kmod & KMOD_CTRL) ? GKM_CTRL : 0) |
+                ((kmod & KMOD_ALT) ? GKM_ALT : 0);
+
+            D_PostEvent(&evt);
+        }
+        break;
+
+#if 0
+        case SDL_JOYAXISMOTION:
+#if SDL_MAJOR_VERSION >= 2
+            if (joystick.isGameController)
+                break;
+            fallthrough__;
+        case SDL_CONTROLLERAXISMOTION:
+#endif
+            if (appactive && ev.jaxis.axis < joystick.numAxes)
+            {
+                joystick.pAxis[ev.jaxis.axis] = ev.jaxis.value;
+                int32_t const scaledValue = ev.jaxis.value * 10000 / 32767;
+                if ((scaledValue < joydead[ev.jaxis.axis]) &&
+                    (scaledValue > -joydead[ev.jaxis.axis]))
+                    joystick.pAxis[ev.jaxis.axis] = 0;
+                else if (scaledValue >= joysatur[ev.jaxis.axis])
+                    joystick.pAxis[ev.jaxis.axis] = 32767;
+                else if (scaledValue <= -joysatur[ev.jaxis.axis])
+                    joystick.pAxis[ev.jaxis.axis] = -32767;
+                else
+                    joystick.pAxis[ev.jaxis.axis] = joystick.pAxis[ev.jaxis.axis] * 10000 / joysatur[ev.jaxis.axis];
+            }
+            break;
+#endif
+
+        case SDL_JOYHATMOTION:
+        {
+            int32_t hatvals[16] = {
+                -1,     // centre
+                0,      // up 1
+                9000,   // right 2
+                4500,   // up+right 3
+                18000,  // down 4
+                -1,     // down+up!! 5
+                13500,  // down+right 6
+                -1,     // down+right+up!! 7
+                27000,  // left 8
+                27500,  // left+up 9
+                -1,     // left+right!! 10
+                -1,     // left+right+up!! 11
+                22500,  // left+down 12
+                -1,     // left+down+up!! 13
+                -1,     // left+down+right!! 14
+                -1,     // left+down+right+up!! 15
+            };
+            if (appactive && ev.jhat.hat < joystick.numHats)
+                joystick.pHat[ev.jhat.hat] = hatvals[ev.jhat.value & 15];
+            break;
+        }
+
+        case SDL_JOYBUTTONDOWN:
+        case SDL_JOYBUTTONUP:
+            if (!GUICapture)
+            {
+                evt.type = ev.type == SDL_JOYBUTTONDOWN ? EV_KeyDown : EV_KeyUp;
+                evt.data1 = KEY_FIRSTJOYBUTTON + ev.jbutton.button;
+                if (evt.data1 != 0)
+                    D_PostEvent(&evt);
+            }
+            break;
+
+        case SDL_QUIT:
+            throw ExitEvent(0);	// completely bypass the hackery in the games to block Alt-F4.
+            return -1;
+
+        default:
+            break;
+        }
+    }
+    MouseRead();
+
+    return rv;
+}
+
+int32_t handleevents(void)
+{
+    int32_t rv;
+
+    if (inputchecked && g_mouseEnabled)
+    {
+        // This is a horrible crutch
+        if (inputState.mouseReadButtons() & WHEELUP_MOUSE)
+        {
+            event_t ev = { EV_KeyUp, 0, (int16_t)KEY_MWHEELUP };
+            D_PostEvent(&ev);
+        }
+        if (inputState.mouseReadButtons() & WHEELDOWN_MOUSE)
+        {
+            event_t ev = { EV_KeyUp, 0, (int16_t)KEY_MWHEELDOWN };
+            D_PostEvent(&ev);
+        }
+        if (inputState.mouseReadButtons() & WHEELLEFT_MOUSE)
+        {
+            event_t ev = { EV_KeyUp, 0, (int16_t)KEY_MWHEELLEFT };
+            D_PostEvent(&ev);
+        }
+        if (inputState.mouseReadButtons() & WHEELRIGHT_MOUSE)
+        {
+            event_t ev = { EV_KeyUp, 0, (int16_t)KEY_MWHEELRIGHT };
+            D_PostEvent(&ev);
+        }
+    }
+
+    rv = handleevents_pollsdl();
+
+    inputchecked = 0;
+    timerUpdateClock();
+    //I_ProcessJoysticks();
+    return rv;
+}
+
+
+int32_t handleevents_peekkeys(void)
+{
+    SDL_PumpEvents();
+
+    return SDL_PeepEvents(NULL, 1, SDL_PEEKEVENT, SDL_KEYDOWN, SDL_KEYDOWN);
+}
+
+void I_SetMouseCapture()
+{
+    // Clear out any mouse movement.
+    SDL_CaptureMouse(SDL_TRUE);
+}
+
+void I_ReleaseMouseCapture()
+{
+    SDL_CaptureMouse(SDL_FALSE);
+}
diff --git a/source/common/input/i_joystick.cpp b/source/common/input/i_joystick.cpp
new file mode 100644
index 000000000..695a44686
--- /dev/null
+++ b/source/common/input/i_joystick.cpp
@@ -0,0 +1,342 @@
+/*
+** i_joystick.cpp
+**
+**---------------------------------------------------------------------------
+** Copyright 2005-2016 Randy Heit
+** All rights reserved.
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions
+** are met:
+**
+** 1. Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+** 2. Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in the
+**    documentation and/or other materials provided with the distribution.
+** 3. The name of the author may not be used to endorse or promote products
+**    derived from this software without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+**---------------------------------------------------------------------------
+**
+*/
+#include <SDL.h>
+
+#include "basics.h"
+#include "templates.h"
+#include "m_joy.h"
+#include "keydef.h"
+
+// Very small deadzone so that floating point magic doesn't happen
+#define MIN_DEADZONE 0.000001f
+
+class SDLInputJoystick: public IJoystickConfig
+{
+public:
+	SDLInputJoystick(int DeviceIndex) : DeviceIndex(DeviceIndex), Multiplier(1.0f)
+	{
+		Device = SDL_JoystickOpen(DeviceIndex);
+		if(Device != NULL)
+		{
+			NumAxes = SDL_JoystickNumAxes(Device);
+			NumHats = SDL_JoystickNumHats(Device);
+
+			SetDefaultConfig();
+		}
+	}
+	~SDLInputJoystick()
+	{
+		if(Device != NULL)
+			M_SaveJoystickConfig(this);
+		SDL_JoystickClose(Device);
+	}
+
+	bool IsValid() const
+	{
+		return Device != NULL;
+	}
+
+	FString GetName()
+	{
+		return SDL_JoystickName(Device);
+	}
+	float GetSensitivity()
+	{
+		return Multiplier;
+	}
+	void SetSensitivity(float scale)
+	{
+		Multiplier = scale;
+	}
+
+	int GetNumAxes()
+	{
+		return NumAxes + NumHats*2;
+	}
+	float GetAxisDeadZone(int axis)
+	{
+		return Axes[axis].DeadZone;
+	}
+	EJoyAxis GetAxisMap(int axis)
+	{
+		return Axes[axis].GameAxis;
+	}
+	const char *GetAxisName(int axis)
+	{
+		return Axes[axis].Name.GetChars();
+	}
+	float GetAxisScale(int axis)
+	{
+		return Axes[axis].Multiplier;
+	}
+
+	void SetAxisDeadZone(int axis, float zone)
+	{
+		Axes[axis].DeadZone = clamp(zone, MIN_DEADZONE, 1.f);
+	}
+	void SetAxisMap(int axis, EJoyAxis gameaxis)
+	{
+		Axes[axis].GameAxis = gameaxis;
+	}
+	void SetAxisScale(int axis, float scale)
+	{
+		Axes[axis].Multiplier = scale;
+	}
+
+	// Used by the saver to not save properties that are at their defaults.
+	bool IsSensitivityDefault()
+	{
+		return Multiplier == 1.0f;
+	}
+	bool IsAxisDeadZoneDefault(int axis)
+	{
+		return Axes[axis].DeadZone <= MIN_DEADZONE;
+	}
+	bool IsAxisMapDefault(int axis)
+	{
+		if(axis >= 5)
+			return Axes[axis].GameAxis == JOYAXIS_None;
+		return Axes[axis].GameAxis == DefaultAxes[axis];
+	}
+	bool IsAxisScaleDefault(int axis)
+	{
+		return Axes[axis].Multiplier == 1.0f;
+	}
+
+	void SetDefaultConfig()
+	{
+		for(int i = 0;i < GetNumAxes();i++)
+		{
+			AxisInfo info;
+			if(i < NumAxes)
+				info.Name.Format("Axis %d", i+1);
+			else
+				info.Name.Format("Hat %d (%c)", (i-NumAxes)/2 + 1, (i-NumAxes)%2 == 0 ? 'x' : 'y');
+			info.DeadZone = MIN_DEADZONE;
+			info.Multiplier = 1.0f;
+			info.Value = 0.0;
+			info.ButtonValue = 0;
+			if(i >= 5)
+				info.GameAxis = JOYAXIS_None;
+			else
+				info.GameAxis = DefaultAxes[i];
+			Axes.Push(info);
+		}
+	}
+	FString GetIdentifier()
+	{
+		char id[16];
+		snprintf(id, countof(id), "JS:%d", DeviceIndex);
+		return id;
+	}
+
+	void AddAxes(float axes[NUM_JOYAXIS])
+	{
+		// Add to game axes.
+		for (int i = 0; i < GetNumAxes(); ++i)
+		{
+			if(Axes[i].GameAxis != JOYAXIS_None)
+				axes[Axes[i].GameAxis] -= float(Axes[i].Value * Multiplier * Axes[i].Multiplier);
+		}
+	}
+
+	void ProcessInput()
+	{
+		uint8_t buttonstate;
+
+		for (int i = 0; i < NumAxes; ++i)
+		{
+			buttonstate = 0;
+
+			Axes[i].Value = SDL_JoystickGetAxis(Device, i)/32767.0;
+			Axes[i].Value = Joy_RemoveDeadZone(Axes[i].Value, Axes[i].DeadZone, &buttonstate);
+
+			// Map button to axis
+			// X and Y are handled differently so if we have 2 or more axes then we'll use that code instead.
+			if (NumAxes == 1 || (i >= 2 && i < NUM_JOYAXISBUTTONS))
+			{
+				Joy_GenerateButtonEvents(Axes[i].ButtonValue, buttonstate, 2, KEY_JOYAXIS1PLUS + i*2);
+				Axes[i].ButtonValue = buttonstate;
+			}
+		}
+
+		if(NumAxes > 1)
+		{
+			buttonstate = Joy_XYAxesToButtons(Axes[0].Value, Axes[1].Value);
+			Joy_GenerateButtonEvents(Axes[0].ButtonValue, buttonstate, 4, KEY_JOYAXIS1PLUS);
+			Axes[0].ButtonValue = buttonstate;
+		}
+
+		// Map POV hats to buttons and axes.  Why axes?  Well apparently I have
+		// a gamepad where the left control stick is a POV hat (instead of the
+		// d-pad like you would expect, no that's pressure sensitive).  Also
+		// KDE's joystick dialog maps them to axes as well.
+		for (int i = 0; i < NumHats; ++i)
+		{
+			AxisInfo &x = Axes[NumAxes + i*2];
+			AxisInfo &y = Axes[NumAxes + i*2 + 1];
+
+			buttonstate = SDL_JoystickGetHat(Device, i);
+
+			// If we're going to assume that we can pass SDL's value into
+			// Joy_GenerateButtonEvents then we might as well assume the format here.
+			if(buttonstate & 0x1) // Up
+				y.Value = -1.0;
+			else if(buttonstate & 0x4) // Down
+				y.Value = 1.0;
+			else
+				y.Value = 0.0;
+			if(buttonstate & 0x2) // Left
+				x.Value = 1.0;
+			else if(buttonstate & 0x8) // Right
+				x.Value = -1.0;
+			else
+				x.Value = 0.0;
+
+			if(i < 4)
+			{
+				Joy_GenerateButtonEvents(x.ButtonValue, buttonstate, 4, KEY_JOYPOV1_UP + i*4);
+				x.ButtonValue = buttonstate;
+			}
+		}
+	}
+
+protected:
+	struct AxisInfo
+	{
+		FString Name;
+		float DeadZone;
+		float Multiplier;
+		EJoyAxis GameAxis;
+		double Value;
+		uint8_t ButtonValue;
+	};
+	static const EJoyAxis DefaultAxes[5];
+
+	int					DeviceIndex;
+	SDL_Joystick		*Device;
+
+	float				Multiplier;
+	TArray<AxisInfo>	Axes;
+	int					NumAxes;
+	int					NumHats;
+};
+const EJoyAxis SDLInputJoystick::DefaultAxes[5] = {JOYAXIS_Side, JOYAXIS_Forward, JOYAXIS_Pitch, JOYAXIS_Yaw, JOYAXIS_Up};
+
+class SDLInputJoystickManager
+{
+public:
+	SDLInputJoystickManager()
+	{
+		for(int i = 0;i < SDL_NumJoysticks();i++)
+		{
+			SDLInputJoystick *device = new SDLInputJoystick(i);
+			if(device->IsValid())
+				Joysticks.Push(device);
+			else
+				delete device;
+		}
+	}
+	~SDLInputJoystickManager()
+	{
+		for(unsigned int i = 0;i < Joysticks.Size();i++)
+			delete Joysticks[i];
+	}
+
+	void AddAxes(float axes[NUM_JOYAXIS])
+	{
+		for(unsigned int i = 0;i < Joysticks.Size();i++)
+			Joysticks[i]->AddAxes(axes);
+	}
+	void GetDevices(TArray<IJoystickConfig *> &sticks)
+	{
+		for(unsigned int i = 0;i < Joysticks.Size();i++)
+		{
+			M_LoadJoystickConfig(Joysticks[i]);
+			sticks.Push(Joysticks[i]);
+		}
+	}
+
+	void ProcessInput() const
+	{
+		for(unsigned int i = 0;i < Joysticks.Size();++i)
+			Joysticks[i]->ProcessInput();
+	}
+protected:
+	TArray<SDLInputJoystick *> Joysticks;
+};
+static SDLInputJoystickManager *JoystickManager;
+
+void I_StartupJoysticks()
+{
+	if(SDL_InitSubSystem(SDL_INIT_JOYSTICK) >= 0)
+		JoystickManager = new SDLInputJoystickManager();
+}
+void I_ShutdownInput()
+{
+	if(JoystickManager)
+	{
+		delete JoystickManager;
+		SDL_QuitSubSystem(SDL_INIT_JOYSTICK);
+	}
+}
+
+void I_GetJoysticks(TArray<IJoystickConfig *> &sticks)
+{
+	sticks.Clear();
+
+	JoystickManager->GetDevices(sticks);
+}
+
+void I_GetAxes(float axes[NUM_JOYAXIS])
+{
+	for (int i = 0; i < NUM_JOYAXIS; ++i)
+	{
+		axes[i] = 0;
+	}
+	if (use_joystick)
+	{
+		JoystickManager->AddAxes(axes);
+	}
+}
+
+void I_ProcessJoysticks()
+{
+	if (use_joystick && JoystickManager)
+		JoystickManager->ProcessInput();
+}
+
+IJoystickConfig *I_UpdateDeviceList()
+{
+	return NULL;
+}
diff --git a/source/common/input/m_joy.cpp b/source/common/input/m_joy.cpp
new file mode 100644
index 000000000..bcdb429d0
--- /dev/null
+++ b/source/common/input/m_joy.cpp
@@ -0,0 +1,331 @@
+/*
+**
+**
+**---------------------------------------------------------------------------
+** Copyright 2005-2016 Randy Heit
+** All rights reserved.
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions
+** are met:
+**
+** 1. Redistributions of source code must retain the above copyright
+**    notice, this list of conditions and the following disclaimer.
+** 2. Redistributions in binary form must reproduce the above copyright
+**    notice, this list of conditions and the following disclaimer in the
+**    documentation and/or other materials provided with the distribution.
+** 3. The name of the author may not be used to endorse or promote products
+**    derived from this software without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+**---------------------------------------------------------------------------
+**
+*/
+// HEADER FILES ------------------------------------------------------------
+
+#include <math.h>
+#include "vectors.h"
+#include "m_joy.h"
+#include "gameconfigfile.h"
+#include "d_event.h"
+
+// MACROS ------------------------------------------------------------------
+
+// TYPES -------------------------------------------------------------------
+
+// EXTERNAL FUNCTION PROTOTYPES --------------------------------------------
+
+// PUBLIC FUNCTION PROTOTYPES ----------------------------------------------
+
+// PRIVATE FUNCTION PROTOTYPES ---------------------------------------------
+
+// EXTERNAL DATA DECLARATIONS ----------------------------------------------
+
+#if 0
+EXTERN_CVAR(Bool, joy_ps2raw)
+EXTERN_CVAR(Bool, joy_dinput)
+EXTERN_CVAR(Bool, joy_xinput)
+#endif
+
+// PUBLIC DATA DEFINITIONS -------------------------------------------------
+
+CUSTOM_CVAR(Bool, use_joystick, false, CVAR_ARCHIVE|CVAR_GLOBALCONFIG|CVAR_NOINITCALL)
+{
+#if 0//def _WIN32
+	joy_ps2raw.Callback();
+	joy_dinput.Callback();
+	joy_xinput.Callback();
+#endif
+}
+
+// PRIVATE DATA DEFINITIONS ------------------------------------------------
+
+// Bits 0 is X+, 1 is X-, 2 is Y+, and 3 is Y-.
+static uint8_t JoyAngleButtons[8] = { 1, 1+4, 4, 2+4, 2, 2+8, 8, 1+8 };
+
+// CODE --------------------------------------------------------------------
+
+//==========================================================================
+//
+// IJoystickConfig - Virtual Destructor
+//
+//==========================================================================
+
+IJoystickConfig::~IJoystickConfig()
+{
+}
+
+//==========================================================================
+//
+// M_SetJoystickConfigSection
+//
+// Sets up the config for reading or writing this controller's axis config. 
+//
+//==========================================================================
+
+static bool M_SetJoystickConfigSection(IJoystickConfig *joy, bool create)
+{
+	FString id = "Joy:";
+	id += joy->GetIdentifier();
+	return GameConfig->SetSection(id, create);
+}
+
+//==========================================================================
+//
+// M_LoadJoystickConfig
+//
+//==========================================================================
+
+bool M_LoadJoystickConfig(IJoystickConfig *joy)
+{
+	char key[32];
+	const char *value;
+	int axislen;
+	int numaxes;
+
+	joy->SetDefaultConfig();
+	if (!M_SetJoystickConfigSection(joy, false))
+	{
+		return false;
+	}
+	value = GameConfig->GetValueForKey("Sensitivity");
+	if (value != NULL)
+	{
+		joy->SetSensitivity((float)atof(value));
+	}
+	numaxes = joy->GetNumAxes();
+	for (int i = 0; i < numaxes; ++i)
+	{
+		axislen = snprintf(key, countof(key), "Axis%u", i);
+
+		snprintf(key + axislen, countof(key) - axislen, "deadzone");
+		value = GameConfig->GetValueForKey(key);
+		if (value != NULL)
+		{
+			joy->SetAxisDeadZone(i, (float)atof(value));
+		}
+
+		snprintf(key + axislen, countof(key) - axislen, "scale");
+		value = GameConfig->GetValueForKey(key);
+		if (value != NULL)
+		{
+			joy->SetAxisScale(i, (float)atof(value));
+		}
+
+		snprintf(key + axislen, countof(key) - axislen, "map");
+		value = GameConfig->GetValueForKey(key);
+		if (value != NULL)
+		{
+			EJoyAxis gameaxis = (EJoyAxis)atoi(value);
+			if (gameaxis < JOYAXIS_None || gameaxis >= NUM_JOYAXIS)
+			{
+				gameaxis = JOYAXIS_None;
+			}
+			joy->SetAxisMap(i, gameaxis);
+		}
+	}
+	return true;
+}
+
+//==========================================================================
+//
+// M_SaveJoystickConfig
+//
+// Only saves settings that are not at their defaults.
+//
+//==========================================================================
+
+void M_SaveJoystickConfig(IJoystickConfig *joy)
+{
+	char key[32], value[32];
+	int axislen, numaxes;
+
+	if (M_SetJoystickConfigSection(joy, true))
+	{
+		GameConfig->ClearCurrentSection();
+		if (!joy->IsSensitivityDefault())
+		{
+			snprintf(value, countof(value), "%g", joy->GetSensitivity());
+			GameConfig->SetValueForKey("Sensitivity", value);
+		}
+		numaxes = joy->GetNumAxes();
+		for (int i = 0; i < numaxes; ++i)
+		{
+			axislen = snprintf(key, countof(key), "Axis%u", i);
+
+			if (!joy->IsAxisDeadZoneDefault(i))
+			{
+				snprintf(key + axislen, countof(key) - axislen, "deadzone");
+				snprintf(value, countof(value), "%g", joy->GetAxisDeadZone(i));
+				GameConfig->SetValueForKey(key, value);
+			}
+			if (!joy->IsAxisScaleDefault(i))
+			{
+				snprintf(key + axislen, countof(key) - axislen, "scale");
+				snprintf(value, countof(value), "%g", joy->GetAxisScale(i));
+				GameConfig->SetValueForKey(key, value);
+			}
+			if (!joy->IsAxisMapDefault(i))
+			{
+				snprintf(key + axislen, countof(key) - axislen, "map");
+				snprintf(value, countof(value), "%d", joy->GetAxisMap(i));
+				GameConfig->SetValueForKey(key, value);
+			}
+		}
+		// If the joystick is entirely at its defaults, delete this section
+		// so that we don't write out a lone section header.
+		if (GameConfig->SectionIsEmpty())
+		{
+			GameConfig->DeleteCurrentSection();
+		}
+	}
+}
+
+
+//===========================================================================
+//
+// Joy_RemoveDeadZone
+//
+//===========================================================================
+
+double Joy_RemoveDeadZone(double axisval, double deadzone, uint8_t *buttons)
+{
+	uint8_t butt;
+
+	// Cancel out deadzone.
+	if (fabs(axisval) < deadzone)
+	{
+		axisval = 0;
+		butt = 0;
+	}
+	// Make the dead zone the new 0.
+	else if (axisval < 0)
+	{
+		axisval = (axisval + deadzone) / (1.0 - deadzone);
+		butt = 2;	// button minus
+	}
+	else
+	{
+		axisval = (axisval - deadzone) / (1.0 - deadzone);
+		butt = 1;	// button plus
+	}
+	if (buttons != NULL)
+	{
+		*buttons = butt;
+	}
+	return axisval;
+}
+
+//===========================================================================
+//
+// Joy_XYAxesToButtons
+//
+// Given two axes, returns a button set for them. They should have already
+// been sent through Joy_RemoveDeadZone. For axes that share the same
+// physical stick, the angle the stick forms should determine whether or
+// not the four component buttons are present. Going by deadzone alone gives
+// you huge areas where you have to buttons pressed and thin strips where
+// you only have one button pressed. For DirectInput gamepads, there is
+// not much standard for how the right stick is presented, so we can only
+// do this for the left stick for those, since X and Y axes are pretty
+// standard. For XInput and Raw PS2 controllers, both sticks are processed
+// through here.
+//
+//===========================================================================
+
+int Joy_XYAxesToButtons(double x, double y)
+{
+	if (x == 0 && y == 0)
+	{
+		return 0;
+	}
+	double rad = atan2(y, x);
+	if (rad < 0)
+	{
+		rad += 2*pi::pi();
+	}
+	// The circle is divided into eight segments for corresponding
+	// button combinations. Each segment is pi/4 radians wide. We offset
+	// by half this so that the segments are centered around the ideal lines
+	// their buttons represent instead of being right on the lines.
+	rad += pi::pi()/8;		// Offset
+	rad *= 4/pi::pi();		// Convert range from [0,2pi) to [0,8)
+	return JoyAngleButtons[int(rad) & 7];
+}
+
+//===========================================================================
+//
+// Joy_GenerateButtonEvents
+//
+// Provided two bitmasks for a set of buttons, generates events to reflect
+// any changes from the old to new set, where base is the key for bit 0,
+// base+1 is the key for bit 1, etc.
+//
+//===========================================================================
+
+void Joy_GenerateButtonEvents(int oldbuttons, int newbuttons, int numbuttons, int base)
+{
+	int changed = oldbuttons ^ newbuttons;
+	if (changed != 0)
+	{
+		event_t ev = { 0, 0, 0, 0, 0, 0, 0 };
+		int mask = 1;
+		for (int j = 0; j < numbuttons; mask <<= 1, ++j)
+		{
+			if (changed & mask)
+			{
+				ev.data1 = base + j;
+				ev.type = (newbuttons & mask) ? EV_KeyDown : EV_KeyUp;
+				D_PostEvent(&ev);
+			}
+		}
+	}
+}
+
+void Joy_GenerateButtonEvents(int oldbuttons, int newbuttons, int numbuttons, const int *keys)
+{
+	int changed = oldbuttons ^ newbuttons;
+	if (changed != 0)
+	{
+		event_t ev = { 0, 0, 0, 0, 0, 0, 0 };
+		int mask = 1;
+		for (int j = 0; j < numbuttons; mask <<= 1, ++j)
+		{
+			if (changed & mask)
+			{
+				ev.data1 = keys[j];
+				ev.type = (newbuttons & mask) ? EV_KeyDown : EV_KeyUp;
+				D_PostEvent(&ev);
+			}
+		}
+	}
+}
diff --git a/source/common/input/m_joy.h b/source/common/input/m_joy.h
new file mode 100644
index 000000000..90cea3e14
--- /dev/null
+++ b/source/common/input/m_joy.h
@@ -0,0 +1,65 @@
+#ifndef M_JOY_H
+#define M_JOY_H
+
+#include <stdint.h>
+#include "tarray.h"
+#include "c_cvars.h"
+
+enum EJoyAxis
+{
+	JOYAXIS_None = -1,
+	JOYAXIS_Yaw,
+	JOYAXIS_Pitch,
+	JOYAXIS_Forward,
+	JOYAXIS_Side,
+	JOYAXIS_Up,
+//	JOYAXIS_Roll,		// Ha ha. No roll for you.
+	NUM_JOYAXIS,
+};
+
+// Generic configuration interface for a controller.
+struct IJoystickConfig
+{
+	virtual ~IJoystickConfig() = 0;
+	
+	virtual FString GetName() = 0;
+	virtual float GetSensitivity() = 0;
+	virtual void SetSensitivity(float scale) = 0;
+
+	virtual int GetNumAxes() = 0;
+	virtual float GetAxisDeadZone(int axis) = 0;
+	virtual EJoyAxis GetAxisMap(int axis) = 0;
+	virtual const char *GetAxisName(int axis) = 0;
+	virtual float GetAxisScale(int axis) = 0;
+
+	virtual void SetAxisDeadZone(int axis, float zone) = 0;
+	virtual void SetAxisMap(int axis, EJoyAxis gameaxis) = 0;
+	virtual void SetAxisScale(int axis, float scale) = 0;
+
+	// Used by the saver to not save properties that are at their defaults.
+	virtual bool IsSensitivityDefault() = 0;
+	virtual bool IsAxisDeadZoneDefault(int axis) = 0;
+	virtual bool IsAxisMapDefault(int axis) = 0;
+	virtual bool IsAxisScaleDefault(int axis) = 0;
+
+	virtual void SetDefaultConfig() = 0;
+	virtual FString GetIdentifier() = 0;
+};
+
+EXTERN_CVAR(Bool, use_joystick);
+
+bool M_LoadJoystickConfig(IJoystickConfig *joy);
+void M_SaveJoystickConfig(IJoystickConfig *joy);
+
+void Joy_GenerateButtonEvents(int oldbuttons, int newbuttons, int numbuttons, int base);
+void Joy_GenerateButtonEvents(int oldbuttons, int newbuttons, int numbuttons, const int *keys);
+int Joy_XYAxesToButtons(double x, double y);
+double Joy_RemoveDeadZone(double axisval, double deadzone, uint8_t *buttons);
+
+// These ought to be provided by a system-specific i_input.cpp.
+void I_GetAxes(float axes[NUM_JOYAXIS]);
+void I_GetJoysticks(TArray<IJoystickConfig *> &sticks);
+IJoystickConfig *I_UpdateDeviceList();
+extern void UpdateJoystickMenu(IJoystickConfig *);
+
+#endif
diff --git a/source/common/inputstate.cpp b/source/common/inputstate.cpp
index f5a3e6ced..ef7c45452 100644
--- a/source/common/inputstate.cpp
+++ b/source/common/inputstate.cpp
@@ -83,6 +83,8 @@ void InputState::AddEvent(const event_t *ev)
 			case KEY_MOUSE4	: mouseSetBit(THUMB_MOUSE, ev->type == EV_KeyDown); break;
 			case KEY_MWHEELUP: mouseSetBit(WHEELUP_MOUSE, ev->type == EV_KeyDown); break;
 			case KEY_MWHEELDOWN: mouseSetBit(WHEELDOWN_MOUSE, ev->type == EV_KeyDown); break;
+			case KEY_MWHEELLEFT: mouseSetBit(WHEELLEFT_MOUSE, ev->type == EV_KeyDown); break;
+			case KEY_MWHEELRIGHT: mouseSetBit(WHEELRIGHT_MOUSE, ev->type == EV_KeyDown); break;
 			case KEY_MOUSE5: mouseSetBit(THUMB2_MOUSE, ev->type == EV_KeyDown); break;
 			default: break;
 		}
diff --git a/source/common/inputstate.h b/source/common/inputstate.h
index 12b973ca3..6325c305f 100644
--- a/source/common/inputstate.h
+++ b/source/common/inputstate.h
@@ -39,6 +39,8 @@ enum EMouseBits
 	WHEELUP_MOUSE  = 16,
 	WHEELDOWN_MOUSE= 32,
 	THUMB2_MOUSE    = 64,
+	WHEELLEFT_MOUSE = 128,
+	WHEELRIGHT_MOUSE = 256,
 };
 
 enum
diff --git a/source/mact/src/control.cpp b/source/mact/src/control.cpp
index 6eb85ce87..05aae41ac 100644
--- a/source/mact/src/control.cpp
+++ b/source/mact/src/control.cpp
@@ -18,8 +18,6 @@
 #include "pragmas.h"
 
 bool CONTROL_Started         = false;
-bool CONTROL_MouseEnabled    = false;
-bool CONTROL_MousePresent    = false;
 bool CONTROL_JoyPresent      = false;
 bool CONTROL_JoystickEnabled = false;
 
@@ -368,7 +366,7 @@ static void CONTROL_PollDevices(ControlInfo *info)
 {
     memset(info, 0, sizeof(ControlInfo));
 
-    if (CONTROL_MouseEnabled)
+    if (in_mouse)
         inputState.GetMouseDelta(info);
 
     if (CONTROL_JoystickEnabled)
@@ -531,8 +529,6 @@ bool CONTROL_Startup(controltype which, int32_t(*TimeFunction)(void), int32_t ti
 
     CONTROL_NumMouseButtons = MAXMOUSEBUTTONS;
     mouseInit();
-    CONTROL_MousePresent = ((inputdevices & 2) == 2);
-    CONTROL_MouseEnabled    = CONTROL_MousePresent;
 
     CONTROL_ResetJoystickValues();