Add support for actions triggered by key combination

Change-Id: I9dfa7de40229f00412d63fc9c1eb3a809a6eb2e6
Signed-off-by: Vojtech Bocek <vbocek@gmail.com>
diff --git a/gui/action.cpp b/gui/action.cpp
index 9d7e482..94acf18 100644
--- a/gui/action.cpp
+++ b/gui/action.cpp
@@ -73,8 +73,6 @@
 	xml_node<>* actions;
 	xml_attribute<>* attr;
 
-	mKey = 0;
-
 	if (!node)  return;
 
 	// First, get the action
@@ -105,9 +103,12 @@
 		attr = child->first_attribute("key");
 		if (attr)
 		{
-			std::string key = attr->value();
-	
-			mKey = getKeyByName(key);
+			std::vector<std::string> keys = TWFunc::Split_String(attr->value(), "+");
+			for(size_t i = 0; i < keys.size(); ++i)
+			{
+				const int key = getKeyByName(keys[i]);
+				mKeys[key] = false;
+			}
 		}
 		else
 		{
@@ -135,12 +136,41 @@
 	return 0;
 }
 
-int GUIAction::NotifyKey(int key)
+int GUIAction::NotifyKey(int key, bool down)
 {
-	if (!mKey || key != mKey)
-		return 1;
+	if (mKeys.empty())
+		return 0;
 
-	doActions();
+	std::map<int, bool>::iterator itr = mKeys.find(key);
+	if(itr == mKeys.end())
+		return 0;
+
+	bool prevState = itr->second;
+	itr->second = down;
+
+	// If there is only one key for this action, wait for key up so it
+	// doesn't trigger with multi-key actions.
+	// Else, check if all buttons are pressed, then consume their release events
+	// so they don't trigger one-button actions and reset mKeys pressed status
+	if(mKeys.size() == 1) {
+		if(!down && prevState)
+			doActions();
+	} else if(down) {
+		for(itr = mKeys.begin(); itr != mKeys.end(); ++itr) {
+			if(!itr->second)
+				return 0;
+		}
+
+		// Passed, all req buttons are pressed, reset them and consume release events
+		HardwareKeyboard *kb = PageManager::GetHardwareKeyboard();
+		for(itr = mKeys.begin(); itr != mKeys.end(); ++itr) {
+			kb->ConsumeKeyRelease(itr->first);
+			itr->second = false;
+		}
+
+		doActions();
+	}
+
 	return 0;
 }
 
@@ -148,7 +178,7 @@
 {
 	GUIObject::NotifyVarChange(varName, value);
 
-	if (varName.empty() && !isConditionValid() && !mKey && !mActionW)
+	if (varName.empty() && !isConditionValid() && mKeys.empty() && !mActionW)
 		doActions();
 	else if((varName.empty() || IsConditionVariable(varName)) && isConditionValid() && isConditionTrue())
 		doActions();
@@ -380,7 +410,9 @@
 
 	if (function == "key")
 	{
-		PageManager::NotifyKey(getKeyByName(arg));
+		const int key = getKeyByName(arg);
+		PageManager::NotifyKey(key, true);
+		PageManager::NotifyKey(key, false);
 		return 0;
 	}
 
diff --git a/gui/gui.cpp b/gui/gui.cpp
index 0164ec3..c0bd008 100644
--- a/gui/gui.cpp
+++ b/gui/gui.cpp
@@ -187,8 +187,8 @@
 	static int x = 0, y = 0;
 	static int lshift = 0, rshift = 0;
 	static struct timeval touchStart;
-	HardwareKeyboard kb;
 	string seconds;
+	HardwareKeyboard *kb = PageManager::GetHardwareKeyboard();
 	MouseCursor *cursor = PageManager::GetMouseCursor();
 
 #ifndef TW_NO_SCREEN_TIMEOUT
@@ -249,7 +249,7 @@
 #endif
 				gettimeofday(&touchStart, NULL);
 				key_repeat = 2;
-				kb.KeyRepeat();
+				kb->KeyRepeat();
 #ifndef TW_NO_SCREEN_TIMEOUT
 				blankTimer.resetTimerAndUnblank();
 #endif
@@ -261,7 +261,7 @@
 				LOGERR("KEY_REPEAT: %d,%d\n", x, y);
 #endif
 				gettimeofday(&touchStart, NULL);
-				kb.KeyRepeat();
+				kb->KeyRepeat();
 #ifndef TW_NO_SCREEN_TIMEOUT
 				blankTimer.resetTimerAndUnblank();
 #endif
@@ -356,6 +356,9 @@
 					{
 						cursor->GetPos(x, y);
 
+#ifdef _EVENT_LOGGING
+						LOGERR("TOUCH_RELEASE: %d,%d\n", x, y);
+#endif
 						PageManager::NotifyTouch(TOUCH_RELEASE, x, y);
 
 						touch_and_hold = 0;
@@ -371,15 +374,13 @@
 			else if(ev.code == BTN_SIDE)
 			{
 				if(ev.value == 1)
-					kb.KeyDown(KEY_BACK);
+					kb->KeyDown(KEY_BACK);
 				else
-					kb.KeyUp(KEY_BACK);
-			}
-			else if (ev.value != 0)
-			{
+					kb->KeyUp(KEY_BACK);
+			} else if (ev.value != 0) {
 				// This is a key press
-				if (kb.KeyDown(ev.code))
-				{
+				if (kb->KeyDown(ev.code)) {
+					// Key repeat is enabled for this key
 					key_repeat = 1;
 					touch_and_hold = 0;
 					touch_repeat = 0;
@@ -388,9 +389,7 @@
 #ifndef TW_NO_SCREEN_TIMEOUT
 					blankTimer.resetTimerAndUnblank();
 #endif
-				}
-				else
-				{
+				} else {
 					key_repeat = 0;
 					touch_and_hold = 0;
 					touch_repeat = 0;
@@ -399,11 +398,9 @@
 					blankTimer.resetTimerAndUnblank();
 #endif
 				}
-			}
-			else
-			{
+			} else {
 				// This is a key release
-				kb.KeyUp(ev.code);
+				kb->KeyUp(ev.code);
 				key_repeat = 0;
 				touch_and_hold = 0;
 				touch_repeat = 0;
diff --git a/gui/hardwarekeyboard.cpp b/gui/hardwarekeyboard.cpp
index a5a9987..f219493 100644
--- a/gui/hardwarekeyboard.cpp
+++ b/gui/hardwarekeyboard.cpp
@@ -38,15 +38,22 @@
 
 int HardwareKeyboard::KeyDown(int key_code)
 {
+	mPressedKeys.insert(key_code);
+	PageManager::NotifyKey(key_code, true);
 #ifdef _EVENT_LOGGING
 	LOGERR("HardwareKeyboard::KeyDown %i\n", key_code);
 #endif
-	PageManager::NotifyKey(key_code);
 	return 0; // 0 = no key repeat anything else turns on key repeat
 }
 
 int HardwareKeyboard::KeyUp(int key_code)
 {
+	std::set<int>::iterator itr = mPressedKeys.find(key_code);
+	if(itr != mPressedKeys.end())
+	{
+		mPressedKeys.erase(itr);
+		PageManager::NotifyKey(key_code, false);
+	}
 #ifdef _EVENT_LOGGING
 	LOGERR("HardwareKeyboard::KeyUp %i\n", key_code);
 #endif
@@ -55,8 +62,22 @@
 
 int HardwareKeyboard::KeyRepeat(void)
 {
+	/*
+	 * Uncomment when key repeats are sent somewhere.
+	 * std::set<int>::iterator itr = mPressedKeys.find(key_code);
+	 * if(itr != mPressedKeys.end())
+	 * {
+	 *	Send repeats somewhere, don't remove itr from mPressedKeys
+	 * }
+	 */
+
 #ifdef _EVENT_LOGGING
 	LOGERR("HardwareKeyboard::KeyRepeat\n");
 #endif
 	return 0;
 }
+
+void HardwareKeyboard::ConsumeKeyRelease(int key)
+{
+	mPressedKeys.erase(key);
+}
diff --git a/gui/objects.hpp b/gui/objects.hpp
index 4942cd7..0241715 100644
--- a/gui/objects.hpp
+++ b/gui/objects.hpp
@@ -25,6 +25,7 @@
 #include <vector>
 #include <string>
 #include <map>
+#include <set>
 #include <time.h>
 
 extern "C" {
@@ -101,7 +102,7 @@
 
 	// NotifyKey - Notify of a key press
 	//  Return 0 on success (and consume key), >0 to pass key to next handler, and <0 on error
-	virtual int NotifyKey(int key) { return 1; }
+	virtual int NotifyKey(int key, bool down) { 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; }
@@ -269,7 +270,7 @@
 
 public:
 	virtual int NotifyTouch(TOUCH_STATE state, int x, int y);
-	virtual int NotifyKey(int key);
+	virtual int NotifyKey(int key, bool down);
 	virtual int NotifyVarChange(const std::string& varName, const std::string& value);
 	virtual int doActions();
 
@@ -282,7 +283,7 @@
 	};
 
 	std::vector<Action> mActions;
-	int mKey;
+	std::map<int, bool> mKeys;
 
 protected:
 	int getKeyByName(std::string key);
@@ -927,6 +928,11 @@
 	virtual int KeyDown(int key_code);
 	virtual int KeyUp(int key_code);
 	virtual int KeyRepeat(void);
+
+	void ConsumeKeyRelease(int key);
+
+private:
+	std::set<int> mPressedKeys;
 };
 
 class GUISliderValue: public GUIObject, public RenderObject, public ActionObject
diff --git a/gui/pages.cpp b/gui/pages.cpp
index 2953edd..398224e 100644
--- a/gui/pages.cpp
+++ b/gui/pages.cpp
@@ -55,6 +55,7 @@
 PageSet* PageManager::mCurrentSet;
 PageSet* PageManager::mBaseSet = NULL;
 MouseCursor *PageManager::mMouseCursor = NULL;
+HardwareKeyboard *PageManager::mHardwareKeyboard = NULL;
 
 // Helper routine to convert a string to a color declaration
 int ConvertStrToColor(std::string str, COLOR* color)
@@ -447,7 +448,7 @@
 	return ret;
 }
 
-int Page::NotifyKey(int key)
+int Page::NotifyKey(int key, bool down)
 {
 	std::vector<ActionObject*>::reverse_iterator iter;
 
@@ -455,16 +456,17 @@
 	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++)
 	{
-		int ret = (*iter)->NotifyKey(key);
-		if (ret == 0)
-			return 0;
-		else if (ret < 0)
-			LOGERR("An action handler has returned an error");
+		ret = (*iter)->NotifyKey(key, down);
+		if (ret < 0) {
+			LOGERR("An action handler has returned an error\n");
+			ret = 1;
+		}
 	}
-	return 1;
+	return ret;
 }
 
 int Page::NotifyKeyboard(int key)
@@ -719,12 +721,12 @@
 	return (mCurrentPage ? mCurrentPage->NotifyTouch(state, x, y) : -1);
 }
 
-int PageSet::NotifyKey(int key)
+int PageSet::NotifyKey(int key, bool down)
 {
 	if (mOverlayPage)
-		return (mOverlayPage->NotifyKey(key));
+		return (mOverlayPage->NotifyKey(key, down));
 
-	return (mCurrentPage ? mCurrentPage->NotifyKey(key) : -1);
+	return (mCurrentPage ? mCurrentPage->NotifyKey(key, down) : -1);
 }
 
 int PageSet::NotifyKeyboard(int key)
@@ -964,6 +966,13 @@
 	return res;
 }
 
+HardwareKeyboard *PageManager::GetHardwareKeyboard()
+{
+	if(!mHardwareKeyboard)
+		mHardwareKeyboard = new HardwareKeyboard();
+	return mHardwareKeyboard;
+}
+
 MouseCursor *PageManager::GetMouseCursor()
 {
 	if(!mMouseCursor)
@@ -1002,9 +1011,9 @@
 	return (mCurrentSet ? mCurrentSet->NotifyTouch(state, x, y) : -1);
 }
 
-int PageManager::NotifyKey(int key)
+int PageManager::NotifyKey(int key, bool down)
 {
-	return (mCurrentSet ? mCurrentSet->NotifyKey(key) : -1);
+	return (mCurrentSet ? mCurrentSet->NotifyKey(key, down) : -1);
 }
 
 int PageManager::NotifyKeyboard(int key)
diff --git a/gui/pages.hpp b/gui/pages.hpp
index 23ceee9..a9cc0c1 100644
--- a/gui/pages.hpp
+++ b/gui/pages.hpp
@@ -30,6 +30,7 @@
 class InputObject;
 class MouseCursor;
 class GUIObject;
+class HardwareKeyboard;
 
 class Page
 {
@@ -43,7 +44,7 @@
 	virtual int Render(void);
 	virtual int Update(void);
 	virtual int NotifyTouch(TOUCH_STATE state, int x, int y);
-	virtual int NotifyKey(int key);
+	virtual int NotifyKey(int key, bool down);
 	virtual int NotifyKeyboard(int key);
 	virtual int SetKeyBoardFocus(int inFocus);
 	virtual int NotifyVarChange(std::string varName, std::string value);
@@ -84,7 +85,7 @@
 	int Render(void);
 	int Update(void);
 	int NotifyTouch(TOUCH_STATE state, int x, int y);
-	int NotifyKey(int key);
+	int NotifyKey(int key, bool down);
 	int NotifyKeyboard(int key);
 	int SetKeyBoardFocus(int inFocus);
 	int NotifyVarChange(std::string varName, std::string value);
@@ -127,7 +128,7 @@
 	static int Render(void);
 	static int Update(void);
 	static int NotifyTouch(TOUCH_STATE state, int x, int y);
-	static int NotifyKey(int key);
+	static int NotifyKey(int key, bool down);
 	static int NotifyKeyboard(int key);
 	static int SetKeyBoardFocus(int inFocus);
 	static int NotifyVarChange(std::string varName, std::string value);
@@ -135,6 +136,8 @@
 	static MouseCursor *GetMouseCursor();
 	static void LoadCursorData(xml_node<>* node);
 
+	static HardwareKeyboard *GetHardwareKeyboard();
+
 protected:
 	static PageSet* FindPackage(std::string name);
 
@@ -143,6 +146,7 @@
 	static PageSet* mCurrentSet;
 	static PageSet* mBaseSet;
 	static MouseCursor *mMouseCursor;
+	static HardwareKeyboard *mHardwareKeyboard;
 };
 
 #endif  // _PAGES_HEADER_HPP
diff --git a/minuitwrp/events.c b/minuitwrp/events.c
index 93c41f2..eb14907 100644
--- a/minuitwrp/events.c
+++ b/minuitwrp/events.c
@@ -384,6 +384,7 @@
 {
     static int downX = -1, downY = -1;
     static int discard = 0;
+    static int last_virt_key = 0;
     static int lastWasSynReport = 0;
     static int touchReleaseOnNextSynReport = 0;
 	static int use_tracking_id_negative_as_touch_release = 0; // On some devices, type: 3  code: 39  value: -1, aka EV_ABS ABS_MT_TRACKING_ID -1 indicates a true touch release
@@ -593,7 +594,11 @@
         if (discard)
         {
             discard = 0;
-            return 1;
+
+            // Send the keyUp event
+            ev->type = EV_KEY;
+            ev->code = last_virt_key;
+            ev->value = 0;
         }
         return 0;
     }
@@ -651,6 +656,8 @@
                 ev->code = e->vks[i].scancode;
                 ev->value = 1;
 
+                last_virt_key = e->vks[i].scancode;
+
                 vibrate(VIBRATOR_TIME_MS);
 
                 // Mark that all further movement until lift is discard, 
diff --git a/twrp-functions.cpp b/twrp-functions.cpp
index 4551e84..fd974d2 100644
--- a/twrp-functions.cpp
+++ b/twrp-functions.cpp
@@ -1142,4 +1142,24 @@
 #endif
 }
 
+std::vector<std::string> TWFunc::Split_String(const std::string& str, const std::string& delimiter, bool removeEmpty)
+{
+	std::vector<std::string> res;
+	size_t idx = 0, idx_last = 0;
+
+	while(idx < str.size())
+	{
+		idx = str.find_first_of(delimiter, idx_last);
+		if(idx == std::string::npos)
+			idx = str.size();
+
+		if(idx-idx_last != 0 || !removeEmpty)
+			res.push_back(str.substr(idx_last, idx-idx_last));
+
+		idx_last = idx + delimiter.size();
+	}
+
+	return res;
+}
+
 #endif // ndef BUILD_TWRPTAR_MAIN
diff --git a/twrp-functions.hpp b/twrp-functions.hpp
index 64a45f5..ff11763 100644
--- a/twrp-functions.hpp
+++ b/twrp-functions.hpp
@@ -80,6 +80,7 @@
 	static string Get_Current_Date(void);                               // Returns the current date in ccyy-m-dd--hh-nn-ss format
 	static void Auto_Generate_Backup_Name();                            // Populates TW_BACKUP_NAME with a backup name based on current date and ro.build.display.id from /system/build.prop
 	static void Fixup_Time_On_Boot(); // Fixes time on devices which need it
+	static std::vector<std::string> Split_String(const std::string& str, const std::string& delimiter, bool removeEmpty = true); // Splits string by delimiter
 
 private:
 	static void Copy_Log(string Source, string Destination);