/* Copyright 2015 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 . */ // console.cpp - GUIConsole object #include #include #include #include #include #include #include #include #include extern "C" { #include "../twcommon.h" } #include "../minuitwrp/minui.h" #include "rapidxml.hpp" #include "objects.hpp" #include "gui.hpp" #include "twmsg.h" #define GUI_CONSOLE_BUFFER_SIZE 512 static pthread_mutex_t console_lock; static size_t last_message_count = 0; static std::vector gMessages; static std::vector gConsole; static std::vector gConsoleColor; static FILE* ors_file = NULL; struct InitMutex { InitMutex() { pthread_mutex_init(&console_lock, NULL); } } initMutex; static void internal_gui_print(const char *color, char *buf) { // make sure to flush any outstanding messages first to preserve order of outputs GUIConsole::Translate_Now(); fputs(buf, stdout); if (ors_file) { fprintf(ors_file, "%s", buf); fflush(ors_file); } char *start, *next; if (buf[0] == '\n' && strlen(buf) < 2) { // This prevents the double lines bug seen in the console during zip installs return; } pthread_mutex_lock(&console_lock); for (start = next = buf; *next != '\0';) { if (*next == '\n') { *next = '\0'; gConsole.push_back(start); gConsoleColor.push_back(color); start = ++next; } else ++next; } // The text after last \n (or whole string if there is no \n) if (*start) { gConsole.push_back(start); gConsoleColor.push_back(color); } pthread_mutex_unlock(&console_lock); } extern "C" void gui_print(const char *fmt, ...) { char buf[GUI_CONSOLE_BUFFER_SIZE]; // We're going to limit a single request to 512 bytes va_list ap; va_start(ap, fmt); vsnprintf(buf, GUI_CONSOLE_BUFFER_SIZE, fmt, ap); va_end(ap); internal_gui_print("normal", buf); } extern "C" void gui_print_color(const char *color, const char *fmt, ...) { char buf[GUI_CONSOLE_BUFFER_SIZE]; // We're going to limit a single request to 512 bytes va_list ap; va_start(ap, fmt); vsnprintf(buf, GUI_CONSOLE_BUFFER_SIZE, fmt, ap); va_end(ap); internal_gui_print(color, buf); } extern "C" void gui_set_FILE(FILE* f) { ors_file = f; } void gui_msg(const char* text) { if (text) { Message msg = Msg(text); gui_msg(msg); } } void gui_warn(const char* text) { if (text) { Message msg = Msg(msg::kWarning, text); gui_msg(msg); } } void gui_err(const char* text) { if (text) { Message msg = Msg(msg::kError, text); gui_msg(msg); } } void gui_highlight(const char* text) { if (text) { Message msg = Msg(msg::kHighlight, text); gui_msg(msg); } } void gui_msg(Message msg) { std::string output = msg; output += "\n"; fputs(output.c_str(), stdout); if (ors_file) { fprintf(ors_file, "%s", output.c_str()); fflush(ors_file); } pthread_mutex_lock(&console_lock); gMessages.push_back(msg); pthread_mutex_unlock(&console_lock); } void GUIConsole::Translate_Now() { pthread_mutex_lock(&console_lock); size_t message_count = gMessages.size(); if (message_count <= last_message_count) { pthread_mutex_unlock(&console_lock); return; } for (size_t m = last_message_count; m < message_count; m++) { std::string message = gMessages[m]; std::string color = "normal"; if (gMessages[m].GetKind() == msg::kError) color = "error"; else if (gMessages[m].GetKind() == msg::kHighlight) color = "highlight"; else if (gMessages[m].GetKind() == msg::kWarning) color = "warning"; gConsole.push_back(message); gConsoleColor.push_back(color); } last_message_count = message_count; pthread_mutex_unlock(&console_lock); } void GUIConsole::Clear_For_Retranslation() { pthread_mutex_lock(&console_lock); last_message_count = 0; gConsole.clear(); gConsoleColor.clear(); pthread_mutex_unlock(&console_lock); } GUIConsole::GUIConsole(xml_node<>* node) : GUIScrollList(node) { xml_node<>* child; mLastCount = 0; scrollToEnd = true; mSlideoutX = mSlideoutY = mSlideoutW = mSlideoutH = 0; mSlideout = 0; mSlideoutState = visible; allowSelection = false; // console doesn't support list item selections if (!node) { mRenderX = 0; mRenderY = 0; mRenderW = gr_fb_width(); mRenderH = gr_fb_height(); } else { child = FindNode(node, "color"); if (child) { mFontColor = LoadAttrColor(child, "foreground", mFontColor); mBackgroundColor = LoadAttrColor(child, "background", mBackgroundColor); //mScrollColor = LoadAttrColor(child, "scroll", mScrollColor); } child = FindNode(node, "slideout"); if (child) { mSlideout = 1; mSlideoutState = hidden; LoadPlacement(child, &mSlideoutX, &mSlideoutY, &mSlideoutW, &mSlideoutH, &mPlacement); mSlideoutImage = LoadAttrImage(child, "resource"); if (mSlideoutImage && mSlideoutImage->GetResource()) { mSlideoutW = mSlideoutImage->GetWidth(); mSlideoutH = mSlideoutImage->GetHeight(); if (mPlacement == CENTER || mPlacement == CENTER_X_ONLY) { mSlideoutX = mSlideoutX - (mSlideoutW / 2); if (mPlacement == CENTER) { mSlideoutY = mSlideoutY - (mSlideoutH / 2); } } } } } } int GUIConsole::RenderSlideout(void) { if (!mSlideoutImage || !mSlideoutImage->GetResource()) return -1; gr_blit(mSlideoutImage->GetResource(), 0, 0, mSlideoutW, mSlideoutH, mSlideoutX, mSlideoutY); return 0; } int GUIConsole::RenderConsole(void) { Translate_Now(); pthread_mutex_lock(&console_lock); AddLines(&gConsole, &gConsoleColor, &mLastCount, &rConsole, &rConsoleColor); pthread_mutex_unlock(&console_lock); GUIScrollList::Render(); // if last line is fully visible, keep tracking the last line when new lines are added int bottom_offset = GetDisplayRemainder() - actualItemHeight; bool isAtBottom = firstDisplayedItem == (int)GetItemCount() - GetDisplayItemCount() - (bottom_offset != 0) && y_offset == bottom_offset; if (isAtBottom) scrollToEnd = true; #if 0 // debug - show if we are tracking the last line if (scrollToEnd) { gr_color(0,255,0,255); gr_fill(mRenderX+mRenderW-5, mRenderY+mRenderH-5, 5, 5); } else { gr_color(255,0,0,255); gr_fill(mRenderX+mRenderW-5, mRenderY+mRenderH-5, 5, 5); } #endif return (mSlideout ? RenderSlideout() : 0); } int GUIConsole::Render(void) { if (!isConditionTrue()) return 0; if (mSlideout && mSlideoutState == hidden) return RenderSlideout(); return RenderConsole(); } int GUIConsole::Update(void) { if (mSlideout && mSlideoutState != visible) { if (mSlideoutState == hidden) return 0; if (mSlideoutState == request_hide) mSlideoutState = hidden; if (mSlideoutState == request_show) mSlideoutState = visible; // Any time we activate the console, we reset the position SetVisibleListLocation(rConsole.size() - 1); mUpdate = 1; scrollToEnd = true; } pthread_mutex_lock(&console_lock); bool addedNewText = AddLines(&gConsole, &gConsoleColor, &mLastCount, &rConsole, &rConsoleColor); pthread_mutex_unlock(&console_lock); if (addedNewText) { // someone added new text // at least the scrollbar must be updated, even if the new lines are currently not visible mUpdate = 1; } if (scrollToEnd) { // keep the last line in view SetVisibleListLocation(rConsole.size() - 1); } GUIScrollList::Update(); if (mUpdate) { mUpdate = 0; if (Render() == 0) return 2; } return 0; } // IsInRegion - Checks if the request is handled by this object // Return 1 if this object handles the request, 0 if not int GUIConsole::IsInRegion(int x, int y) { if (mSlideout) { // Check if they tapped the slideout button if (x >= mSlideoutX && x < mSlideoutX + mSlideoutW && y >= mSlideoutY && y < mSlideoutY + mSlideoutH) return 1; // If we're only rendering the slideout, bail now if (mSlideoutState == hidden) return 0; } return GUIScrollList::IsInRegion(x, y); } // NotifyTouch - Notify of a touch event // Return 0 on success, >0 to ignore remainder of touch, and <0 on error int GUIConsole::NotifyTouch(TOUCH_STATE state, int x, int y) { if (!isConditionTrue()) return -1; if (mSlideout && x >= mSlideoutX && x < mSlideoutX + mSlideoutW && y >= mSlideoutY && y < mSlideoutY + mSlideoutH) { if (state == TOUCH_START) { if (mSlideoutState == hidden) mSlideoutState = request_show; else if (mSlideoutState == visible) mSlideoutState = request_hide; } return 1; } scrollToEnd = false; return GUIScrollList::NotifyTouch(state, x, y); } size_t GUIConsole::GetItemCount() { return rConsole.size(); } void GUIConsole::RenderItem(size_t itemindex, int yPos, bool selected __unused) { // Set the color for the font if (rConsoleColor[itemindex] == "normal") { gr_color(mFontColor.red, mFontColor.green, mFontColor.blue, mFontColor.alpha); } else { COLOR FontColor; std::string color = rConsoleColor[itemindex]; ConvertStrToColor(color, &FontColor); FontColor.alpha = 255; gr_color(FontColor.red, FontColor.green, FontColor.blue, FontColor.alpha); } // render text const char* text = rConsole[itemindex].c_str(); gr_textEx_scaleW(mRenderX, yPos, text, mFont->GetResource(), mRenderW, TOP_LEFT, 0); } void GUIConsole::NotifySelect(size_t item_selected __unused) { // do nothing - console ignores selections }