CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!
CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!
Path: blob/master/Common/Log/ConsoleListener.cpp
Views: 1401
// Copyright (C) 2003 Dolphin Project.12// This program is free software: you can redistribute it and/or modify3// it under the terms of the GNU General Public License as published by4// the Free Software Foundation, version 2.0 or later versions.56// This program is distributed in the hope that it will be useful,7// but WITHOUT ANY WARRANTY; without even the implied warranty of8// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the9// GNU General Public License 2.0 for more details.1011// A copy of the GPL 2.0 should have been included with the program.12// If not, see http://www.gnu.org/licenses/1314// Official SVN repository and contact information can be found at15// http://code.google.com/p/dolphin-emu/1617#include "ppsspp_config.h"1819#if PPSSPP_PLATFORM(WINDOWS) && !PPSSPP_PLATFORM(UWP)20#include <atomic>21#include <algorithm> // min22#include <array>23#include <cstring>24#include <string> // System: To be able to add strings with "+"25#include <math.h>26#include <process.h>27#include "Common/CommonWindows.h"2829#ifndef _MSC_VER30#include <unistd.h>31#endif3233#include "Common/Thread/ThreadUtil.h"34#include "Common/Data/Encoding/Utf8.h"35#include "Common/CommonTypes.h"36#include "Common/Log/ConsoleListener.h"37#include "Common/StringUtils.h"3839const int LOG_PENDING_MAX = 120 * 10000;40const int LOG_LATENCY_DELAY_MS = 20;41const int LOG_SHUTDOWN_DELAY_MS = 250;42const int LOG_MAX_DISPLAY_LINES = 4000;4344static bool g_Initialized;4546ConsoleListener::ConsoleListener() : hidden_(true) {47useColor_ = true;4849// useThread_ = false;5051if (useThread_ && !hTriggerEvent) {52hTriggerEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr);53InitializeCriticalSection(&criticalSection);54logPending_ = new char[LOG_PENDING_MAX];55}5657_dbg_assert_(!g_Initialized);58g_Initialized = true;5960logPendingReadPos_.store(0);61logPendingWritePos_.store(0);62}6364ConsoleListener::~ConsoleListener() {65g_Initialized = false;66Close();67}6869// Handle console event70bool WINAPI ConsoleHandler(DWORD msgType) {71if (msgType == CTRL_C_EVENT) {72OutputDebugString(L"Ctrl-C!\n");73return TRUE;74} else if (msgType == CTRL_CLOSE_EVENT) {75OutputDebugString(L"Close console window!\n");76return TRUE;77}78/*79Other messages:80CTRL_BREAK_EVENT Ctrl-Break pressed81CTRL_LOGOFF_EVENT User log off82CTRL_SHUTDOWN_EVENT System shutdown83*/84return FALSE;85}8687// Open console window - width and height is the size of console window88// Name is the window title89void ConsoleListener::Init(bool AutoOpen, int Width, int Height) {90openWidth_ = Width;91openHeight_ = Height;92if (AutoOpen) {93Open();94hidden_ = false;95}96}9798void ConsoleListener::Open() {99if (!GetConsoleWindow()) {100// Open the console window and create the window handle for GetStdHandle()101AllocConsole();102hWnd = GetConsoleWindow();103ShowWindow(hWnd, SW_SHOWDEFAULT);104// disable console close button105HMENU hMenu = GetSystemMenu(hWnd, false);106EnableMenuItem(hMenu,SC_CLOSE,MF_GRAYED|MF_BYCOMMAND);107// Save the window handle that AllocConsole() created108hConsole = GetStdHandle(STD_OUTPUT_HANDLE);109// Set console handler110if (SetConsoleCtrlHandler((PHANDLER_ROUTINE)ConsoleHandler, TRUE)) {111OutputDebugStringA("Console handler is installed!\n");112}113// Set the console window title114SetConsoleTitle(L"PPSSPP Debug Console");115SetConsoleCP(CP_UTF8);116SetConsoleOutputCP(CP_UTF8);117118// Set letter space119LetterSpace(openWidth_, LOG_MAX_DISPLAY_LINES);120//MoveWindow(GetConsoleWindow(), 200,200, 800,800, true);121} else {122hConsole = GetStdHandle(STD_OUTPUT_HANDLE);123}124125if (useThread_ && hTriggerEvent != NULL && !thread_.joinable()) {126thread_ = std::thread([&] {127SetCurrentThreadName("Console");128LogWriterThread();129});130}131}132133void ConsoleListener::Show(bool bShow) {134if (bShow && hidden_) {135if (!IsOpen()) {136Open();137}138ShowWindow(GetConsoleWindow(), SW_SHOW);139hidden_ = false;140} else if (!bShow && !hidden_) {141ShowWindow(GetConsoleWindow(), SW_HIDE);142hidden_ = true;143}144}145146void ConsoleListener::UpdateHandle() {147hConsole = GetStdHandle(STD_OUTPUT_HANDLE);148}149150// Close the console window and close the eventual file handle151void ConsoleListener::Close() {152if (thread_.joinable()) {153logPendingWritePos_.store((u32)-1, std::memory_order_release);154SetEvent(hTriggerEvent);155thread_.join();156}157if (hTriggerEvent) {158DeleteCriticalSection(&criticalSection);159CloseHandle(hTriggerEvent);160hTriggerEvent = nullptr;161}162if (logPending_) {163delete [] logPending_;164logPending_ = nullptr;165}166167if (hConsole) {168FreeConsole();169hConsole = nullptr;170}171}172173bool ConsoleListener::IsOpen() {174return (hConsole != nullptr);175}176177// LetterSpace: SetConsoleScreenBufferSize and SetConsoleWindowInfo are178// dependent on each other, that's the reason for the additional checks.179void ConsoleListener::BufferWidthHeight(int BufferWidth, int BufferHeight, int ScreenWidth, int ScreenHeight, bool BufferFirst) {180_dbg_assert_msg_(IsOpen(), "Don't call this before opening the console.");181BOOL SB, SW;182if (BufferFirst) {183// Change screen buffer size184COORD Co = {(SHORT)BufferWidth, (SHORT)BufferHeight};185SB = SetConsoleScreenBufferSize(hConsole, Co);186// Change the screen buffer window size187SMALL_RECT coo = {(SHORT)0, (SHORT)0, (SHORT)ScreenWidth, (SHORT)ScreenHeight}; // top, left, right, bottom188SW = SetConsoleWindowInfo(hConsole, TRUE, &coo);189} else {190// Change the screen buffer window size191SMALL_RECT coo = {(SHORT)0, (SHORT)0, (SHORT)ScreenWidth, (SHORT)ScreenHeight}; // top, left, right, bottom192SW = SetConsoleWindowInfo(hConsole, TRUE, &coo);193// Change screen buffer size194COORD Co = {(SHORT)BufferWidth, (SHORT)BufferHeight};195SB = SetConsoleScreenBufferSize(hConsole, Co);196}197}198199void ConsoleListener::LetterSpace(int Width, int Height) {200_dbg_assert_msg_(IsOpen(), "Don't call this before opening the console.");201// Get console info202CONSOLE_SCREEN_BUFFER_INFO ConInfo;203GetConsoleScreenBufferInfo(hConsole, &ConInfo);204205int OldBufferWidth = ConInfo.dwSize.X;206int OldBufferHeight = ConInfo.dwSize.Y;207int OldScreenWidth = (ConInfo.srWindow.Right - ConInfo.srWindow.Left);208int OldScreenHeight = (ConInfo.srWindow.Bottom - ConInfo.srWindow.Top);209210int NewBufferWidth = Width;211int NewBufferHeight = Height;212int NewScreenWidth = NewBufferWidth - 1;213int NewScreenHeight = OldScreenHeight;214215// Width216BufferWidthHeight(NewBufferWidth, OldBufferHeight, NewScreenWidth, OldScreenHeight, (NewBufferWidth > OldScreenWidth-1));217// Height218BufferWidthHeight(NewBufferWidth, NewBufferHeight, NewScreenWidth, NewScreenHeight, (NewBufferHeight > OldScreenHeight-1));219220// Resize the window too221// MoveWindow(GetConsoleWindow(), 200,200, (Width*8 + 50),(NewScreenHeight*12 + 200), true);222}223224COORD ConsoleListener::GetCoordinates(int BytesRead, int BufferWidth) {225COORD Ret = {0, 0};226// Full rows227int Step = (int)floor((float)BytesRead / (float)BufferWidth);228Ret.Y += Step;229// Partial row230Ret.X = BytesRead - (BufferWidth * Step);231return Ret;232}233234void ConsoleListener::LogWriterThread() {235char *logLocal = new char[LOG_PENDING_MAX];236int logLocalSize = 0;237238while (true) {239WaitForSingleObject(hTriggerEvent, INFINITE);240Sleep(LOG_LATENCY_DELAY_MS);241242u32 logRemotePos = logPendingWritePos_.load(std::memory_order_acquire);243if (logRemotePos == (u32)-1) {244break;245} else if (logRemotePos == logPendingReadPos_) {246continue;247} else {248EnterCriticalSection(&criticalSection);249logRemotePos = logPendingWritePos_.load(std::memory_order_acquire);250251int start = 0;252if (logRemotePos < logPendingReadPos_) {253const int count = LOG_PENDING_MAX - logPendingReadPos_;254memcpy(logLocal + start, logPending_ + logPendingReadPos_, count);255256start = count;257logPendingReadPos_ = 0;258}259260const int count = logRemotePos - logPendingReadPos_;261memcpy(logLocal + start, logPending_ + logPendingReadPos_, count);262263logPendingReadPos_ += count;264LeaveCriticalSection(&criticalSection);265266// Double check.267if (logPendingWritePos_ == (u32)-1) {268break;269}270271logLocalSize = start + count;272}273274for (char *Text = logLocal, *End = logLocal + logLocalSize; Text < End; ) {275LogLevel Level = LogLevel::LINFO;276277char *next = (char *) memchr(Text + 1, '\033', End - Text);278size_t Len = next - Text;279if (!next) {280Len = End - Text;281}282283if (Text[0] == '\033' && Text + 1 < End) {284Level = (LogLevel)(Text[1] - '0');285Len -= 2;286Text += 2;287}288289// Make sure we didn't start quitting. This is kinda slow.290if (logPendingWritePos_ == (u32)-1) {291break;292}293294WriteToConsole(Level, Text, Len);295Text += Len;296}297}298299delete [] logLocal;300}301302void ConsoleListener::SendToThread(LogLevel Level, const char *Text) {303// Oops, we're already quitting. Just do nothing.304if (logPendingWritePos_ == (u32)-1) {305return;306}307308int Len = (int)strlen(Text);309if (Len > LOG_PENDING_MAX)310Len = LOG_PENDING_MAX - 16;311312char ColorAttr[16] = "";313int ColorLen = 0;314if (useColor_) {315// Not ANSI, since the console doesn't support it, but ANSI-like.316snprintf(ColorAttr, 16, "\033%d", Level);317// For now, rather than properly support it.318_dbg_assert_msg_(strlen(ColorAttr) == 2, "Console logging doesn't support > 9 levels.");319ColorLen = (int)strlen(ColorAttr);320}321322EnterCriticalSection(&criticalSection);323u32 logWritePos = logPendingWritePos_.load();324u32 prevLogWritePos = logWritePos;325if (logWritePos + ColorLen + Len >= LOG_PENDING_MAX) {326for (int i = 0; i < ColorLen; ++i) {327logPending_[(logWritePos + i) % LOG_PENDING_MAX] = ColorAttr[i];328}329logWritePos += ColorLen;330if (logWritePos >= LOG_PENDING_MAX)331logWritePos -= LOG_PENDING_MAX;332333int start = 0;334if (logWritePos < LOG_PENDING_MAX && logWritePos + Len >= LOG_PENDING_MAX) {335const int count = LOG_PENDING_MAX - logWritePos;336memcpy(logPending_ + logWritePos, Text, count);337start = count;338logWritePos = 0;339}340const int count = Len - start;341if (count > 0) {342memcpy(logPending_ + logWritePos, Text + start, count);343logWritePos += count;344}345} else {346memcpy(logPending_ + logWritePos, ColorAttr, ColorLen);347memcpy(logPending_ + logWritePos + ColorLen, Text, Len);348logWritePos += ColorLen + Len;349}350351// Oops, we passed the read pos.352if (prevLogWritePos < logPendingReadPos_ && logWritePos >= logPendingReadPos_) {353char *nextNewline = (char *) memchr(logPending_ + logWritePos, '\n', LOG_PENDING_MAX - logWritePos);354if (nextNewline == NULL && logWritePos > 0)355nextNewline = (char *) memchr(logPending_, '\n', logWritePos);356357// Okay, have it go right after the next newline.358if (nextNewline != NULL)359logPendingReadPos_ = (u32)(nextNewline - logPending_ + 1);360}361362// Double check we didn't start quitting.363if (logPendingWritePos_ == (u32) -1) {364LeaveCriticalSection(&criticalSection);365return;366}367368logPendingWritePos_.store(logWritePos, std::memory_order::memory_order_release);369LeaveCriticalSection(&criticalSection);370371SetEvent(hTriggerEvent);372}373374void ConsoleListener::WriteToConsole(LogLevel Level, const char *Text, size_t Len) {375_dbg_assert_msg_(IsOpen(), "Don't call this before opening the console.");376377/*378const int MAX_BYTES = 1024*10;379char Str[MAX_BYTES];380va_list ArgPtr;381int Cnt;382va_start(ArgPtr, Text);383Cnt = vsnprintf(Str, MAX_BYTES, Text, ArgPtr);384va_end(ArgPtr);385*/386DWORD cCharsWritten;387WORD Color;388static wchar_t tempBuf[2048];389390switch (Level) {391case LogLevel::LNOTICE: // light green392Color = FOREGROUND_GREEN | FOREGROUND_INTENSITY;393break;394case LogLevel::LERROR: // light red395Color = FOREGROUND_RED | FOREGROUND_INTENSITY;396break;397case LogLevel::LWARNING: // light yellow398Color = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY;399break;400case LogLevel::LINFO: // cyan401Color = FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_INTENSITY;402break;403case LogLevel::LDEBUG: // gray404Color = FOREGROUND_INTENSITY;405break;406default: // off-white407Color = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE;408break;409}410if (Len > 10) {411// First 10 chars white412SetConsoleTextAttribute(hConsole, FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_INTENSITY);413int wlen = MultiByteToWideChar(CP_UTF8, 0, Text, (int)Len, NULL, NULL);414MultiByteToWideChar(CP_UTF8, 0, Text, (int)Len, tempBuf, wlen);415WriteConsole(hConsole, tempBuf, 10, &cCharsWritten, NULL);416Text += 10;417Len -= 10;418}419SetConsoleTextAttribute(hConsole, Color);420int wlen = MultiByteToWideChar(CP_UTF8, 0, Text, (int)Len, NULL, NULL);421MultiByteToWideChar(CP_UTF8, 0, Text, (int)Len, tempBuf, wlen);422WriteConsole(hConsole, tempBuf, (DWORD)wlen, &cCharsWritten, NULL);423}424425void ConsoleListener::PixelSpace(int Left, int Top, int Width, int Height, bool Resize) {426_dbg_assert_msg_(IsOpen(), "Don't call this before opening the console.");427// Check size428if (Width < 8 || Height < 12) return;429430std::string SLog = "";431432// Get console info433CONSOLE_SCREEN_BUFFER_INFO ConInfo;434GetConsoleScreenBufferInfo(hConsole, &ConInfo);435DWORD BufferSize = ConInfo.dwSize.X * ConInfo.dwSize.Y;436437// ---------------------------------------------------------------------438// Save the current text439// ------------------------440DWORD cCharsRead = 0;441COORD coordScreen = { 0, 0 };442443static const int MAX_BYTES = 1024 * 16;444445std::vector<std::array<wchar_t, MAX_BYTES>> Str;446std::vector<std::array<WORD, MAX_BYTES>> Attr;447448// ReadConsoleOutputAttribute seems to have a limit at this level449static const int ReadBufferSize = MAX_BYTES - 32;450451DWORD cAttrRead = ReadBufferSize;452DWORD BytesRead = 0;453while (BytesRead < BufferSize) {454Str.resize(Str.size() + 1);455if (!ReadConsoleOutputCharacter(hConsole, Str.back().data(), ReadBufferSize, coordScreen, &cCharsRead))456SLog += StringFromFormat("WriteConsoleOutputCharacter error");457458Attr.resize(Attr.size() + 1);459if (!ReadConsoleOutputAttribute(hConsole, Attr.back().data(), ReadBufferSize, coordScreen, &cAttrRead))460SLog += StringFromFormat("WriteConsoleOutputAttribute error");461462// Break on error463if (cAttrRead == 0) break;464BytesRead += cAttrRead;465coordScreen = GetCoordinates(BytesRead, ConInfo.dwSize.X);466}467// Letter space468int LWidth = (int)(floor((float)Width / 8.0f) - 1.0f);469int LHeight = (int)(floor((float)Height / 12.0f) - 1.0f);470int LBufWidth = LWidth + 1;471int LBufHeight = (int)floor((float)BufferSize / (float)LBufWidth);472// Change screen buffer size473LetterSpace(LBufWidth, LBufHeight);474475476ClearScreen(true);477coordScreen.Y = 0;478coordScreen.X = 0;479DWORD cCharsWritten = 0;480481int BytesWritten = 0;482DWORD cAttrWritten = 0;483for (size_t i = 0; i < Attr.size(); i++) {484if (!WriteConsoleOutputCharacter(hConsole, Str[i].data(), ReadBufferSize, coordScreen, &cCharsWritten))485SLog += StringFromFormat("WriteConsoleOutputCharacter error");486if (!WriteConsoleOutputAttribute(hConsole, Attr[i].data(), ReadBufferSize, coordScreen, &cAttrWritten))487SLog += StringFromFormat("WriteConsoleOutputAttribute error");488489BytesWritten += cAttrWritten;490coordScreen = GetCoordinates(BytesWritten, LBufWidth);491}492493const int OldCursor = ConInfo.dwCursorPosition.Y * ConInfo.dwSize.X + ConInfo.dwCursorPosition.X;494COORD Coo = GetCoordinates(OldCursor, LBufWidth);495SetConsoleCursorPosition(hConsole, Coo);496497// if (SLog.length() > 0) Log(LogLevel::LNOTICE, SLog.c_str());498499// Resize the window too500if (Resize) {501MoveWindow(GetConsoleWindow(), Left, Top, (Width + 100), Height, true);502}503}504505void ConsoleListener::Log(const LogMessage &msg) {506char buf[2048];507snprintf(buf, sizeof(buf), "%s %s %s", msg.timestamp, msg.header, msg.msg.c_str());508buf[sizeof(buf) - 2] = '\n';509buf[sizeof(buf) - 1] = '\0';510511if (!useThread_ && IsOpen())512WriteToConsole(msg.level, buf, strlen(buf));513else514SendToThread(msg.level, buf);515}516517// Clear console screen518void ConsoleListener::ClearScreen(bool Cursor) {519_dbg_assert_msg_(IsOpen(), "Don't call this before opening the console.");520521CONSOLE_SCREEN_BUFFER_INFO csbi;522GetConsoleScreenBufferInfo(hConsole, &csbi);523DWORD dwConSize = csbi.dwSize.X * csbi.dwSize.Y;524// Write space to the entire console525DWORD cCharsWritten;526COORD coordScreen = { 0, 0 };527FillConsoleOutputCharacter(hConsole, TEXT(' '), dwConSize, coordScreen, &cCharsWritten);528GetConsoleScreenBufferInfo(hConsole, &csbi);529FillConsoleOutputAttribute(hConsole, csbi.wAttributes, dwConSize, coordScreen, &cCharsWritten);530// Reset cursor531if (Cursor) {532SetConsoleCursorPosition(hConsole, coordScreen);533}534}535536#endif // PPSSPP_PLATFORM(WINDOWS)537538539