gui: add keyboard support for Ctrl layer and more special keys

- rename NotifyKeyboard to NotifyCharInput
- input: handle arrow keys in NotifyKey with standard KEY_* codes
- fix page handler to return 0 from NotifyKey if key was handled
- fix GUIAction::NotifyKey to not swallow all keys
- change home button code from KEY_HOME to KEY_HOMEPAGE
  (to avoid collision with Home/End, conforms to Android 3.0+)

Change-Id: Ib138afa492df8d0c1975415e8b5334c8778ccc90
diff --git a/gui/action.cpp b/gui/action.cpp
index 9f746c3..656c687 100644
--- a/gui/action.cpp
+++ b/gui/action.cpp
@@ -292,12 +292,9 @@
 
 int GUIAction::NotifyKey(int key, bool down)
 {
-	if (mKeys.empty())
-		return 0;
-
 	std::map<int, bool>::iterator itr = mKeys.find(key);
 	if(itr == mKeys.end())
-		return 0;
+		return 1;
 
 	bool prevState = itr->second;
 	itr->second = down;
@@ -312,7 +309,7 @@
 	} else if(down) {
 		for(itr = mKeys.begin(); itr != mKeys.end(); ++itr) {
 			if(!itr->second)
-				return 0;
+				return 1;
 		}
 
 		// Passed, all req buttons are pressed, reset them and consume release events
@@ -1727,7 +1724,7 @@
 
 int GUIAction::getKeyByName(std::string key)
 {
-	if (key == "home")			return KEY_HOME;
+	if (key == "home")		return KEY_HOMEPAGE;  // note: KEY_HOME is cursor movement (like KEY_END)
 	else if (key == "menu")		return KEY_MENU;
 	else if (key == "back")	 	return KEY_BACK;
 	else if (key == "search")	return KEY_SEARCH;
diff --git a/gui/hardwarekeyboard.cpp b/gui/hardwarekeyboard.cpp
index 1f34c5e..9ca607c 100644
--- a/gui/hardwarekeyboard.cpp
+++ b/gui/hardwarekeyboard.cpp
@@ -35,31 +35,14 @@
 static int TranslateKeyCode(int key_code)
 {
 	switch (key_code) {
-		case KEY_HOMEPAGE: // Home key on Asus Transformer hardware keyboard
-			return KEY_HOME;
 		case KEY_SLEEP: // Lock key on Asus Transformer hardware keyboard
 			return KEY_POWER;
 	}
 	return key_code;
 }
 
-int HardwareKeyboard::KeyDown(int key_code)
+static int KeyCodeToChar(int key_code, bool shiftkey, bool ctrlkey)
 {
-#ifdef _EVENT_LOGGING
-	LOGE("HardwareKeyboard::KeyDown %i\n", key_code);
-#endif
-	key_code = TranslateKeyCode(key_code);
-
-	// determine if any Shift key is held down
-	bool shiftkey = false;
-	std::set<int>::iterator it = mPressedKeys.find(KEY_LEFTSHIFT);
-	if (it == mPressedKeys.end())
-		it = mPressedKeys.find(KEY_RIGHTSHIFT);
-	if (it != mPressedKeys.end())
-		shiftkey = true;
-
-	mPressedKeys.insert(key_code);
-
 	int keyboard = -1;
 
 	switch (key_code) {
@@ -285,6 +268,9 @@
 		case KEY_BACKSPACE:
 			keyboard = KEYBOARD_BACKSPACE;
 			break;
+		case KEY_TAB:
+			keyboard = KEYBOARD_TAB;
+			break;
 		case KEY_ENTER:
 			keyboard = KEYBOARD_ACTION;
 			break;
@@ -354,18 +340,6 @@
 			else
 				keyboard = '\'';
 			break;
-		case KEY_UP: // Up arrow
-			keyboard = KEYBOARD_ARROW_UP;
-			break;
-		case KEY_DOWN: // Down arrow
-			keyboard = KEYBOARD_ARROW_DOWN;
-			break;
-		case KEY_LEFT: // Left arrow
-			keyboard = KEYBOARD_ARROW_LEFT;
-			break;
-		case KEY_RIGHT: // Right arrow
-			keyboard = KEYBOARD_ARROW_RIGHT;
-			break;
 
 #ifdef _EVENT_LOGGING
 		default:
@@ -373,14 +347,44 @@
 			break;
 #endif
 	}
-	if (keyboard != -1) {
-		mLastKeyChar = keyboard;
-		// NotifyKeyboard means: "report character to input widget". KEYBOARD_* codes are special, others are ASCII chars.
-		if (!PageManager::NotifyKeyboard(keyboard))
+	if (ctrlkey)
+	{
+		if (keyboard >= 96)
+			keyboard -= 96;
+		else
+			keyboard = -1;
+	}
+	return keyboard;
+}
+
+bool HardwareKeyboard::IsKeyDown(int key_code)
+{
+	std::set<int>::iterator it = mPressedKeys.find(key_code);
+	return (it != mPressedKeys.end());
+}
+
+int HardwareKeyboard::KeyDown(int key_code)
+{
+#ifdef _EVENT_LOGGING
+	LOGE("HardwareKeyboard::KeyDown %i\n", key_code);
+#endif
+	key_code = TranslateKeyCode(key_code);
+	mPressedKeys.insert(key_code);
+
+	bool ctrlkey = IsKeyDown(KEY_LEFTCTRL) || IsKeyDown(KEY_RIGHTCTRL);
+	bool shiftkey = IsKeyDown(KEY_LEFTSHIFT) || IsKeyDown(KEY_RIGHTSHIFT);
+
+	int ch = KeyCodeToChar(key_code, shiftkey, ctrlkey);
+
+	if (ch != -1) {
+		mLastKeyChar = ch;
+		if (!PageManager::NotifyCharInput(ch))
 			return 1;  // Return 1 to enable key repeat
 	} else {
 		mLastKeyChar = 0;
-		PageManager::NotifyKey(key_code, true);
+		mLastKey = key_code;
+		if (!PageManager::NotifyKey(key_code, true))
+			return 1;  // Return 1 to enable key repeat
 	}
 	return 0;
 }
@@ -405,7 +409,9 @@
 	LOGE("HardwareKeyboard::KeyRepeat: %i\n", mLastKeyChar);
 #endif
 	if (mLastKeyChar)
-		PageManager::NotifyKeyboard(mLastKeyChar);
+		PageManager::NotifyCharInput(mLastKeyChar);
+	else if (mLastKey)
+		PageManager::NotifyKey(mLastKey, true);
 	return 0;
 }
 
diff --git a/gui/input.cpp b/gui/input.cpp
index ca27ea8..68bd163 100644
--- a/gui/input.cpp
+++ b/gui/input.cpp
@@ -469,7 +469,7 @@
 		if (GetSelection(x, y) >= 0) {
 			// When changing focus, we don't scroll or change the cursor location
 			PageManager::SetKeyBoardFocus(0);
-			PageManager::NotifyKeyboard(0);
+			PageManager::NotifyCharInput(0);
 			SetInputFocus(1);
 			DrawCursor = true;
 			mRendered = false;
@@ -561,7 +561,66 @@
 	return 0;
 }
 
-int GUIInput::NotifyKeyboard(int key)
+int GUIInput::NotifyKey(int key, bool down)
+{
+	if (!HasInputFocus || !down)
+		return 1;
+
+	string variableValue;
+	switch (key)
+	{
+		case KEY_LEFT:
+			if (mCursorLocation == 0 && skipChars == 0)
+				return 0; // we're already at the beginning
+			if (mCursorLocation == -1) {
+				DataManager::GetValue(mVariable, variableValue);
+				if (variableValue.size() == 0)
+					return 0;
+				mCursorLocation = variableValue.size() - skipChars - 1;
+			} else if (mCursorLocation == 0) {
+				skipChars--;
+				HandleTextLocation(-1002);
+			} else {
+				mCursorLocation--;
+				HandleTextLocation(-1002);
+			}
+			mRendered = false;
+			return 0;
+
+		case KEY_RIGHT:
+			if (mCursorLocation == -1)
+				return 0; // we're already at the end
+			mCursorLocation++;
+			DataManager::GetValue(mVariable, variableValue);
+			if (variableValue.size() <= mCursorLocation + skipChars)
+				mCursorLocation = -1;
+			HandleTextLocation(-1001);
+			mRendered = false;
+			return 0;
+
+		case KEY_HOME:
+		case KEY_UP:
+			DataManager::GetValue(mVariable, variableValue);
+			if (variableValue.size() == 0)
+				return 0;
+			mCursorLocation = 0;
+			skipChars = 0;
+			mRendered = false;
+			HandleTextLocation(-1002);
+			return 0;
+
+		case KEY_END:
+		case KEY_DOWN:
+			mCursorLocation = -1;
+			mRendered = false;
+			HandleTextLocation(-1003);
+			return 0;
+	}
+
+	return 1;
+}
+
+int GUIInput::NotifyCharInput(int key)
 {
 	string variableValue;
 
@@ -617,48 +676,7 @@
 			isLocalChange = false;
 			mRendered = false;
 			return 0;
-		} else if (key == KEYBOARD_ARROW_LEFT) {
-			if (mCursorLocation == 0 && skipChars == 0)
-				return 0; // we're already at the beginning
-			if (mCursorLocation == -1) {
-				DataManager::GetValue(mVariable, variableValue);
-				if (variableValue.size() == 0)
-					return 0;
-				mCursorLocation = variableValue.size() - skipChars - 1;
-			} else if (mCursorLocation == 0) {
-				skipChars--;
-				HandleTextLocation(-1002);
-			} else {
-				mCursorLocation--;
-				HandleTextLocation(-1002);
-			}
-			mRendered = false;
-			return 0;
-		} else if (key == KEYBOARD_ARROW_RIGHT) {
-			if (mCursorLocation == -1)
-				return 0; // we're already at the end
-			mCursorLocation++;
-			DataManager::GetValue(mVariable, variableValue);
-			if (variableValue.size() <= mCursorLocation + skipChars)
-				mCursorLocation = -1;
-			HandleTextLocation(-1001);
-			mRendered = false;
-			return 0;
-		} else if (key == KEYBOARD_HOME || key == KEYBOARD_ARROW_UP) {
-			DataManager::GetValue(mVariable, variableValue);
-			if (variableValue.size() == 0)
-				return 0;
-			mCursorLocation = 0;
-			skipChars = 0;
-			mRendered = false;
-			HandleTextLocation(-1002);
-			return 0;
-		} else if (key == KEYBOARD_END || key == KEYBOARD_ARROW_DOWN) {
-			mCursorLocation = -1;
-			mRendered = false;
-			HandleTextLocation(-1003);
-			return 0;
-		} else if (key < KEYBOARD_SPECIAL_KEYS && key > 0) {
+		} else if (key >= 32) {
 			// Regular key
 			if (HasAllowed && AllowedList.find((char)key) == string::npos) {
 				return 0;
diff --git a/gui/keyboard.cpp b/gui/keyboard.cpp
index e55fb1b..3b8fdc9 100644
--- a/gui/keyboard.cpp
+++ b/gui/keyboard.cpp
@@ -16,6 +16,7 @@
         along with TWRP.  If not, see <http://www.gnu.org/licenses/>.
 */
 
+#include <linux/input.h>
 #include <stdlib.h>
 #include <string.h>
 #include "../data.hpp"
@@ -31,13 +32,15 @@
 #include "rapidxml.hpp"
 #include "objects.hpp"
 
+bool GUIKeyboard::CtrlActive = false;
+
 GUIKeyboard::GUIKeyboard(xml_node<>* node)
 	: GUIObject(node)
 {
 	int layoutindex, rowindex, keyindex, Xindex, Yindex, keyHeight = 0, keyWidth = 0;
 	currentKey = NULL;
 	highlightRenderCount = 0;
-	hasHighlight = hasCapsHighlight = false;
+	hasHighlight = hasCapsHighlight = hasCtrlHighlight = false;
 	char resource[10], layout[8], row[5], key[6], longpress[7];
 	xml_attribute<>* attr;
 	xml_node<>* child;
@@ -58,6 +61,7 @@
 
 	mHighlightColor = LoadAttrColor(FindNode(node, "highlight"), "color", &hasHighlight);
 	mCapsHighlightColor = LoadAttrColor(FindNode(node, "capshighlight"), "color", &hasCapsHighlight);
+	mCtrlHighlightColor = LoadAttrColor(FindNode(node, "ctrlhighlight"), "color", &hasCtrlHighlight);
 
 	child = FindNode(node, "keymargin");
 	mKeyMarginX = LoadAttrIntScaleX(child, "x", 0);
@@ -228,10 +232,11 @@
 			keychar = *ptr;
 		} else if (*ptr == 'c') {  // This is an ASCII character code: "c:{number}"
 			keychar = atoi(ptr + 2);
+		} else if (*ptr == 'k') {  // This is a Linux keycode from input.h: "k:{number}"
+			keychar = -atoi(ptr + 2);
 		} else if (*ptr == 'l') {  // This is a different layout: "layout{number}"
-			keychar = KEYBOARD_LAYOUT;
 			key.layout = atoi(ptr + 6);
-		} else if (*ptr == 'a') {  // This is an action: "action"
+		} else if (*ptr == 'a') {  // This is an action: "action" (the Enter key)
 			keychar = KEYBOARD_ACTION;
 		} else
 			return -1;
@@ -273,8 +278,8 @@
 
 void GUIKeyboard::DrawKey(Key& key, int keyX, int keyY, int keyW, int keyH)
 {
-	unsigned char keychar = key.key;
-	if (!keychar)
+	int keychar = key.key;
+	if (!keychar && !key.layout)
 		return;
 
 	// key background
@@ -291,7 +296,13 @@
 	string labelText;
 	ImageResource* labelImage = NULL;
 	if (keychar > 32 && keychar < 127) {
+		// TODO: this will eventually need UTF-8 support
 		labelText = (char) keychar;
+		if (CtrlActive) {
+			int ctrlchar = KeyCharToCtrlChar(keychar);
+			if (ctrlchar != keychar)
+				labelText = std::string("^") + (char)(ctrlchar + 64);
+		}
 		gr_color(mFontColor.red, mFontColor.green, mFontColor.blue, mFontColor.alpha);
 	}
 	else {
@@ -343,6 +354,15 @@
 	}
 }
 
+int GUIKeyboard::KeyCharToCtrlChar(int key)
+{
+	// convert upper and lower case to ctrl chars
+	// Ctrl+A to Ctrl+_ (we don't support entering null bytes)
+	if (key >= 65 && key <= 127 && key != 96)
+		return key & 0x1f;
+	return key; // pass on others (already ctrl chars, numbers, etc.) unchanged
+}
+
 int GUIKeyboard::Render(void)
 {
 	if (!isConditionTrue())
@@ -385,11 +405,17 @@
 				DrawKey(key, keyX, keyY, keyW, keyH);
 
 			// Draw highlight for capslock
-			if (hasCapsHighlight && lay.is_caps && CapsLockOn && (int)key.key == KEYBOARD_LAYOUT && key.layout == lay.revert_layout) {
+			if (hasCapsHighlight && lay.is_caps && CapsLockOn && key.layout > 0 && key.layout == lay.revert_layout) {
 				gr_color(mCapsHighlightColor.red, mCapsHighlightColor.green, mCapsHighlightColor.blue, mCapsHighlightColor.alpha);
 				gr_fill(keyX, keyY, keyW, keyH);
 			}
 
+			// Draw highlight for control
+			if (hasCtrlHighlight && key.key == -KEY_LEFTCTRL && CtrlActive) {
+				gr_color(mCtrlHighlightColor.red, mCtrlHighlightColor.green, mCtrlHighlightColor.blue, mCtrlHighlightColor.alpha);
+				gr_fill(keyX, keyY, keyW, keyH);
+			}
+
 			// Highlight current key
 			if (hasHighlight && &key == currentKey && highlightRenderCount != 0) {
 				gr_color(mHighlightColor.red, mHighlightColor.green, mHighlightColor.blue, mHighlightColor.alpha);
@@ -444,7 +470,7 @@
 	int x1 = 0;
 	for (col = 0; col < MAX_KEYBOARD_KEYS; ++col) {
 		Key& key = lay.keys[row][col];
-		if (x1 <= relx && relx < key.end_x && key.key != 0) {
+		if (x1 <= relx && relx < key.end_x && (key.key != 0 || key.layout != 0)) {
 			// This is the key that was pressed!
 			return &key;
 		}
@@ -476,19 +502,20 @@
 		break;
 
 	case TOUCH_RELEASE:
+		// TODO: we might want to notify of key releases here
 		if (x < startX - (mRenderW * 0.5)) {
 			if (highlightRenderCount != 0) {
 				highlightRenderCount = 0;
 				mRendered = false;
 			}
-			PageManager::NotifyKeyboard(KEYBOARD_SWIPE_LEFT);
+			PageManager::NotifyCharInput(KEYBOARD_SWIPE_LEFT);
 			return 0;
 		} else if (x > startX + (mRenderW * 0.5)) {
 			if (highlightRenderCount != 0) {
 				highlightRenderCount = 0;
 				mRendered = false;
 			}
-			PageManager::NotifyKeyboard(KEYBOARD_SWIPE_RIGHT);
+			PageManager::NotifyCharInput(KEYBOARD_SWIPE_RIGHT);
 			return 0;
 		}
 		// fall through
@@ -520,10 +547,11 @@
 			return 0;
 		} else {
 			Key& key = *currentKey;
+			bool repeatKey = false;
 			Layout& lay = layouts[currentLayout - 1];
 			if (state == TOUCH_RELEASE && was_held == 0) {
 				DataManager::Vibrate("tw_keyboard_vibrate");
-				if ((int)key.key == KEYBOARD_LAYOUT) {
+				if (key.layout > 0) {
 					// Switch layouts
 					if (lay.is_caps && key.layout == lay.revert_layout && !CapsLockOn) {
 						CapsLockOn = true; // Set the caps lock
@@ -532,14 +560,30 @@
 						currentLayout = key.layout;
 					}
 					mRendered = false;
-				} else if ((int)key.key == KEYBOARD_ACTION) {
+				} else if (key.key == KEYBOARD_ACTION) {
 					// Action
 					highlightRenderCount = 0;
 					// Send action notification
-					PageManager::NotifyKeyboard(key.key);
-				} else if ((int)key.key < KEYBOARD_SPECIAL_KEYS && (int)key.key > 0) {
+					PageManager::NotifyCharInput(key.key);
+				} else if (key.key == -KEY_LEFTCTRL) {
+					CtrlActive = !CtrlActive; // toggle Control key state
+					mRendered = false; // render Ctrl key highlight
+				} else if (key.key != 0) {
 					// Regular key
-					PageManager::NotifyKeyboard(key.key);
+					if (key.key > 0) {
+						// ASCII code or character
+						int keycode = key.key;
+						if (CtrlActive) {
+							CtrlActive = false;
+							mRendered = false;
+							keycode = KeyCharToCtrlChar(key.key);
+						}
+						PageManager::NotifyCharInput(keycode);
+					} else {
+						// Linux key code
+						PageManager::NotifyKey(-key.key, true);
+						PageManager::NotifyKey(-key.key, false);
+					}
 					if (!CapsLockOn && lay.is_caps) {
 						// caps lock was not set, change layouts
 						currentLayout = lay.revert_layout;
@@ -548,19 +592,32 @@
 				}
 			} else if (state == TOUCH_HOLD) {
 				was_held = 1;
-				if ((int)key.key == KEYBOARD_BACKSPACE) {
-					// Repeat backspace
-					PageManager::NotifyKeyboard(key.key);
-				} else if ((int)key.longpresskey < KEYBOARD_SPECIAL_KEYS && (int)key.longpresskey > 0) {
+				if (key.longpresskey > 0) {
 					// Long Press Key
 					DataManager::Vibrate("tw_keyboard_vibrate");
-					PageManager::NotifyKeyboard(key.longpresskey);
+					PageManager::NotifyCharInput(key.longpresskey);
 				}
+				else
+					repeatKey = true;
 			} else if (state == TOUCH_REPEAT) {
 				was_held = 1;
-				if ((int)key.key == KEYBOARD_BACKSPACE) {
+				repeatKey = true;
+			}
+			if (repeatKey) {
+				if (key.key == KEYBOARD_BACKSPACE) {
 					// Repeat backspace
-					PageManager::NotifyKeyboard(key.key);
+					PageManager::NotifyCharInput(key.key);
+				}
+				switch (key.key)
+				{
+					// Repeat arrows
+					case -KEY_LEFT:
+					case -KEY_RIGHT:
+					case -KEY_UP:
+					case -KEY_DOWN:
+						PageManager::NotifyKey(-key.key, true);
+						PageManager::NotifyKey(-key.key, false);
+						break;
 				}
 			}
 		}
@@ -569,3 +626,9 @@
 
 	return 0;
 }
+
+void GUIKeyboard::SetPageFocus(int inFocus)
+{
+	if (inFocus)
+		CtrlActive = false;
+}
diff --git a/gui/objects.hpp b/gui/objects.hpp
index 4e7ea29..5e09607 100644
--- a/gui/objects.hpp
+++ b/gui/objects.hpp
@@ -103,10 +103,8 @@
 	//  Return 0 on success (and consume key), >0 to pass key to next handler, and <0 on error
 	virtual int NotifyKey(int key __unused, bool down __unused) { return 1; }
 
-	// GetRenderPos - Returns the current position of the object
 	virtual int GetActionPos(int& x, int& y, int& w, int& h) { x = mActionX; y = mActionY; w = mActionW; h = mActionH; return 0; }
 
-	// SetRenderPos - Update the position of the object
 	//  Return 0 on success, <0 on error
 	virtual int SetActionPos(int x, int y, int w = 0, int h = 0);
 
@@ -166,9 +164,9 @@
 	virtual ~InputObject() {}
 
 public:
-	// NotifyKeyboard - Notify of keyboard input
+	// NotifyCharInput - Notify of character input (usually from the onscreen or hardware keyboard)
 	//  Return 0 on success (and consume key), >0 to pass key to next handler, and <0 on error
-	virtual int NotifyKeyboard(int key __unused) { return 1; }
+	virtual int NotifyCharInput(int ch __unused) { return 1; }
 
 	virtual int SetInputFocus(int focus) { HasInputFocus = focus; return 1; }
 
@@ -856,18 +854,13 @@
 	int sUpdate;
 };
 
-#define KEYBOARD_ACTION 253
-#define KEYBOARD_LAYOUT 254
-#define KEYBOARD_SWIPE_LEFT 252
-#define KEYBOARD_SWIPE_RIGHT 251
-#define KEYBOARD_ARROW_LEFT 250
-#define KEYBOARD_ARROW_RIGHT 249
-#define KEYBOARD_HOME 248
-#define KEYBOARD_END 247
-#define KEYBOARD_ARROW_UP 246
-#define KEYBOARD_ARROW_DOWN 245
-#define KEYBOARD_SPECIAL_KEYS 245
-#define KEYBOARD_BACKSPACE 8
+// these are ASCII codes reported via NotifyCharInput
+// other special keys (arrows etc.) are reported via NotifyKey
+#define KEYBOARD_ACTION 13	// CR
+#define KEYBOARD_BACKSPACE 8	// Backspace
+#define KEYBOARD_TAB 9		// Tab
+#define KEYBOARD_SWIPE_LEFT 21	// Ctrl+U to delete line, same as in readline (used by shell etc.)
+#define KEYBOARD_SWIPE_RIGHT 11	// Ctrl+K, same as in readline
 
 class GUIKeyboard : public GUIObject, public RenderObject, public ActionObject
 {
@@ -880,18 +873,20 @@
 	virtual int Update(void);
 	virtual int NotifyTouch(TOUCH_STATE state, int x, int y);
 	virtual int SetRenderPos(int x, int y, int w = 0, int h = 0);
+	virtual void SetPageFocus(int inFocus);
 
 protected:
 	struct Key
 	{
-		unsigned char key; // ASCII code or one of the special KEYBOARD_* codes above
-		unsigned char longpresskey;
+		int key; // positive: ASCII/Unicode code; negative: Linux key code (KEY_*)
+		int longpresskey;
 		int end_x;
 		int layout;
 	};
 	int ParseKey(const char* keyinfo, Key& key, int& Xindex, int keyWidth, bool longpress);
 	void LoadKeyLabels(xml_node<>* parent, int layout);
 	void DrawKey(Key& key, int keyX, int keyY, int keyW, int keyH);
+	int KeyCharToCtrlChar(int key);
 
 	enum {
 		MAX_KEYBOARD_LAYOUTS = 5,
@@ -901,7 +896,7 @@
 	struct Layout
 	{
 		ImageResource* keyboardImg;
-		struct Key keys[MAX_KEYBOARD_ROWS][MAX_KEYBOARD_KEYS];
+		Key keys[MAX_KEYBOARD_ROWS][MAX_KEYBOARD_KEYS];
 		int row_end_y[MAX_KEYBOARD_ROWS];
 		bool is_caps;
 		int revert_layout;
@@ -910,7 +905,7 @@
 
 	struct KeyLabel
 	{
-		unsigned char key; // same as in struct Key
+		int key; // same as in struct Key
 		int layout_from; // 1-based; 0 for labels that apply to all layouts
 		int layout_to; // same as Key.layout
 		string text; // key label text
@@ -925,11 +920,13 @@
 	std::string mVariable;
 	int currentLayout;
 	bool CapsLockOn;
+	static bool CtrlActive; // all keyboards share a common Control key state so that the Control key can be on a separate keyboard instance
 	int highlightRenderCount;
 	Key* currentKey;
-	bool hasHighlight, hasCapsHighlight;
+	bool hasHighlight, hasCapsHighlight, hasCtrlHighlight;
 	COLOR mHighlightColor;
 	COLOR mCapsHighlightColor;
+	COLOR mCtrlHighlightColor;
 	COLOR mFontColor; // for centered key labels
 	COLOR mFontColorSmall; // for centered key labels
 	FontResource* mFont; // for main key labels
@@ -966,7 +963,8 @@
 	//  Return 0 on success, >0 to ignore remainder of touch, and <0 on error
 	virtual int NotifyTouch(TOUCH_STATE state, int x, int y);
 
-	virtual int NotifyKeyboard(int key);
+	virtual int NotifyKey(int key, bool down);
+	virtual int NotifyCharInput(int ch);
 
 protected:
 	virtual int GetSelection(int x, int y);
@@ -1024,7 +1022,9 @@
 	// called by multi-key actions to suppress key-release notifications
 	void ConsumeKeyRelease(int key);
 
+	bool IsKeyDown(int key_code);
 private:
+	int mLastKey;
 	int mLastKeyChar;
 	std::set<int> mPressedKeys;
 };
diff --git a/gui/pages.cpp b/gui/pages.cpp
index 4a65c69..c097c39 100644
--- a/gui/pages.cpp
+++ b/gui/pages.cpp
@@ -584,15 +584,13 @@
 {
 	std::vector<ActionObject*>::reverse_iterator iter;
 
-	// Don't try to handle a lack of handlers
-	if (mActions.size() == 0)
-		return 1;
-
 	int ret = 1;
 	// We work backwards, from top-most element to bottom-most element
 	for (iter = mActions.rbegin(); iter != mActions.rend(); iter++)
 	{
 		ret = (*iter)->NotifyKey(key, down);
+		if (ret == 0)
+			return 0;
 		if (ret < 0) {
 			LOGERR("An action handler has returned an error\n");
 			ret = 1;
@@ -601,22 +599,18 @@
 	return ret;
 }
 
-int Page::NotifyKeyboard(int key)
+int Page::NotifyCharInput(int ch)
 {
 	std::vector<InputObject*>::reverse_iterator iter;
 
-	// Don't try to handle a lack of handlers
-	if (mInputs.size() == 0)
-		return 1;
-
 	// We work backwards, from top-most element to bottom-most element
 	for (iter = mInputs.rbegin(); iter != mInputs.rend(); iter++)
 	{
-		int ret = (*iter)->NotifyKeyboard(key);
+		int ret = (*iter)->NotifyCharInput(ch);
 		if (ret == 0)
 			return 0;
 		else if (ret < 0)
-			LOGERR("A keyboard handler has returned an error");
+			LOGERR("A char input handler has returned an error");
 	}
 	return 1;
 }
@@ -625,10 +619,6 @@
 {
 	std::vector<InputObject*>::reverse_iterator iter;
 
-	// Don't try to handle a lack of handlers
-	if (mInputs.size() == 0)
-		return 1;
-
 	// We work backwards, from top-most element to bottom-most element
 	for (iter = mInputs.rbegin(); iter != mInputs.rend(); iter++)
 	{
@@ -1155,12 +1145,12 @@
 	return (mCurrentPage ? mCurrentPage->NotifyKey(key, down) : -1);
 }
 
-int PageSet::NotifyKeyboard(int key)
+int PageSet::NotifyCharInput(int ch)
 {
 	if (!mOverlays.empty())
-		return mOverlays.back()->NotifyKeyboard(key);
+		return mOverlays.back()->NotifyCharInput(ch);
 
-	return (mCurrentPage ? mCurrentPage->NotifyKeyboard(key) : -1);
+	return (mCurrentPage ? mCurrentPage->NotifyCharInput(ch) : -1);
 }
 
 int PageSet::SetKeyBoardFocus(int inFocus)
@@ -1654,9 +1644,9 @@
 	return (mCurrentSet ? mCurrentSet->NotifyKey(key, down) : -1);
 }
 
-int PageManager::NotifyKeyboard(int key)
+int PageManager::NotifyCharInput(int ch)
 {
-	return (mCurrentSet ? mCurrentSet->NotifyKeyboard(key) : -1);
+	return (mCurrentSet ? mCurrentSet->NotifyCharInput(ch) : -1);
 }
 
 int PageManager::SetKeyBoardFocus(int inFocus)
diff --git a/gui/pages.hpp b/gui/pages.hpp
index e7ad55e..87e1fb5 100644
--- a/gui/pages.hpp
+++ b/gui/pages.hpp
@@ -64,7 +64,7 @@
 	virtual int Update(void);
 	virtual int NotifyTouch(TOUCH_STATE state, int x, int y);
 	virtual int NotifyKey(int key, bool down);
-	virtual int NotifyKeyboard(int key);
+	virtual int NotifyCharInput(int ch);
 	virtual int SetKeyBoardFocus(int inFocus);
 	virtual int NotifyVarChange(std::string varName, std::string value);
 	virtual void SetPageFocus(int inFocus);
@@ -108,7 +108,7 @@
 	int Update(void);
 	int NotifyTouch(TOUCH_STATE state, int x, int y);
 	int NotifyKey(int key, bool down);
-	int NotifyKeyboard(int key);
+	int NotifyCharInput(int ch);
 	int SetKeyBoardFocus(int inFocus);
 	int NotifyVarChange(std::string varName, std::string value);
 
@@ -155,7 +155,7 @@
 	static int Update(void);
 	static int NotifyTouch(TOUCH_STATE state, int x, int y);
 	static int NotifyKey(int key, bool down);
-	static int NotifyKeyboard(int key);
+	static int NotifyCharInput(int ch);
 	static int SetKeyBoardFocus(int inFocus);
 	static int NotifyVarChange(std::string varName, std::string value);