diff --git a/src/android/i_system.c b/src/android/i_system.c
index 150cbd505c1d40b40c1e5ac69bc911ec8eb849df..58fca7c1943448ff81ede9a67854f3791c93bc00 100644
--- a/src/android/i_system.c
+++ b/src/android/i_system.c
@@ -258,6 +258,18 @@ INT32 I_PutEnv(char *variable)
   return -1;
 }
 
+INT32 I_ClipboardCopy(const char *data, size_t size)
+{
+	(void)data;
+	(void)size;
+	return -1;
+}
+
+char *I_ClipboardPaste(void)
+{
+	return NULL;
+}
+
 void I_RegisterSysCommands(void) {}
 
 #include "../sdl/dosstr.c"
diff --git a/src/console.c b/src/console.c
index 11aa5cd56b5eec9e3ef1c94b2c9b61f18af11c69..02324ada20a201d4703aeba4eb25784229f51b12 100644
--- a/src/console.c
+++ b/src/console.c
@@ -641,6 +641,12 @@ boolean CON_Responder(event_t *ev)
 
 	key = ev->data1;
 
+	// Always eat ctrl/shift/alt, so the menu doesn't get ideas
+	if (key == KEY_LSHIFT || key == KEY_RSHIFT
+	 || key == KEY_LCTRL || key == KEY_RCTRL
+	 || key == KEY_LALT || key == KEY_RALT)
+		return true;
+
 	// check for console toggle key
 	if (ev->type != ev_console)
 	{
@@ -677,67 +683,70 @@ boolean CON_Responder(event_t *ev)
 
 	}
 
-	// command completion forward (tab) and backward (shift-tab)
-	if (key == KEY_TAB)
+	// ctrl modifier -- changes behavior, adds shortcuts
+	if (ctrldown)
 	{
 		// show all cvars/commands that match what we have inputted
-		if (ctrldown)
+		if (key == KEY_TAB)
 		{
-			UINT32 i;
-			size_t stop = input_cx - 1;
-			char nameremainder[255];
+			size_t i, len = strlen(inputlines[inputline]+1);
 
-			if (input_cx < 2 || strlen(inputlines[inputline]+1) >= 80)
+			if (input_cx < 2 || len >= 80 || strchr(inputlines[inputline]+1, ' '))
 				return true;
 
 			strcpy(completion, inputlines[inputline]+1);
 
-			// trimming: stop at the first newline
-			for (i = 0; i < input_cx - 1; ++i)
-			{
-				if (completion[i] == ' ')
-				{
-					completion[i] = '\0';
-					stop = i;
-					break;
-				}
-			}
-
-			i = 0;
-
 			//first check commands
 			CONS_Printf("\nCommands:\n");
-
-			for (cmd = COM_CompleteCommand(completion, i); cmd; cmd = COM_CompleteCommand(completion, i))
-			{
-				strncpy(nameremainder, cmd+(stop), strlen(cmd)-(stop));
-				nameremainder[strlen(cmd)-(stop)] = '\0';
-
-				CONS_Printf("  \x83" "%s" "\x80" "%s\n", completion, nameremainder);
-				++i;
-			}
-			if (i == 0)
-				CONS_Printf("  (none)\n");
-
-			i = 0;
+			for (i = 0, cmd = COM_CompleteCommand(completion, i); cmd; cmd = COM_CompleteCommand(completion, ++i))
+				CONS_Printf("  \x83" "%s" "\x80" "%s\n", completion, cmd+len);
+			if (i == 0) CONS_Printf("  (none)\n");
 
 			//now we move on to CVARs
 			CONS_Printf("Variables:\n");
+			for (i = 0, cmd = CV_CompleteVar(completion, i); cmd; cmd = CV_CompleteVar(completion, ++i))
+				CONS_Printf("  \x83" "%s" "\x80" "%s\n", completion, cmd+len);
+			if (i == 0) CONS_Printf("  (none)\n");
 
-			for (cmd = CV_CompleteVar(completion, i); cmd; cmd = CV_CompleteVar(completion, i))
-			{
-				strncpy(nameremainder, cmd+(stop), strlen(cmd)-(stop));
-				nameremainder[strlen(cmd)-(stop)] = '\0';
+			return true;
+		}
+		// ---
 
-				CONS_Printf("  \x83" "%s" "\x80" "%s\n", completion, nameremainder);
-				++i;
-			}
-			if (i == 0)
-				CONS_Printf("  (none)\n");
+		if (key == KEY_HOME) // oldest text in buffer
+		{
+			con_scrollup = (con_totallines-((con_curlines-16)>>3));
+			return true;
+		}
+		else if (key == KEY_END) // most recent text in buffer
+		{
+			con_scrollup = 0;
+			return true;
+		}
 
+		if (key == 'x' || key == 'X')
+		{
+			CONS_Printf("Cut\n");
+			return true;
+		}
+		else if (key == 'c' || key == 'C')
+		{
+			CONS_Printf("Copy\n");
+			I_ClipboardCopy(inputlines[inputline]+1, strlen(inputlines[inputline]+1));
 			return true;
 		}
+		else if (key == 'v' || key == 'V')
+		{
+			CONS_Printf("Paste: %s\n", I_ClipboardPaste());
+			return true;
+		}
+
+		// don't eat the key
+		return false;
+	}
 
+	// command completion forward (tab) and backward (shift-tab)
+	if (key == KEY_TAB)
+	{
 		// sequential command completion forward and backward
 
 		// remember typing for several completions (a-la-4dos)
@@ -815,16 +824,7 @@ boolean CON_Responder(event_t *ev)
 		return true;
 	}
 
-	if (key == KEY_HOME) // oldest text in buffer
-	{
-		con_scrollup = (con_totallines-((con_curlines-16)>>3));
-		return true;
-	}
-	else if (key == KEY_END) // most recent text in buffer
-	{
-		con_scrollup = 0;
-		return true;
-	}
+
 
 	// command enter
 	if (key == KEY_ENTER)
diff --git a/src/djgppdos/i_system.c b/src/djgppdos/i_system.c
index 854d68f4d5b98ac3e145468ef3eaac4949af69a6..dae9ed16e3b426d195bf91c8ee62890fc42d7f48 100644
--- a/src/djgppdos/i_system.c
+++ b/src/djgppdos/i_system.c
@@ -1721,6 +1721,18 @@ INT32 I_PutEnv(char *variable)
 	return putenv(variable);
 }
 
+INT32 I_ClipboardCopy(const char *data, size_t size)
+{
+	(void)data;
+	(void)size;
+	return -1;
+}
+
+char *I_ClipboardPaste(void)
+{
+	return NULL;
+}
+
 const CPUInfoFlags *I_CPUInfo(void)
 {
 	static CPUInfoFlags DOS_CPUInfo;
diff --git a/src/dummy/i_system.c b/src/dummy/i_system.c
index c1e48b60bb0239de595447dc9999ecb4ec949073..a3fe3077c2a46f6956a5723eb1de40127da7e798 100644
--- a/src/dummy/i_system.c
+++ b/src/dummy/i_system.c
@@ -162,6 +162,18 @@ INT32 I_PutEnv(char *variable)
 	return -1;
 }
 
+INT32 I_ClipboardCopy(const char *data, size_t size)
+{
+	(void)data;
+	(void)size;
+	return -1;
+}
+
+char *I_ClipboardPaste(void)
+{
+	return NULL;
+}
+
 void I_RegisterSysCommands(void) {}
 
 #include "../sdl/dosstr.c"
diff --git a/src/i_system.h b/src/i_system.h
index c161851e05ce93c6c460da4575f1c14f8dc5bbc4..d61f2d16e7730c8516b556d3d1f9705e5fb565fc 100644
--- a/src/i_system.h
+++ b/src/i_system.h
@@ -296,6 +296,14 @@ char *I_GetEnv(const char *name);
 
 INT32 I_PutEnv(char *variable);
 
+/** \brief Put data in system clipboard
+*/
+INT32 I_ClipboardCopy(const char *data, size_t size);
+
+/** \brief Retrieve data from system clipboard
+*/
+const char *I_ClipboardPaste(void);
+
 void I_RegisterSysCommands(void);
 
 #endif
diff --git a/src/nds/i_system.c b/src/nds/i_system.c
index 0ed58029c194c12ce43c977c54b4cc0a18250b58..3e5c4b8c6d33fb6d266d12aad24e3d50748a9769 100644
--- a/src/nds/i_system.c
+++ b/src/nds/i_system.c
@@ -269,6 +269,18 @@ INT32 I_PutEnv(char *variable)
 	return -1;
 }
 
+INT32 I_ClipboardCopy(const char *data, size_t size)
+{
+	(void)data;
+	(void)size;
+	return -1;
+}
+
+char *I_ClipboardPaste(void)
+{
+	return NULL;
+}
+
 void I_RegisterSysCommands(void) {}
 
 #include "../sdl/dosstr.c"
diff --git a/src/sdl/i_system.c b/src/sdl/i_system.c
index ea8ade8c1f5b901e800810f64c691982040ada8e..8c4331aa0c96e8ebd3c70d40faa499ae994215d8 100644
--- a/src/sdl/i_system.c
+++ b/src/sdl/i_system.c
@@ -260,6 +260,18 @@ static char returnWadPath[256];
 #include "../byteptr.h"
 #endif
 
+// FAKE_CLIPBOARD simulates system clipboard, but is just local to our app
+#ifndef FAKE_CLIPBOARD
+#include "SDL_syswm.h"
+
+#if defined (_WIN32) && !defined (_XBOX)
+#include <winuser.h>
+#endif
+
+// TODO: clipboard support for other OSes
+
+#endif
+
 /**	\brief	The JoyReset function
 
 	\param	JoySet	Joystick info to reset
@@ -2647,6 +2659,109 @@ INT32 I_PutEnv(char *variable)
 #endif
 }
 
+#ifdef FAKE_CLIPBOARD
+static char __clipboard[512] = "";
+#endif
+
+INT32 I_ClipboardCopy(const char *data, size_t size)
+{
+#ifdef FAKE_CLIPBOARD
+	if (size >= 512) size = 511;
+	memcpy(__clipboard, data, size);
+	__clipboard[size] = 0;
+	return 0;
+#else // !FAKE_CLIPBOARD
+
+#if defined(_WIN32) && !defined(_XBOX)
+	SDL_SysWMinfo syswm;
+	HGLOBAL *storage_ptr;
+	char *storage_raw;
+
+	SDL_VERSION(&syswm.version);
+
+	// Get window (HWND) information, use that to open the clipboard
+	if (!SDL_GetWindowWMInfo(window, &syswm) || !(OpenClipboard(syswm.info.win.window)))
+		return -1;
+
+	// Erase clipboard contents -- if we have nothing to copy, just leave them blank
+	EmptyClipboard();
+	if (size == 0)
+		goto clipboardend;
+
+	// Allocate movable global memory to store our text.
+	if (!(storage_ptr = GlobalAlloc(GMEM_MOVEABLE, size+1))
+	 || !(storage_raw = (char *)GlobalLock(storage_ptr))) // Lock our pointer (gives us access to its data)
+		goto clipboardfail;
+	memcpy(storage_raw, data, size);
+	storage_raw[size] = 0;
+	GlobalUnlock(storage_ptr); // Unlock it before sending it off
+	if (!SetClipboardData(CF_TEXT, storage_ptr)) // NOTE: this removes our permissions from the pointer
+		goto clipboardfail;
+
+clipboardend:
+	CloseClipboard();
+	return 0; //OpenClipboard();
+clipboardfail:
+	CloseClipboard(); // Don't leave me hanging
+	return -1;
+#else
+	(void)data;
+	(void)size;
+	return -1;
+#endif
+
+#endif // FAKE_CLIPBOARD
+}
+
+const char *I_ClipboardPaste(void)
+{
+#ifdef FAKE_CLIPBOARD
+	return (const char *)&__clipboard;
+#else // !FAKE_CLIPBOARD
+
+#if defined(_WIN32) && !defined(_XBOX)
+	HGLOBAL *clipboard_ptr;
+	const char *clipboard_raw;
+	static char clipboard_contents[512];
+	char *i = clipboard_contents;
+
+	if (!IsClipboardFormatAvailable(CF_TEXT))
+		return NULL; // Data either unavailable, or in wrong format
+	OpenClipboard(NULL); // NOTE: Don't need window pointer to get, only to set
+	if (!(clipboard_ptr = GetClipboardData(CF_TEXT))) // Get global pointer
+		return NULL;
+	if (!(clipboard_raw = (const char *)GlobalLock(clipboard_ptr))) // Lock access -- gives us direct ptr to data
+	{
+		CloseClipboard(); // Don't leave me hanging if we fail
+		return NULL;
+	}
+	memcpy(clipboard_contents, clipboard_raw, 511);
+	GlobalUnlock(clipboard_ptr); // Unlock for other apps
+	CloseClipboard(); // And make sure to close the clipboard for other apps too
+	clipboard_contents[511] = 0; // Done after unlock to cause as little interference as possible
+
+	while (*i)
+	{
+		if (*i == '\n' || *i == '\r')
+		{ // End on newline
+			*i = 0;
+			break;
+		}
+		else if (*i == '\t')
+			*i = ' '; // Tabs become spaces
+		else if (*i < 32 || (unsigned)*i > 127)
+			*i = '?'; // Nonprintable chars become question marks
+		++i;
+	}
+
+	return (const char *)&clipboard_contents;
+#else
+	return NULL;
+#endif
+
+#endif // FAKE_CLIPBOARD
+}
+
 /**	\brief	The isWadPathOk function
 
 	\param	path	string path to check
diff --git a/src/sdl/ogl_sdl.h b/src/sdl/ogl_sdl.h
index 7e144644ce325d7c18b1d0916ad3fdae3f384421..2d6209f2bb94c1e28e35e96e949c5ddb641f567c 100644
--- a/src/sdl/ogl_sdl.h
+++ b/src/sdl/ogl_sdl.h
@@ -24,7 +24,6 @@ boolean OglSdlSurface(INT32 w, INT32 h);
 
 void OglSdlFinishUpdate(boolean vidwait);
 
-extern SDL_Window *window;
 extern SDL_Renderer *renderer;
 extern SDL_GLContext sdlglcontext;
 extern Uint16      realwidth;
diff --git a/src/sdl/sdlmain.h b/src/sdl/sdlmain.h
index 7ac32f4b3bf0268be5aaada164426a207195d790..fea1e16487dd8f5c4f7acfbbcf2de94d9ea77b1b 100644
--- a/src/sdl/sdlmain.h
+++ b/src/sdl/sdlmain.h
@@ -71,4 +71,7 @@ void I_GetConsoleEvents(void);
 
 void SDLforceUngrabMouse(void);
 
+// Needed for some WIN32 functions
+extern SDL_Window *window;
+
 #endif
diff --git a/src/sdl12/i_system.c b/src/sdl12/i_system.c
index 888a6a507637e5a6c5d4e2c5de9099fcf34933d1..ed0db653d1f51df07ae714199f5bd9f68c05de21 100644
--- a/src/sdl12/i_system.c
+++ b/src/sdl12/i_system.c
@@ -2666,6 +2666,18 @@ INT32 I_PutEnv(char *variable)
 #endif
 }
 
+INT32 I_ClipboardCopy(const char *data, size_t size)
+{
+	(void)data;
+	(void)size;
+	return -1;
+}
+
+char *I_ClipboardPaste(void)
+{
+	return NULL;
+}
+
 /**	\brief	The isWadPathOk function
 
 	\param	path	string path to check
diff --git a/src/win32/win_sys.c b/src/win32/win_sys.c
index 0331080c8e42acf6941f20ce39588a71e416acc3..eec043803ddcf1a2c349785c5c62ac2381ef668d 100644
--- a/src/win32/win_sys.c
+++ b/src/win32/win_sys.c
@@ -3598,6 +3598,18 @@ INT32 I_PutEnv(char *variable)
 	return putenv(variable);
 }
 
+INT32 I_ClipboardCopy(const char *data, size_t size)
+{
+	(void)data;
+	(void)size;
+	return -1;
+}
+
+char *I_ClipboardPaste(void)
+{
+	return NULL;
+}
+
 typedef BOOL (WINAPI *p_IsProcessorFeaturePresent) (DWORD);
 
 const CPUInfoFlags *I_CPUInfo(void)
diff --git a/src/win32ce/win_sys.c b/src/win32ce/win_sys.c
index 88764ef733c70f2400ce9101433f69b7fd441337..3b6a472587cc161f214736005c48df406dde9925 100644
--- a/src/win32ce/win_sys.c
+++ b/src/win32ce/win_sys.c
@@ -3470,6 +3470,18 @@ INT32 I_PutEnv(char *variable)
 	return putenv(variable);
 }
 
+INT32 I_ClipboardCopy(const char *data, size_t size)
+{
+	(void)data;
+	(void)size;
+	return -1;
+}
+
+char *I_ClipboardPaste(void)
+{
+	return NULL;
+}
+
 typedef BOOL (WINAPI *MyFunc3) (DWORD);
 
 const CPUInfoFlags *I_CPUInfo(void)