diff options
Diffstat (limited to 'gui/pages.cpp')
-rw-r--r-- | gui/pages.cpp | 1710 |
1 files changed, 1710 insertions, 0 deletions
diff --git a/gui/pages.cpp b/gui/pages.cpp new file mode 100644 index 000000000..a3a1df325 --- /dev/null +++ b/gui/pages.cpp @@ -0,0 +1,1710 @@ +/* + Copyright 2013 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/>. +*/ + +// pages.cpp - Source to manage GUI base objects + +#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 <dirent.h> +#include "../twrp-functions.hpp" +#include "../partitions.hpp" + +#include <string> +#include <algorithm> + +#ifdef USE_MINZIP +#include "../minzip/SysUtil.h" +#else +#include "../otautil/SysUtil.h" +#endif + +extern "C" { +#include "../twcommon.h" +#include "gui.h" +} +#include "../zipwrap.hpp" +#include "../minuitwrp/minui.h" + +#include "rapidxml.hpp" +#include "objects.hpp" +#include "blanktimer.hpp" + +// version 2 requires theme to handle power button as action togglebacklight +#define TW_THEME_VERSION 3 + +#define TW_THEME_VER_ERR -2 + +extern int gGuiRunning; + +std::map<std::string, PageSet*> PageManager::mPageSets; +PageSet* PageManager::mCurrentSet; +MouseCursor *PageManager::mMouseCursor = NULL; +HardwareKeyboard *PageManager::mHardwareKeyboard = NULL; +bool PageManager::mReloadTheme = false; +std::string PageManager::mStartPage = "main"; +std::vector<language_struct> Language_List; + +int tw_x_offset = 0; +int tw_y_offset = 0; +int tw_w_offset = 0; +int tw_h_offset = 0; + +// Helper routine to convert a string to a color declaration +int ConvertStrToColor(std::string str, COLOR* color) +{ + // Set the default, solid black + memset(color, 0, sizeof(COLOR)); + color->alpha = 255; + + // Translate variables + DataManager::GetValue(str, str); + + // Look for some defaults + if (str == "black") return 0; + else if (str == "white") { color->red = color->green = color->blue = 255; return 0; } + else if (str == "red") { color->red = 255; return 0; } + else if (str == "green") { color->green = 255; return 0; } + else if (str == "blue") { color->blue = 255; return 0; } + + // At this point, we require an RGB(A) color + if (str[0] != '#') + return -1; + + str.erase(0, 1); + + int result; + if (str.size() >= 8) { + // We have alpha channel + string alpha = str.substr(6, 2); + result = strtol(alpha.c_str(), NULL, 16); + color->alpha = result & 0x000000FF; + str.resize(6); + result = strtol(str.c_str(), NULL, 16); + color->red = (result >> 16) & 0x000000FF; + color->green = (result >> 8) & 0x000000FF; + color->blue = result & 0x000000FF; + } else { + result = strtol(str.c_str(), NULL, 16); + color->red = (result >> 16) & 0x000000FF; + color->green = (result >> 8) & 0x000000FF; + color->blue = result & 0x000000FF; + } + return 0; +} + +// Helper APIs +xml_node<>* FindNode(xml_node<>* parent, const char* nodename, int depth /* = 0 */) +{ + if (!parent) + return NULL; + + xml_node<>* child = parent->first_node(nodename); + if (child) + return child; + + if (depth == 10) { + LOGERR("Too many style loops detected.\n"); + return NULL; + } + + xml_node<>* style = parent->first_node("style"); + if (style) { + while (style) { + if (!style->first_attribute("name")) { + LOGERR("No name given for style.\n"); + continue; + } else { + std::string name = style->first_attribute("name")->value(); + xml_node<>* node = PageManager::FindStyle(name); + + if (node) { + // We found the style that was named + xml_node<>* stylenode = FindNode(node, nodename, depth + 1); + if (stylenode) + return stylenode; + } + } + style = style->next_sibling("style"); + } + } else { + // Search for stylename in the parent node <object type="foo" style="foo2"> + xml_attribute<>* attr = parent->first_attribute("style"); + // If no style is found anywhere else and the node wasn't found in the object itself + // as a special case we will search for a style that uses the same style name as the + // object type, so <object type="button"> would search for a style named "button" + if (!attr) + attr = parent->first_attribute("type"); + // if there's no attribute type, the object type must be the element name + std::string stylename = attr ? attr->value() : parent->name(); + xml_node<>* node = PageManager::FindStyle(stylename); + if (node) { + xml_node<>* stylenode = FindNode(node, nodename, depth + 1); + if (stylenode) + return stylenode; + } + } + return NULL; +} + +std::string LoadAttrString(xml_node<>* element, const char* attrname, const char* defaultvalue) +{ + if (!element) + return defaultvalue; + + xml_attribute<>* attr = element->first_attribute(attrname); + return attr ? attr->value() : defaultvalue; +} + +int LoadAttrInt(xml_node<>* element, const char* attrname, int defaultvalue) +{ + string value = LoadAttrString(element, attrname); + // resolve variables + DataManager::GetValue(value, value); + return value.empty() ? defaultvalue : atoi(value.c_str()); +} + +int LoadAttrIntScaleX(xml_node<>* element, const char* attrname, int defaultvalue) +{ + return scale_theme_x(LoadAttrInt(element, attrname, defaultvalue)); +} + +int LoadAttrIntScaleY(xml_node<>* element, const char* attrname, int defaultvalue) +{ + return scale_theme_y(LoadAttrInt(element, attrname, defaultvalue)); +} + +COLOR LoadAttrColor(xml_node<>* element, const char* attrname, bool* found_color, COLOR defaultvalue) +{ + string value = LoadAttrString(element, attrname); + *found_color = !value.empty(); + // resolve variables + DataManager::GetValue(value, value); + COLOR ret = defaultvalue; + if (ConvertStrToColor(value, &ret) == 0) + return ret; + else + return defaultvalue; +} + +COLOR LoadAttrColor(xml_node<>* element, const char* attrname, COLOR defaultvalue) +{ + bool found_color = false; + return LoadAttrColor(element, attrname, &found_color, defaultvalue); +} + +FontResource* LoadAttrFont(xml_node<>* element, const char* attrname) +{ + std::string name = LoadAttrString(element, attrname, ""); + if (name.empty()) + return NULL; + else + return PageManager::GetResources()->FindFont(name); +} + +ImageResource* LoadAttrImage(xml_node<>* element, const char* attrname) +{ + std::string name = LoadAttrString(element, attrname, ""); + if (name.empty()) + return NULL; + else + return PageManager::GetResources()->FindImage(name); +} + +AnimationResource* LoadAttrAnimation(xml_node<>* element, const char* attrname) +{ + std::string name = LoadAttrString(element, attrname, ""); + if (name.empty()) + return NULL; + else + return PageManager::GetResources()->FindAnimation(name); +} + +bool LoadPlacement(xml_node<>* node, int* x, int* y, int* w /* = NULL */, int* h /* = NULL */, Placement* placement /* = NULL */) +{ + if (!node) + return false; + + if (node->first_attribute("x")) + *x = LoadAttrIntScaleX(node, "x") + tw_x_offset; + + if (node->first_attribute("y")) + *y = LoadAttrIntScaleY(node, "y") + tw_y_offset; + + if (w && node->first_attribute("w")) + *w = LoadAttrIntScaleX(node, "w"); + + if (h && node->first_attribute("h")) + *h = LoadAttrIntScaleY(node, "h"); + + if (placement && node->first_attribute("placement")) + *placement = (Placement) LoadAttrInt(node, "placement"); + + return true; +} + +int ActionObject::SetActionPos(int x, int y, int w, int h) +{ + if (x < 0 || y < 0) + return -1; + + mActionX = x; + mActionY = y; + if (w || h) + { + mActionW = w; + mActionH = h; + } + return 0; +} + +Page::Page(xml_node<>* page, std::vector<xml_node<>*> *templates) +{ + mTouchStart = NULL; + + // We can memset the whole structure, because the alpha channel is ignored + memset(&mBackground, 0, sizeof(COLOR)); + + // With NULL, we make a console-only display + if (!page) + { + mName = "console"; + + GUIConsole* element = new GUIConsole(NULL); + mRenders.push_back(element); + mActions.push_back(element); + return; + } + + if (page->first_attribute("name")) + mName = page->first_attribute("name")->value(); + else + { + LOGERR("No page name attribute found!\n"); + return; + } + + LOGINFO("Loading page %s\n", mName.c_str()); + + // This is a recursive routine for template handling + ProcessNode(page, templates, 0); +} + +Page::~Page() +{ + for (std::vector<GUIObject*>::iterator itr = mObjects.begin(); itr != mObjects.end(); ++itr) + delete *itr; +} + +bool Page::ProcessNode(xml_node<>* page, std::vector<xml_node<>*> *templates, int depth) +{ + if (depth == 10) + { + LOGERR("Page processing depth has exceeded 10. Failing out. This is likely a recursive template.\n"); + return false; + } + + for (xml_node<>* child = page->first_node(); child; child = child->next_sibling()) + { + std::string type = child->name(); + + if (type == "background") { + mBackground = LoadAttrColor(child, "color", COLOR(0,0,0,0)); + continue; + } + + if (type == "object") { + // legacy format : <object type="..."> + xml_attribute<>* attr = child->first_attribute("type"); + type = attr ? attr->value() : "*unspecified*"; + } + + if (type == "text") + { + GUIText* element = new GUIText(child); + mObjects.push_back(element); + mRenders.push_back(element); + mActions.push_back(element); + } + else if (type == "image") + { + GUIImage* element = new GUIImage(child); + mObjects.push_back(element); + mRenders.push_back(element); + } + else if (type == "fill") + { + GUIFill* element = new GUIFill(child); + mObjects.push_back(element); + mRenders.push_back(element); + } + else if (type == "action") + { + GUIAction* element = new GUIAction(child); + mObjects.push_back(element); + mActions.push_back(element); + } + else if (type == "console") + { + GUIConsole* element = new GUIConsole(child); + mObjects.push_back(element); + mRenders.push_back(element); + mActions.push_back(element); + } + else if (type == "terminal") + { + GUITerminal* element = new GUITerminal(child); + mObjects.push_back(element); + mRenders.push_back(element); + mActions.push_back(element); + mInputs.push_back(element); + } + else if (type == "button") + { + GUIButton* element = new GUIButton(child); + mObjects.push_back(element); + mRenders.push_back(element); + mActions.push_back(element); + } + else if (type == "checkbox") + { + GUICheckbox* element = new GUICheckbox(child); + mObjects.push_back(element); + mRenders.push_back(element); + mActions.push_back(element); + } + else if (type == "fileselector") + { + GUIFileSelector* element = new GUIFileSelector(child); + mObjects.push_back(element); + mRenders.push_back(element); + mActions.push_back(element); + } + else if (type == "animation") + { + GUIAnimation* element = new GUIAnimation(child); + mObjects.push_back(element); + mRenders.push_back(element); + } + else if (type == "progressbar") + { + GUIProgressBar* element = new GUIProgressBar(child); + mObjects.push_back(element); + mRenders.push_back(element); + mActions.push_back(element); + } + else if (type == "slider") + { + GUISlider* element = new GUISlider(child); + mObjects.push_back(element); + mRenders.push_back(element); + mActions.push_back(element); + } + else if (type == "slidervalue") + { + GUISliderValue *element = new GUISliderValue(child); + mObjects.push_back(element); + mRenders.push_back(element); + mActions.push_back(element); + } + else if (type == "listbox") + { + GUIListBox* element = new GUIListBox(child); + mObjects.push_back(element); + mRenders.push_back(element); + mActions.push_back(element); + } + else if (type == "keyboard") + { + GUIKeyboard* element = new GUIKeyboard(child); + mObjects.push_back(element); + mRenders.push_back(element); + mActions.push_back(element); + } + else if (type == "input") + { + GUIInput* element = new GUIInput(child); + mObjects.push_back(element); + mRenders.push_back(element); + mActions.push_back(element); + mInputs.push_back(element); + } + else if (type == "partitionlist") + { + GUIPartitionList* element = new GUIPartitionList(child); + mObjects.push_back(element); + mRenders.push_back(element); + mActions.push_back(element); + } + else if (type == "patternpassword") + { + GUIPatternPassword* element = new GUIPatternPassword(child); + mObjects.push_back(element); + mRenders.push_back(element); + mActions.push_back(element); + } + else if (type == "textbox") + { + GUITextBox* element = new GUITextBox(child); + mObjects.push_back(element); + mRenders.push_back(element); + mActions.push_back(element); + } + else if (type == "template") + { + if (!templates || !child->first_attribute("name")) + { + LOGERR("Invalid template request.\n"); + } + else + { + std::string name = child->first_attribute("name")->value(); + xml_node<>* node; + bool node_found = false; + + // We need to find the correct template + for (std::vector<xml_node<>*>::iterator itr = templates->begin(); itr != templates->end(); itr++) { + node = (*itr)->first_node("template"); + + while (node) + { + if (!node->first_attribute("name")) + continue; + + if (name == node->first_attribute("name")->value()) + { + if (!ProcessNode(node, templates, depth + 1)) + return false; + else { + node_found = true; + break; + } + } + if (node_found) + break; + node = node->next_sibling("template"); + } + // [check] why is there no if (node_found) here too? + } + } + } + else + { + LOGERR("Unknown object type: %s.\n", type.c_str()); + } + } + return true; +} + +int Page::Render(void) +{ + // Render background + gr_color(mBackground.red, mBackground.green, mBackground.blue, mBackground.alpha); + gr_fill(0, 0, gr_fb_width(), gr_fb_height()); + + // Render remaining objects + std::vector<RenderObject*>::iterator iter; + for (iter = mRenders.begin(); iter != mRenders.end(); iter++) + { + if ((*iter)->Render()) + LOGERR("A render request has failed.\n"); + } + return 0; +} + +int Page::Update(void) +{ + int retCode = 0; + + std::vector<RenderObject*>::iterator iter; + for (iter = mRenders.begin(); iter != mRenders.end(); iter++) + { + int ret = (*iter)->Update(); + if (ret < 0) + LOGERR("An update request has failed.\n"); + else if (ret > retCode) + retCode = ret; + } + + return retCode; +} + +int Page::NotifyTouch(TOUCH_STATE state, int x, int y) +{ + // By default, return 1 to ignore further touches if nobody is listening + int ret = 1; + + // Don't try to handle a lack of handlers + if (mActions.size() == 0) + return ret; + + // We record mTouchStart so we can pass all the touch stream to the same handler + if (state == TOUCH_START) + { + std::vector<ActionObject*>::reverse_iterator iter; + // We work backwards, from top-most element to bottom-most element + for (iter = mActions.rbegin(); iter != mActions.rend(); iter++) + { + if ((*iter)->IsInRegion(x, y)) + { + mTouchStart = (*iter); + ret = mTouchStart->NotifyTouch(state, x, y); + if (ret >= 0) + break; + mTouchStart = NULL; + } + } + } + else if (state == TOUCH_RELEASE && mTouchStart != NULL) + { + ret = mTouchStart->NotifyTouch(state, x, y); + mTouchStart = NULL; + } + else if ((state == TOUCH_DRAG || state == TOUCH_HOLD || state == TOUCH_REPEAT) && mTouchStart != NULL) + { + ret = mTouchStart->NotifyTouch(state, x, y); + } + return ret; +} + +int Page::NotifyKey(int key, bool down) +{ + std::vector<ActionObject*>::reverse_iterator iter; + + 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; + } + } + return ret; +} + +int Page::NotifyCharInput(int ch) +{ + std::vector<InputObject*>::reverse_iterator iter; + + // We work backwards, from top-most element to bottom-most element + for (iter = mInputs.rbegin(); iter != mInputs.rend(); iter++) + { + int ret = (*iter)->NotifyCharInput(ch); + if (ret == 0) + return 0; + else if (ret < 0) + LOGERR("A char input handler has returned an error\n"); + } + return 1; +} + +int Page::SetKeyBoardFocus(int inFocus) +{ + std::vector<InputObject*>::reverse_iterator iter; + + // We work backwards, from top-most element to bottom-most element + for (iter = mInputs.rbegin(); iter != mInputs.rend(); iter++) + { + int ret = (*iter)->SetInputFocus(inFocus); + if (ret == 0) + return 0; + else if (ret < 0) + LOGERR("An input focus handler has returned an error\n"); + } + return 1; +} + +void Page::SetPageFocus(int inFocus) +{ + // Render remaining objects + std::vector<RenderObject*>::iterator iter; + for (iter = mRenders.begin(); iter != mRenders.end(); iter++) + (*iter)->SetPageFocus(inFocus); + + return; +} + +int Page::NotifyVarChange(std::string varName, std::string value) +{ + std::vector<GUIObject*>::iterator iter; + for (iter = mObjects.begin(); iter != mObjects.end(); ++iter) + { + if ((*iter)->NotifyVarChange(varName, value)) + LOGERR("An action handler errored on NotifyVarChange.\n"); + } + return 0; +} + + +// transient data for loading themes +struct LoadingContext +{ + ZipWrap* zip; // zip to load theme from, or NULL for the stock theme + std::set<std::string> filenames; // to detect cyclic includes + std::string basepath; // if zip is NULL, base path to load includes from with trailing slash, otherwise empty + std::vector<xml_document<>*> xmldocs; // all loaded xml docs + std::vector<char*> xmlbuffers; // text buffers with xml content + std::vector<xml_node<>*> styles; // refer to <styles> nodes inside xmldocs + std::vector<xml_node<>*> templates; // refer to <templates> nodes inside xmldocs + + LoadingContext() + { + zip = NULL; + } + + ~LoadingContext() + { + // free all xml buffers + for (std::vector<char*>::iterator it = xmlbuffers.begin(); it != xmlbuffers.end(); ++it) + free(*it); + } + +}; + +// for FindStyle +LoadingContext* PageManager::currentLoadingContext = NULL; + + +PageSet::PageSet() +{ + mResources = new ResourceManager; + mCurrentPage = NULL; + + set_scale_values(1, 1); // Reset any previous scaling values +} + +PageSet::~PageSet() +{ + mOverlays.clear(); + for (std::vector<Page*>::iterator itr = mPages.begin(); itr != mPages.end(); ++itr) + delete *itr; + + delete mResources; +} + +int PageSet::Load(LoadingContext& ctx, const std::string& filename) +{ + bool isMain = ctx.xmlbuffers.empty(); // if we have no files yet, remember that this is the main XML file + + if (!ctx.filenames.insert(filename).second) + // ignore already loaded files to prevent crash with cyclic includes + return 0; + + // load XML into buffer + char* xmlbuffer = PageManager::LoadFileToBuffer(filename, ctx.zip); + if (!xmlbuffer) + return -1; // error already displayed by LoadFileToBuffer + ctx.xmlbuffers.push_back(xmlbuffer); + + // parse XML + xml_document<>* doc = new xml_document<>(); + doc->parse<0>(xmlbuffer); + ctx.xmldocs.push_back(doc); + + xml_node<>* root = doc->first_node("recovery"); + if (!root) + root = doc->first_node("install"); + if (!root) { + LOGERR("Unknown root element in %s\n", filename.c_str()); + return -1; + } + + if (isMain) { + int rc = LoadDetails(ctx, root); + if (rc != 0) + return rc; + } + + LOGINFO("Loading resources...\n"); + xml_node<>* child = root->first_node("resources"); + if (child) + mResources->LoadResources(child, ctx.zip, "theme"); + + LOGINFO("Loading variables...\n"); + child = root->first_node("variables"); + if (child) + LoadVariables(child); + + LOGINFO("Loading mouse cursor...\n"); + child = root->first_node("mousecursor"); + if (child) + PageManager::LoadCursorData(child); + + LOGINFO("Loading pages...\n"); + child = root->first_node("templates"); + if (child) + ctx.templates.push_back(child); + + child = root->first_node("styles"); + if (child) + ctx.styles.push_back(child); + + // Load pages + child = root->first_node("pages"); + if (child) { + if (LoadPages(ctx, child)) { + LOGERR("PageSet::Load returning -1\n"); + return -1; + } + } + + // process includes recursively + child = root->first_node("include"); + if (child) { + xml_node<>* include = child->first_node("xmlfile"); + while (include != NULL) { + xml_attribute<>* attr = include->first_attribute("name"); + if (!attr) { + LOGERR("Skipping include/xmlfile with no name\n"); + continue; + } + + string filename = ctx.basepath + attr->value(); + LOGINFO("Including file: %s...\n", filename.c_str()); + int rc = Load(ctx, filename); + if (rc != 0) + return rc; + + include = include->next_sibling("xmlfile"); + } + } + + return 0; +} + +void PageSet::MakeEmergencyConsoleIfNeeded() +{ + if (mPages.empty()) { + mCurrentPage = new Page(NULL, NULL); // fallback console page + // TODO: since removal of non-TTF fonts, the emergency console doesn't work without a font, which might be missing too + mPages.push_back(mCurrentPage); + } +} + +int PageSet::LoadLanguage(char* languageFile, ZipWrap* package) +{ + xml_document<> lang; + xml_node<>* parent; + xml_node<>* child; + std::string resource_source; + int ret = 0; + + if (languageFile) { + printf("parsing languageFile\n"); + lang.parse<0>(languageFile); + printf("parsing languageFile done\n"); + } else { + return -1; + } + + parent = lang.first_node("language"); + if (!parent) { + LOGERR("Unable to locate language node in language file.\n"); + lang.clear(); + return -1; + } + + child = parent->first_node("display"); + if (child) { + DataManager::SetValue("tw_language_display", child->value()); + resource_source = child->value(); + } else { + LOGERR("language file does not have a display value set\n"); + DataManager::SetValue("tw_language_display", "Not Set"); + resource_source = languageFile; + } + + child = parent->first_node("resources"); + if (child) + mResources->LoadResources(child, package, resource_source); + else + ret = -1; + DataManager::SetValue("tw_backup_name", gui_lookup("auto_generate", "(Auto Generate)")); + lang.clear(); + return ret; +} + +int PageSet::LoadDetails(LoadingContext& ctx, xml_node<>* root) +{ + xml_node<>* child = root->first_node("details"); + if (child) { + int theme_ver = 0; + xml_node<>* themeversion = child->first_node("themeversion"); + if (themeversion && themeversion->value()) { + theme_ver = atoi(themeversion->value()); + } else { + LOGINFO("No themeversion in theme.\n"); + } + if (theme_ver != TW_THEME_VERSION) { + LOGINFO("theme version from xml: %i, expected %i\n", theme_ver, TW_THEME_VERSION); + if (ctx.zip) { + gui_err("theme_ver_err=Custom theme version does not match TWRP version. Using stock theme."); + return TW_THEME_VER_ERR; + } else { + gui_print_color("warning", "Stock theme version does not match TWRP version.\n"); + } + } + xml_node<>* resolution = child->first_node("resolution"); + if (resolution) { + LOGINFO("Checking resolution...\n"); + xml_attribute<>* width_attr = resolution->first_attribute("width"); + xml_attribute<>* height_attr = resolution->first_attribute("height"); + xml_attribute<>* noscale_attr = resolution->first_attribute("noscaling"); + if (width_attr && height_attr && !noscale_attr) { + int width = atoi(width_attr->value()); + int height = atoi(height_attr->value()); + int offx = 0, offy = 0; +#ifdef TW_ROUND_SCREEN + xml_node<>* roundscreen = child->first_node("roundscreen"); + if (roundscreen) { + LOGINFO("TW_ROUND_SCREEN := true, using round screen XML settings.\n"); + xml_attribute<>* offx_attr = roundscreen->first_attribute("offset_x"); + xml_attribute<>* offy_attr = roundscreen->first_attribute("offset_y"); + if (offx_attr) { + offx = atoi(offx_attr->value()); + } + if (offy_attr) { + offy = atoi(offy_attr->value()); + } + } +#endif + if (width != 0 && height != 0) { + float scale_w = (((float)gr_fb_width() + (float)tw_w_offset) - ((float)offx * 2.0)) / (float)width; + float scale_h = (((float)gr_fb_height() + (float)tw_h_offset) - ((float)offy * 2.0)) / (float)height; +#ifdef TW_ROUND_SCREEN + float scale_off_w = ((float)gr_fb_width() + (float)tw_w_offset) / (float)width; + float scale_off_h = ((float)gr_fb_height() + (float)tw_h_offset) / (float)height; + tw_x_offset = offx * scale_off_w; + tw_y_offset = offy * scale_off_h; +#endif + if (scale_w != 1 || scale_h != 1) { + LOGINFO("Scaling theme width %fx and height %fx, offsets x: %i y: %i w: %i h: %i\n", + scale_w, scale_h, tw_x_offset, tw_y_offset, tw_w_offset, tw_h_offset); + set_scale_values(scale_w, scale_h); + } + } + } else { + LOGINFO("XML does not contain width and height, no scaling will be applied\n"); + } + } else { + LOGINFO("XML contains no resolution tag, no scaling will be applied.\n"); + } + } else { + LOGINFO("XML contains no details tag, no scaling will be applied.\n"); + } + + return 0; +} + +int PageSet::SetPage(std::string page) +{ + Page* tmp = FindPage(page); + if (tmp) + { + if (mCurrentPage) mCurrentPage->SetPageFocus(0); + mCurrentPage = tmp; + mCurrentPage->SetPageFocus(1); + mCurrentPage->NotifyVarChange("", ""); + return 0; + } + else + { + LOGERR("Unable to locate page (%s)\n", page.c_str()); + } + return -1; +} + +int PageSet::SetOverlay(Page* page) +{ + if (page) { + if (mOverlays.size() >= 10) { + LOGERR("Too many overlays requested, max is 10.\n"); + return -1; + } + + std::vector<Page*>::iterator iter; + for (iter = mOverlays.begin(); iter != mOverlays.end(); iter++) { + if ((*iter)->GetName() == page->GetName()) { + mOverlays.erase(iter); + // SetOverlay() is (and should stay) the only function which + // adds to mOverlays. Then, each page can appear at most once. + break; + } + } + + page->SetPageFocus(1); + page->NotifyVarChange("", ""); + + if (!mOverlays.empty()) + mOverlays.back()->SetPageFocus(0); + + mOverlays.push_back(page); + } else { + if (!mOverlays.empty()) { + mOverlays.back()->SetPageFocus(0); + mOverlays.pop_back(); + if (!mOverlays.empty()) + mOverlays.back()->SetPageFocus(1); + else if (mCurrentPage) + mCurrentPage->SetPageFocus(1); // Just in case somehow the regular page lost focus, we'll set it again + } + } + return 0; +} + +const ResourceManager* PageSet::GetResources() +{ + return mResources; +} + +Page* PageSet::FindPage(std::string name) +{ + std::vector<Page*>::iterator iter; + + for (iter = mPages.begin(); iter != mPages.end(); iter++) + { + if (name == (*iter)->GetName()) + return (*iter); + } + return NULL; +} + +int PageSet::LoadVariables(xml_node<>* vars) +{ + xml_node<>* child; + xml_attribute<> *name, *value, *persist; + int p; + + child = vars->first_node("variable"); + while (child) + { + name = child->first_attribute("name"); + value = child->first_attribute("value"); + persist = child->first_attribute("persist"); + if (name && value) + { + if (strcmp(name->value(), "tw_x_offset") == 0) { + tw_x_offset = atoi(value->value()); + child = child->next_sibling("variable"); + continue; + } + if (strcmp(name->value(), "tw_y_offset") == 0) { + tw_y_offset = atoi(value->value()); + child = child->next_sibling("variable"); + continue; + } + if (strcmp(name->value(), "tw_w_offset") == 0) { + tw_w_offset = atoi(value->value()); + child = child->next_sibling("variable"); + continue; + } + if (strcmp(name->value(), "tw_h_offset") == 0) { + tw_h_offset = atoi(value->value()); + child = child->next_sibling("variable"); + continue; + } + p = persist ? atoi(persist->value()) : 0; + string temp = value->value(); + string valstr = gui_parse_text(temp); + + if (valstr.find("+") != string::npos) { + string val1str = valstr; + val1str = val1str.substr(0, val1str.find('+')); + string val2str = valstr; + val2str = val2str.substr(val2str.find('+') + 1, string::npos); + int val1 = atoi(val1str.c_str()); + int val2 = atoi(val2str.c_str()); + int val = val1 + val2; + + DataManager::SetValue(name->value(), val, p); + } else if (valstr.find("-") != string::npos) { + string val1str = valstr; + val1str = val1str.substr(0, val1str.find('-')); + string val2str = valstr; + val2str = val2str.substr(val2str.find('-') + 1, string::npos); + int val1 = atoi(val1str.c_str()); + int val2 = atoi(val2str.c_str()); + int val = val1 - val2; + + DataManager::SetValue(name->value(), val, p); + } else { + DataManager::SetValue(name->value(), valstr, p); + } + } + + child = child->next_sibling("variable"); + } + return 0; +} + +int PageSet::LoadPages(LoadingContext& ctx, xml_node<>* pages) +{ + xml_node<>* child; + + if (!pages) + return -1; + + child = pages->first_node("page"); + while (child != NULL) + { + Page* page = new Page(child, &ctx.templates); + if (page->GetName().empty()) + { + LOGERR("Unable to process load page\n"); + delete page; + } + else + { + mPages.push_back(page); + } + child = child->next_sibling("page"); + } + if (mPages.size() > 0) + return 0; + return -1; +} + +int PageSet::IsCurrentPage(Page* page) +{ + return ((mCurrentPage && mCurrentPage == page) ? 1 : 0); +} + +std::string PageSet::GetCurrentPage() const +{ + return mCurrentPage ? mCurrentPage->GetName() : ""; +} + +int PageSet::Render(void) +{ + int ret; + + ret = (mCurrentPage ? mCurrentPage->Render() : -1); + if (ret < 0) + return ret; + + std::vector<Page*>::iterator iter; + + for (iter = mOverlays.begin(); iter != mOverlays.end(); iter++) { + ret = ((*iter) ? (*iter)->Render() : -1); + if (ret < 0) + return ret; + } + return ret; +} + +int PageSet::Update(void) +{ + int ret; + + ret = (mCurrentPage ? mCurrentPage->Update() : -1); + if (ret < 0 || ret > 1) + return ret; + + std::vector<Page*>::iterator iter; + + for (iter = mOverlays.begin(); iter != mOverlays.end(); iter++) { + ret = ((*iter) ? (*iter)->Update() : -1); + if (ret < 0) + return ret; + } + return ret; +} + +int PageSet::NotifyTouch(TOUCH_STATE state, int x, int y) +{ + if (!mOverlays.empty()) + return mOverlays.back()->NotifyTouch(state, x, y); + + return (mCurrentPage ? mCurrentPage->NotifyTouch(state, x, y) : -1); +} + +int PageSet::NotifyKey(int key, bool down) +{ + if (!mOverlays.empty()) + return mOverlays.back()->NotifyKey(key, down); + + return (mCurrentPage ? mCurrentPage->NotifyKey(key, down) : -1); +} + +int PageSet::NotifyCharInput(int ch) +{ + if (!mOverlays.empty()) + return mOverlays.back()->NotifyCharInput(ch); + + return (mCurrentPage ? mCurrentPage->NotifyCharInput(ch) : -1); +} + +int PageSet::SetKeyBoardFocus(int inFocus) +{ + if (!mOverlays.empty()) + return mOverlays.back()->SetKeyBoardFocus(inFocus); + + return (mCurrentPage ? mCurrentPage->SetKeyBoardFocus(inFocus) : -1); +} + +int PageSet::NotifyVarChange(std::string varName, std::string value) +{ + std::vector<Page*>::iterator iter; + + for (iter = mOverlays.begin(); iter != mOverlays.end(); iter++) + (*iter)->NotifyVarChange(varName, value); + + return (mCurrentPage ? mCurrentPage->NotifyVarChange(varName, value) : -1); +} + +void PageSet::AddStringResource(std::string resource_source, std::string resource_name, std::string value) +{ + mResources->AddStringResource(resource_source, resource_name, value); +} + +char* PageManager::LoadFileToBuffer(std::string filename, ZipWrap* package) { + size_t len; + char* buffer = NULL; + + if (!package) { + // We can try to load the XML directly... + LOGINFO("PageManager::LoadFileToBuffer loading filename: '%s' directly\n", filename.c_str()); + struct stat st; + if (stat(filename.c_str(),&st) != 0) { + // This isn't always an error, sometimes we request files that don't exist. + return NULL; + } + + len = (size_t)st.st_size; + + buffer = (char*) malloc(len + 1); + if (!buffer) { + LOGERR("PageManager::LoadFileToBuffer failed to malloc\n"); + return NULL; + } + + int fd = open(filename.c_str(), O_RDONLY); + if (fd == -1) { + LOGERR("PageManager::LoadFileToBuffer failed to open '%s' - (%s)\n", filename.c_str(), strerror(errno)); + free(buffer); + return NULL; + } + + if (read(fd, buffer, len) < 0) { + LOGERR("PageManager::LoadFileToBuffer failed to read '%s' - (%s)\n", filename.c_str(), strerror(errno)); + free(buffer); + close(fd); + return NULL; + } + close(fd); + } else { + LOGINFO("PageManager::LoadFileToBuffer loading filename: '%s' from zip\n", filename.c_str()); + if (!package->EntryExists(filename)) { + LOGERR("Unable to locate '%s' in zip file\n", filename.c_str()); + return NULL; + } + + // Allocate the buffer for the file + len = package->GetUncompressedSize(filename); + buffer = (char*) malloc(len + 1); + if (!buffer) + return NULL; + + if (!package->ExtractToBuffer(filename, (unsigned char*) buffer)) { + LOGERR("Unable to extract '%s'\n", filename.c_str()); + free(buffer); + return NULL; + } + } + // NULL-terminate the string + buffer[len] = 0x00; + return buffer; +} + +void PageManager::LoadLanguageListDir(string dir) { + if (!TWFunc::Path_Exists(dir)) { + LOGERR("LoadLanguageListDir '%s' path not found\n", dir.c_str()); + return; + } + + DIR *d = opendir(dir.c_str()); + struct dirent *p; + + if (d == NULL) { + LOGERR("LoadLanguageListDir error opening dir: '%s', %s\n", dir.c_str(), strerror(errno)); + return; + } + + while ((p = readdir(d))) { + if (!strcmp(p->d_name, ".") || !strcmp(p->d_name, "..") || strlen(p->d_name) < 5) + continue; + + string file = p->d_name; + if (file.substr(strlen(p->d_name) - 4) != ".xml") + continue; + string path = dir + p->d_name; + string file_no_extn = file.substr(0, strlen(p->d_name) - 4); + struct language_struct language_entry; + language_entry.filename = file_no_extn; + char* xmlFile = PageManager::LoadFileToBuffer(dir + p->d_name, NULL); + if (xmlFile == NULL) { + LOGERR("LoadLanguageListDir unable to load '%s'\n", language_entry.filename.c_str()); + continue; + } + xml_document<> *doc = new xml_document<>(); + doc->parse<0>(xmlFile); + + xml_node<>* parent = doc->first_node("language"); + if (!parent) { + LOGERR("Invalid language XML file '%s'\n", language_entry.filename.c_str()); + } else { + xml_node<>* child = parent->first_node("display"); + if (child) { + language_entry.displayvalue = child->value(); + } else { + LOGERR("No display value for '%s'\n", language_entry.filename.c_str()); + language_entry.displayvalue = language_entry.filename; + } + Language_List.push_back(language_entry); + } + doc->clear(); + delete doc; + free(xmlFile); + } + closedir(d); +} + +void PageManager::LoadLanguageList(ZipWrap* package) { + Language_List.clear(); + if (TWFunc::Path_Exists(TWRES "customlanguages")) + TWFunc::removeDir(TWRES "customlanguages", true); + if (package) { + TWFunc::Recursive_Mkdir(TWRES "customlanguages"); + package->ExtractRecursive("languages", TWRES "customlanguages/"); + LoadLanguageListDir(TWRES "customlanguages/"); + } else { + LoadLanguageListDir(TWRES "languages/"); + } + + std::sort(Language_List.begin(), Language_List.end()); +} + +void PageManager::LoadLanguage(string filename) { + string actual_filename; + if (TWFunc::Path_Exists(TWRES "customlanguages/" + filename + ".xml")) + actual_filename = TWRES "customlanguages/" + filename + ".xml"; + else + actual_filename = TWRES "languages/" + filename + ".xml"; + char* xmlFile = PageManager::LoadFileToBuffer(actual_filename, NULL); + if (xmlFile == NULL) + LOGERR("Unable to load '%s'\n", actual_filename.c_str()); + else { + mCurrentSet->LoadLanguage(xmlFile, NULL); + free(xmlFile); + } + PartitionManager.Translate_Partition_Display_Names(); +} + +int PageManager::LoadPackage(std::string name, std::string package, std::string startpage) +{ + std::string mainxmlfilename = package; + ZipWrap zip; + char* languageFile = NULL; + char* baseLanguageFile = NULL; + PageSet* pageSet = NULL; + int ret; + MemMapping map; + + mReloadTheme = false; + mStartPage = startpage; + + // init the loading context + LoadingContext ctx; + + // Open the XML file + LOGINFO("Loading package: %s (%s)\n", name.c_str(), package.c_str()); + if (package.size() > 4 && package.substr(package.size() - 4) != ".zip") + { + LOGINFO("Load XML directly\n"); + tw_x_offset = TW_X_OFFSET; + tw_y_offset = TW_Y_OFFSET; + tw_w_offset = TW_W_OFFSET; + tw_h_offset = TW_H_OFFSET; + if (name != "splash") { + LoadLanguageList(NULL); + languageFile = LoadFileToBuffer(TWRES "languages/en.xml", NULL); + } + ctx.basepath = TWRES; + } + else + { + LOGINFO("Loading zip theme\n"); + tw_x_offset = 0; + tw_y_offset = 0; + tw_w_offset = 0; + tw_h_offset = 0; + if (!TWFunc::Path_Exists(package)) + return -1; +#ifdef USE_MINZIP + if (sysMapFile(package.c_str(), &map) != 0) { +#else + if (!map.MapFile(package)) { +#endif + LOGERR("Failed to map '%s'\n", package.c_str()); + goto error; + } + if (!zip.Open(package.c_str(), &map)) { + LOGERR("Unable to open zip archive '%s'\n", package.c_str()); +#ifdef USE_MINZIP + sysReleaseMap(&map); +#endif + goto error; + } + ctx.zip = &zip; + mainxmlfilename = "ui.xml"; + LoadLanguageList(ctx.zip); + languageFile = LoadFileToBuffer("languages/en.xml", ctx.zip); + baseLanguageFile = LoadFileToBuffer(TWRES "languages/en.xml", NULL); + } + + // Before loading, mCurrentSet must be the loading package so we can find resources + pageSet = mCurrentSet; + mCurrentSet = new PageSet(); + + if (baseLanguageFile) { + mCurrentSet->LoadLanguage(baseLanguageFile, NULL); + free(baseLanguageFile); + } + + if (languageFile) { + mCurrentSet->LoadLanguage(languageFile, ctx.zip); + free(languageFile); + } + + // Load and parse the XML and all includes + currentLoadingContext = &ctx; // required to find styles + ret = mCurrentSet->Load(ctx, mainxmlfilename); + currentLoadingContext = NULL; + + if (ret == 0) { + mCurrentSet->SetPage(startpage); + mPageSets.insert(std::pair<std::string, PageSet*>(name, mCurrentSet)); + } else { + if (ret != TW_THEME_VER_ERR) + LOGERR("Package %s failed to load.\n", name.c_str()); + } + + // reset to previous pageset + mCurrentSet = pageSet; + + if (ctx.zip) { + ctx.zip->Close(); +#ifdef USE_MINZIP + sysReleaseMap(&map); +#endif + } + return ret; + +error: + // Sometimes we get here without a real error + if (ctx.zip) { + ctx.zip->Close(); +#ifdef USE_MINZIP + sysReleaseMap(&map); +#endif + } + return -1; +} + +PageSet* PageManager::FindPackage(std::string name) +{ + std::map<std::string, PageSet*>::iterator iter; + + iter = mPageSets.find(name); + if (iter != mPageSets.end()) + return (*iter).second; + + LOGERR("Unable to locate package %s\n", name.c_str()); + return NULL; +} + +PageSet* PageManager::SelectPackage(std::string name) +{ + LOGINFO("Switching packages (%s)\n", name.c_str()); + PageSet* tmp; + + tmp = FindPackage(name); + if (tmp) + { + mCurrentSet = tmp; + mCurrentSet->MakeEmergencyConsoleIfNeeded(); + mCurrentSet->NotifyVarChange("", ""); + } + else + LOGERR("Unable to find package.\n"); + + return mCurrentSet; +} + +int PageManager::ReloadPackage(std::string name, std::string package) +{ + std::map<std::string, PageSet*>::iterator iter; + + mReloadTheme = false; + + iter = mPageSets.find(name); + if (iter == mPageSets.end()) + return -1; + + if (mMouseCursor) + mMouseCursor->ResetData(gr_fb_width(), gr_fb_height()); + + PageSet* set = (*iter).second; + mPageSets.erase(iter); + + if (LoadPackage(name, package, mStartPage) != 0) + { + LOGINFO("Failed to load package '%s'.\n", package.c_str()); + mPageSets.insert(std::pair<std::string, PageSet*>(name, set)); + return -1; + } + if (mCurrentSet == set) + SelectPackage(name); + delete set; + GUIConsole::Translate_Now(); + return 0; +} + +void PageManager::ReleasePackage(std::string name) +{ + std::map<std::string, PageSet*>::iterator iter; + + iter = mPageSets.find(name); + if (iter == mPageSets.end()) + return; + + PageSet* set = (*iter).second; + mPageSets.erase(iter); + delete set; + if (set == mCurrentSet) + mCurrentSet = NULL; + return; +} + +int PageManager::RunReload() { + int ret_val = 0; + std::string theme_path; + + if (!mReloadTheme) + return 0; + + mReloadTheme = false; + theme_path = DataManager::GetSettingsStoragePath(); + if (PartitionManager.Mount_By_Path(theme_path.c_str(), 1) < 0) { + LOGERR("Unable to mount %s during gui_reload_theme function.\n", theme_path.c_str()); + ret_val = 1; + } + + theme_path += "/TWRP/theme/ui.zip"; + if (ret_val != 0 || ReloadPackage("TWRP", theme_path) != 0) + { + // Loading the custom theme failed - try loading the stock theme + LOGINFO("Attempting to reload stock theme...\n"); + if (ReloadPackage("TWRP", TWRES "ui.xml")) + { + LOGERR("Failed to load base packages.\n"); + ret_val = 1; + } + } + if (ret_val == 0) { + if (DataManager::GetStrValue("tw_language") != "en.xml") { + LOGINFO("Loading language '%s'\n", DataManager::GetStrValue("tw_language").c_str()); + LoadLanguage(DataManager::GetStrValue("tw_language")); + } + } + + // This makes the console re-translate + GUIConsole::Clear_For_Retranslation(); + + return ret_val; +} + +void PageManager::RequestReload() { + mReloadTheme = true; +} + +void PageManager::SetStartPage(const std::string& page_name) { + mStartPage = page_name; +} + +int PageManager::ChangePage(std::string name) +{ + DataManager::SetValue("tw_operation_state", 0); + int ret = (mCurrentSet ? mCurrentSet->SetPage(name) : -1); + return ret; +} + +std::string PageManager::GetCurrentPage() +{ + return mCurrentSet ? mCurrentSet->GetCurrentPage() : ""; +} + +int PageManager::ChangeOverlay(std::string name) +{ + if (name.empty()) + return mCurrentSet->SetOverlay(NULL); + else + { + Page* page = mCurrentSet ? mCurrentSet->FindPage(name) : NULL; + return mCurrentSet->SetOverlay(page); + } +} + +const ResourceManager* PageManager::GetResources() +{ + return (mCurrentSet ? mCurrentSet->GetResources() : NULL); +} + +int PageManager::IsCurrentPage(Page* page) +{ + return (mCurrentSet ? mCurrentSet->IsCurrentPage(page) : 0); +} + +int PageManager::Render(void) +{ + if (blankTimer.isScreenOff()) + return 0; + + int res = (mCurrentSet ? mCurrentSet->Render() : -1); + if (mMouseCursor) + mMouseCursor->Render(); + return res; +} + +HardwareKeyboard *PageManager::GetHardwareKeyboard() +{ + if (!mHardwareKeyboard) + mHardwareKeyboard = new HardwareKeyboard(); + return mHardwareKeyboard; +} + +xml_node<>* PageManager::FindStyle(std::string name) +{ + if (!currentLoadingContext) + { + LOGERR("FindStyle works only while loading a theme.\n"); + return NULL; + } + + for (std::vector<xml_node<>*>::iterator itr = currentLoadingContext->styles.begin(); itr != currentLoadingContext->styles.end(); itr++) { + xml_node<>* node = (*itr)->first_node("style"); + + while (node) { + if (!node->first_attribute("name")) + continue; + + if (name == node->first_attribute("name")->value()) + return node; + node = node->next_sibling("style"); + } + } + return NULL; +} + +MouseCursor *PageManager::GetMouseCursor() +{ + if (!mMouseCursor) + mMouseCursor = new MouseCursor(gr_fb_width(), gr_fb_height()); + return mMouseCursor; +} + +void PageManager::LoadCursorData(xml_node<>* node) +{ + if (!mMouseCursor) + mMouseCursor = new MouseCursor(gr_fb_width(), gr_fb_height()); + + mMouseCursor->LoadData(node); +} + +int PageManager::Update(void) +{ + if (blankTimer.isScreenOff()) + return 0; + + if (RunReload()) + return -2; + + int res = (mCurrentSet ? mCurrentSet->Update() : -1); + + if (mMouseCursor) + { + int c_res = mMouseCursor->Update(); + if (c_res > res) + res = c_res; + } + return res; +} + +int PageManager::NotifyTouch(TOUCH_STATE state, int x, int y) +{ + return (mCurrentSet ? mCurrentSet->NotifyTouch(state, x, y) : -1); +} + +int PageManager::NotifyKey(int key, bool down) +{ + return (mCurrentSet ? mCurrentSet->NotifyKey(key, down) : -1); +} + +int PageManager::NotifyCharInput(int ch) +{ + return (mCurrentSet ? mCurrentSet->NotifyCharInput(ch) : -1); +} + +int PageManager::SetKeyBoardFocus(int inFocus) +{ + return (mCurrentSet ? mCurrentSet->SetKeyBoardFocus(inFocus) : -1); +} + +int PageManager::NotifyVarChange(std::string varName, std::string value) +{ + return (mCurrentSet ? mCurrentSet->NotifyVarChange(varName, value) : -1); +} + +void PageManager::AddStringResource(std::string resource_source, std::string resource_name, std::string value) +{ + if (mCurrentSet) + mCurrentSet->AddStringResource(resource_source, resource_name, value); +} + +extern "C" void gui_notifyVarChange(const char *name, const char* value) +{ + if (!gGuiRunning) + return; + + PageManager::NotifyVarChange(name, value); +} |