In the case of password entry, the two differ. Password entry was broken by the earlier commit entitled "Improve input box text handling" because it started using the displayValue as the actual value, even in cases where they differ because of a mask value. Change-Id: Iaf5a67e1d928f34595962a1f1b80eebb64e8b493
575 lines
14 KiB
C++
575 lines
14 KiB
C++
/*
|
|
Copyright 2012 to 2016 bigbiff/Dees_Troy TeamWin
|
|
This file is part of TWRP/TeamWin Recovery Project.
|
|
|
|
TWRP is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
TWRP is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with TWRP. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
// input.cpp - GUIInput object
|
|
|
|
#include <linux/input.h>
|
|
#include <pthread.h>
|
|
#include <stdarg.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <fcntl.h>
|
|
#include <sys/reboot.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/time.h>
|
|
#include <sys/mman.h>
|
|
#include <sys/types.h>
|
|
#include <sys/ioctl.h>
|
|
#include <time.h>
|
|
#include <unistd.h>
|
|
#include <stdlib.h>
|
|
|
|
#include <string>
|
|
|
|
extern "C" {
|
|
#include "../twcommon.h"
|
|
}
|
|
#include "../minuitwrp/minui.h"
|
|
|
|
#include "rapidxml.hpp"
|
|
#include "objects.hpp"
|
|
#include "../data.hpp"
|
|
|
|
#define TW_INPUT_NO_UPDATE -1000 // Magic value for HandleTextLocation when no change in scrolling has occurred
|
|
|
|
GUIInput::GUIInput(xml_node<>* node)
|
|
: GUIObject(node)
|
|
{
|
|
xml_attribute<>* attr;
|
|
xml_node<>* child;
|
|
|
|
mInputText = NULL;
|
|
mAction = NULL;
|
|
mBackground = NULL;
|
|
mCursor = NULL;
|
|
mFont = NULL;
|
|
mRendered = false;
|
|
HasMask = false;
|
|
DrawCursor = false;
|
|
isLocalChange = true;
|
|
HasAllowed = false;
|
|
HasDisabled = false;
|
|
cursorX = textWidth = scrollingX = mFontHeight = mFontY = lastX = 0;
|
|
mBackgroundX = mBackgroundY = mBackgroundW = mBackgroundH = MinLen = MaxLen = 0;
|
|
mCursorLocation = -1; // -1 is always the end of the string
|
|
CursorWidth = 3;
|
|
ConvertStrToColor("black", &mBackgroundColor);
|
|
ConvertStrToColor("white", &mCursorColor);
|
|
mValue = "";
|
|
displayValue = "";
|
|
|
|
if (!node)
|
|
return;
|
|
|
|
// Load text directly from the node
|
|
mInputText = new GUIText(node);
|
|
// Load action directly from the node
|
|
mAction = new GUIAction(node);
|
|
|
|
if (mInputText->Render() < 0)
|
|
{
|
|
delete mInputText;
|
|
mInputText = NULL;
|
|
}
|
|
|
|
// Load the background
|
|
child = FindNode(node, "background");
|
|
if (child)
|
|
{
|
|
mBackground = LoadAttrImage(child, "resource");
|
|
mBackgroundColor = LoadAttrColor(child, "color", mBackgroundColor);
|
|
}
|
|
if (mBackground && mBackground->GetResource())
|
|
{
|
|
mBackgroundW = mBackground->GetWidth();
|
|
mBackgroundH = mBackground->GetHeight();
|
|
}
|
|
|
|
// Load the cursor color
|
|
child = FindNode(node, "cursor");
|
|
if (child)
|
|
{
|
|
mCursor = LoadAttrImage(child, "resource");
|
|
mCursorColor = LoadAttrColor(child, "color", mCursorColor);
|
|
attr = child->first_attribute("hasfocus");
|
|
if (attr)
|
|
{
|
|
std::string focus = attr->value();
|
|
SetInputFocus(atoi(focus.c_str()));
|
|
}
|
|
CursorWidth = LoadAttrIntScaleX(child, "width", CursorWidth);
|
|
}
|
|
DrawCursor = HasInputFocus;
|
|
|
|
// Load the font
|
|
child = FindNode(node, "font");
|
|
if (child)
|
|
{
|
|
mFont = LoadAttrFont(child, "resource");
|
|
mFontHeight = mFont->GetHeight();
|
|
}
|
|
|
|
child = FindNode(node, "data");
|
|
if (child)
|
|
{
|
|
attr = child->first_attribute("name");
|
|
if (attr)
|
|
mVariable = attr->value();
|
|
attr = child->first_attribute("default");
|
|
if (attr)
|
|
DataManager::SetValue(mVariable, attr->value());
|
|
mMask = LoadAttrString(child, "mask");
|
|
HasMask = !mMask.empty();
|
|
}
|
|
|
|
// Load input restrictions
|
|
child = FindNode(node, "restrict");
|
|
if (child)
|
|
{
|
|
MinLen = LoadAttrInt(child, "minlen", MinLen);
|
|
MaxLen = LoadAttrInt(child, "maxlen", MaxLen);
|
|
AllowedList = LoadAttrString(child, "allow");
|
|
HasAllowed = !AllowedList.empty();
|
|
DisabledList = LoadAttrString(child, "disable");
|
|
HasDisabled = !DisabledList.empty();
|
|
}
|
|
|
|
// Load the placement
|
|
LoadPlacement(FindNode(node, "placement"), &mRenderX, &mRenderY, &mRenderW, &mRenderH);
|
|
SetActionPos(mRenderX, mRenderY, mRenderW, mRenderH);
|
|
|
|
if (mInputText && mFontHeight && mFontHeight < (unsigned)mRenderH) {
|
|
mFontY = ((mRenderH - mFontHeight) / 2) + mRenderY;
|
|
mInputText->SetRenderPos(mRenderX, mFontY);
|
|
} else
|
|
mFontY = mRenderY;
|
|
|
|
if (mInputText)
|
|
mInputText->SetMaxWidth(0);
|
|
}
|
|
|
|
GUIInput::~GUIInput()
|
|
{
|
|
delete mInputText;
|
|
delete mAction;
|
|
}
|
|
|
|
void GUIInput::HandleTextLocation(int x) {
|
|
mRendered = false;
|
|
if (textWidth <= mRenderW) {
|
|
if (x != TW_INPUT_NO_UPDATE)
|
|
lastX = x;
|
|
scrollingX = 0;
|
|
return;
|
|
}
|
|
if (scrollingX + textWidth < mRenderW) {
|
|
scrollingX = mRenderW - textWidth;
|
|
}
|
|
|
|
if (x == TW_INPUT_NO_UPDATE)
|
|
return;
|
|
|
|
scrollingX += x - lastX;
|
|
if (scrollingX > 0)
|
|
scrollingX = 0;
|
|
else if (scrollingX + textWidth < mRenderW)
|
|
scrollingX = mRenderW - textWidth;
|
|
lastX = x;
|
|
}
|
|
|
|
void GUIInput::UpdateDisplayText() {
|
|
void* fontResource = NULL;
|
|
|
|
if (mFont) {
|
|
fontResource = mFont->GetResource();
|
|
} else {
|
|
textWidth = 0;
|
|
return;
|
|
}
|
|
|
|
DataManager::GetValue(mVariable, mValue);
|
|
if (HasMask) {
|
|
int index, string_size = mValue.size();
|
|
string maskedValue;
|
|
for (index=0; index<string_size; index++)
|
|
maskedValue += mMask;
|
|
displayValue = maskedValue;
|
|
} else {
|
|
displayValue = mValue;
|
|
}
|
|
|
|
textWidth = gr_ttf_measureEx(displayValue.c_str(), fontResource);
|
|
}
|
|
|
|
void GUIInput::HandleCursorByTouch(int x) {
|
|
// Uses x to find mCursorLocation and cursorX
|
|
if (displayValue.size() == 0) {
|
|
mCursorLocation = -1;
|
|
cursorX = mRenderX;
|
|
return;
|
|
}
|
|
|
|
void* fontResource = NULL;
|
|
if (mFont) {
|
|
fontResource = mFont->GetResource();
|
|
} else {
|
|
return;
|
|
}
|
|
|
|
string cursorString;
|
|
unsigned index = 0, displaySize = displayValue.size();
|
|
int prevX = mRenderX + scrollingX;
|
|
|
|
for(index = 0; index <= displaySize; index++) {
|
|
cursorString = displayValue.substr(0, index);
|
|
cursorX = gr_ttf_measureEx(cursorString.c_str(), fontResource) + mRenderX + scrollingX;
|
|
if (cursorX > x) {
|
|
if (index > 0 && x <= cursorX - ((x - prevX) / 2) && prevX >= mRenderX) {
|
|
// This helps make sure that we can place the cursor before the very first char if the first char is
|
|
// is fully visible while also still letting us place the cursor after the last char if fully visible
|
|
mCursorLocation = index - 1;
|
|
cursorX = prevX;
|
|
return;
|
|
}
|
|
mCursorLocation = index;
|
|
if (cursorX > mRenderX + mRenderW) {
|
|
cursorX = prevX; // This makes sure that the cursor doesn't get placed after the end of the input box
|
|
mCursorLocation--;
|
|
return;
|
|
}
|
|
if (cursorX >= mRenderX) {
|
|
return; // This makes sure that the cursor doesn't get placed before the beginning of the input box
|
|
}
|
|
}
|
|
prevX = cursorX;
|
|
}
|
|
mCursorLocation = -1; // x is at or past the end of the string
|
|
}
|
|
|
|
void GUIInput::HandleCursorByText() {
|
|
// Uses mCursorLocation to find cursorX
|
|
if (!DrawCursor)
|
|
return;
|
|
|
|
void* fontResource = NULL;
|
|
if (mFont) {
|
|
fontResource = mFont->GetResource();
|
|
} else {
|
|
return;
|
|
}
|
|
|
|
int cursorTextWidth = textWidth; // width of text to the left of the cursor
|
|
|
|
if (mCursorLocation != -1) {
|
|
string cursorDisplay = displayValue;
|
|
cursorDisplay.resize(mCursorLocation);
|
|
cursorTextWidth = gr_ttf_measureEx(cursorDisplay.c_str(), fontResource);
|
|
}
|
|
cursorX = mRenderX + cursorTextWidth + scrollingX;
|
|
if (cursorX >= mRenderX + mRenderW) {
|
|
scrollingX = mRenderW - cursorTextWidth;
|
|
cursorX = mRenderX + mRenderW - CursorWidth;
|
|
} else if (cursorX < mRenderX) {
|
|
scrollingX = cursorTextWidth * -1;
|
|
cursorX = mRenderX;
|
|
}
|
|
}
|
|
|
|
int GUIInput::Render(void)
|
|
{
|
|
if (!isConditionTrue())
|
|
{
|
|
mRendered = false;
|
|
return 0;
|
|
}
|
|
|
|
void* fontResource = NULL;
|
|
if (mFont) fontResource = mFont->GetResource();
|
|
|
|
// First step, fill background
|
|
gr_color(mBackgroundColor.red, mBackgroundColor.green, mBackgroundColor.blue, 255);
|
|
gr_fill(mRenderX, mRenderY, mRenderW, mRenderH);
|
|
|
|
// Next, render the background resource (if it exists)
|
|
if (mBackground && mBackground->GetResource())
|
|
{
|
|
mBackgroundX = mRenderX + ((mRenderW - mBackgroundW) / 2);
|
|
mBackgroundY = mRenderY + ((mRenderH - mBackgroundH) / 2);
|
|
gr_blit(mBackground->GetResource(), 0, 0, mBackgroundW, mBackgroundH, mBackgroundX, mBackgroundY);
|
|
}
|
|
|
|
int ret = 0;
|
|
|
|
// Render the text
|
|
if (mInputText) {
|
|
mInputText->SetRenderPos(mRenderX + scrollingX, mFontY);
|
|
mInputText->SetText(displayValue);
|
|
gr_clip(mRenderX, mRenderY, mRenderW, mRenderH);
|
|
ret = mInputText->Render();
|
|
gr_noclip();
|
|
}
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
if (HasInputFocus && DrawCursor) {
|
|
// Render the cursor
|
|
gr_color(mCursorColor.red, mCursorColor.green, mCursorColor.blue, 255);
|
|
gr_fill(cursorX, mFontY, CursorWidth, mFontHeight);
|
|
}
|
|
|
|
mRendered = true;
|
|
return ret;
|
|
}
|
|
|
|
int GUIInput::Update(void)
|
|
{
|
|
if (!isConditionTrue()) return (mRendered ? 2 : 0);
|
|
if (!mRendered) return 2;
|
|
|
|
int ret = 0;
|
|
|
|
if (mInputText) ret = mInputText->Update();
|
|
if (ret < 0) return ret;
|
|
|
|
return ret;
|
|
}
|
|
|
|
int GUIInput::GetSelection(int x, int y)
|
|
{
|
|
if (x < mRenderX || x - mRenderX > mRenderW || y < mRenderY || y - mRenderY > mRenderH) return -1;
|
|
return (x - mRenderX);
|
|
}
|
|
|
|
int GUIInput::NotifyTouch(TOUCH_STATE state, int x, int y)
|
|
{
|
|
static int startSelection = -1;
|
|
void* fontResource = NULL;
|
|
|
|
if (mFont) fontResource = mFont->GetResource();
|
|
|
|
if (!isConditionTrue())
|
|
return -1;
|
|
|
|
if (!HasInputFocus) {
|
|
if (state != TOUCH_RELEASE)
|
|
return 0; // Only change focus if touch releases within the input box
|
|
if (GetSelection(x, y) >= 0) {
|
|
// When changing focus, we don't scroll or change the cursor location
|
|
PageManager::SetKeyBoardFocus(0);
|
|
PageManager::NotifyCharInput(0);
|
|
SetInputFocus(1);
|
|
DrawCursor = true;
|
|
mRendered = false;
|
|
}
|
|
} else {
|
|
switch (state) {
|
|
case TOUCH_HOLD:
|
|
case TOUCH_REPEAT:
|
|
break;
|
|
case TOUCH_START:
|
|
startSelection = GetSelection(x,y);
|
|
lastX = x;
|
|
DrawCursor = false;
|
|
mRendered = false;
|
|
break;
|
|
|
|
case TOUCH_DRAG:
|
|
// Check if we dragged out of the selection window
|
|
if (GetSelection(x, y) == -1) {
|
|
lastX = 0;
|
|
break;
|
|
}
|
|
|
|
DrawCursor = false;
|
|
|
|
// Provide some debounce on initial touches
|
|
if (startSelection != -1 && abs(x - lastX) < 6) {
|
|
break;
|
|
}
|
|
|
|
startSelection = -1;
|
|
if (lastX != x)
|
|
HandleTextLocation(x);
|
|
break;
|
|
|
|
case TOUCH_RELEASE:
|
|
// We've moved the cursor location
|
|
mRendered = false;
|
|
DrawCursor = true;
|
|
HandleCursorByTouch(x);
|
|
break;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int GUIInput::NotifyVarChange(const std::string& varName, const std::string& value)
|
|
{
|
|
GUIObject::NotifyVarChange(varName, value);
|
|
|
|
if (varName == mVariable) {
|
|
if (!isLocalChange) {
|
|
UpdateDisplayText();
|
|
HandleTextLocation(TW_INPUT_NO_UPDATE);
|
|
} else
|
|
isLocalChange = false;
|
|
return 0;
|
|
}
|
|
if (varName.empty()) {
|
|
UpdateDisplayText();
|
|
HandleTextLocation(TW_INPUT_NO_UPDATE);
|
|
HandleCursorByText();
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int GUIInput::NotifyKey(int key, bool down)
|
|
{
|
|
if (!HasInputFocus || !down)
|
|
return 1;
|
|
|
|
switch (key)
|
|
{
|
|
case KEY_LEFT:
|
|
if (mCursorLocation == 0)
|
|
return 0; // we're already at the beginning
|
|
if (mCursorLocation == -1) {
|
|
if (displayValue.size() == 0) {
|
|
cursorX = mRenderX;
|
|
return 0;
|
|
}
|
|
mCursorLocation = displayValue.size() - 1;
|
|
} else {
|
|
mCursorLocation--;
|
|
}
|
|
mRendered = false;
|
|
HandleCursorByText();
|
|
return 0;
|
|
|
|
case KEY_RIGHT:
|
|
if (mCursorLocation == -1)
|
|
return 0; // we're already at the end
|
|
mCursorLocation++;
|
|
if ((int)displayValue.size() <= mCursorLocation)
|
|
mCursorLocation = -1;
|
|
HandleCursorByText();
|
|
mRendered = false;
|
|
return 0;
|
|
|
|
case KEY_HOME:
|
|
case KEY_UP:
|
|
if (displayValue.size() == 0)
|
|
return 0;
|
|
mCursorLocation = 0;
|
|
mRendered = false;
|
|
cursorX = mRenderX;
|
|
return 0;
|
|
|
|
case KEY_END:
|
|
case KEY_DOWN:
|
|
mCursorLocation = -1;
|
|
mRendered = false;
|
|
HandleCursorByText();
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
int GUIInput::NotifyCharInput(int key)
|
|
{
|
|
if (HasInputFocus) {
|
|
if (key == KEYBOARD_BACKSPACE) {
|
|
//Backspace
|
|
if (mValue.size() > 0 && mCursorLocation != 0) {
|
|
if (mCursorLocation == -1) {
|
|
mValue.resize(mValue.size() - 1);
|
|
} else {
|
|
mValue.erase(mCursorLocation - 1, 1);
|
|
mCursorLocation--;
|
|
}
|
|
isLocalChange = true;
|
|
DataManager::SetValue(mVariable, mValue);
|
|
UpdateDisplayText();
|
|
HandleTextLocation(TW_INPUT_NO_UPDATE);
|
|
HandleCursorByText();
|
|
}
|
|
} else if (key == KEYBOARD_SWIPE_LEFT) {
|
|
// Delete all
|
|
isLocalChange = true;
|
|
if (mCursorLocation == -1) {
|
|
DataManager::SetValue (mVariable, "");
|
|
mValue = "";
|
|
textWidth = 0;
|
|
mCursorLocation = -1;
|
|
} else {
|
|
mValue.erase(0, mCursorLocation);
|
|
DataManager::SetValue(mVariable, mValue);
|
|
mCursorLocation = 0;
|
|
}
|
|
UpdateDisplayText();
|
|
cursorX = mRenderX;
|
|
scrollingX = 0;
|
|
mRendered = false;
|
|
return 0;
|
|
} else if (key >= 32) {
|
|
// Regular key
|
|
if (HasAllowed && AllowedList.find((char)key) == string::npos) {
|
|
return 0;
|
|
}
|
|
if (HasDisabled && DisabledList.find((char)key) != string::npos) {
|
|
return 0;
|
|
}
|
|
if (MaxLen != 0 && mValue.size() >= MaxLen) {
|
|
return 0;
|
|
}
|
|
if (mCursorLocation == -1) {
|
|
mValue += key;
|
|
} else {
|
|
mValue.insert(mCursorLocation, 1, key);
|
|
mCursorLocation++;
|
|
}
|
|
isLocalChange = true;
|
|
DataManager::SetValue(mVariable, mValue);
|
|
UpdateDisplayText();
|
|
HandleTextLocation(TW_INPUT_NO_UPDATE);
|
|
HandleCursorByText();
|
|
} else if (key == KEYBOARD_ACTION) {
|
|
// Action
|
|
if (mAction) {
|
|
unsigned inputLen = mValue.length();
|
|
if (inputLen < MinLen)
|
|
return 0;
|
|
else if (MaxLen != 0 && inputLen > MaxLen)
|
|
return 0;
|
|
else
|
|
return (mAction ? mAction->NotifyTouch(TOUCH_RELEASE, mRenderX, mRenderY) : 1);
|
|
}
|
|
}
|
|
return 0;
|
|
} else {
|
|
if (key == 0) {
|
|
// Somewhat ugly hack-ish way to tell the box to redraw after losing focus to remove the cursor
|
|
mRendered = false;
|
|
return 1;
|
|
}
|
|
}
|
|
return 1;
|
|
}
|