Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
hrydgard
GitHub Repository: hrydgard/ppsspp
Path: blob/master/Common/Log/ConsoleListener.cpp
3187 views
1
// Copyright (C) 2003 Dolphin Project.
2
3
// This program is free software: you can redistribute it and/or modify
4
// it under the terms of the GNU General Public License as published by
5
// the Free Software Foundation, version 2.0 or later versions.
6
7
// This program is distributed in the hope that it will be useful,
8
// but WITHOUT ANY WARRANTY; without even the implied warranty of
9
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10
// GNU General Public License 2.0 for more details.
11
12
// A copy of the GPL 2.0 should have been included with the program.
13
// If not, see http://www.gnu.org/licenses/
14
15
// Official SVN repository and contact information can be found at
16
// http://code.google.com/p/dolphin-emu/
17
18
#include "ppsspp_config.h"
19
20
#if PPSSPP_PLATFORM(WINDOWS) && !PPSSPP_PLATFORM(UWP)
21
#include <atomic>
22
#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"
28
29
#ifndef _MSC_VER
30
#include <unistd.h>
31
#endif
32
33
#include "Common/Thread/ThreadUtil.h"
34
#include "Common/Data/Encoding/Utf8.h"
35
#include "Common/CommonTypes.h"
36
#include "Common/Log/LogManager.h"
37
#include "Common/Log/ConsoleListener.h"
38
#include "Common/StringUtils.h"
39
40
const int LOG_PENDING_MAX = 120 * 10000;
41
const int LOG_LATENCY_DELAY_MS = 20;
42
const int LOG_SHUTDOWN_DELAY_MS = 250;
43
const int LOG_MAX_DISPLAY_LINES = 4000;
44
45
static bool g_Initialized;
46
47
ConsoleListener::ConsoleListener() : hidden_(true) {
48
useColor_ = true;
49
50
// useThread_ = false;
51
52
if (useThread_ && !hTriggerEvent) {
53
hTriggerEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr);
54
InitializeCriticalSection(&criticalSection);
55
logPending_ = new char[LOG_PENDING_MAX];
56
}
57
58
_dbg_assert_(!g_Initialized);
59
g_Initialized = true;
60
61
logPendingReadPos_.store(0);
62
logPendingWritePos_.store(0);
63
}
64
65
ConsoleListener::~ConsoleListener() {
66
g_Initialized = false;
67
Close();
68
}
69
70
// Handle console event
71
bool WINAPI ConsoleHandler(DWORD msgType) {
72
if (msgType == CTRL_C_EVENT) {
73
OutputDebugString(L"Ctrl-C!\n");
74
return TRUE;
75
} else if (msgType == CTRL_CLOSE_EVENT) {
76
OutputDebugString(L"Close console window!\n");
77
return TRUE;
78
}
79
/*
80
Other messages:
81
CTRL_BREAK_EVENT Ctrl-Break pressed
82
CTRL_LOGOFF_EVENT User log off
83
CTRL_SHUTDOWN_EVENT System shutdown
84
*/
85
return FALSE;
86
}
87
88
// Open console window - width and height is the size of console window
89
// Name is the window title
90
void ConsoleListener::Init(bool AutoOpen, int Width, int Height) {
91
openWidth_ = Width;
92
openHeight_ = Height;
93
if (AutoOpen) {
94
Open();
95
hidden_ = false;
96
}
97
}
98
99
void ConsoleListener::Open() {
100
if (!GetConsoleWindow()) {
101
// Open the console window and create the window handle for GetStdHandle()
102
AllocConsole();
103
hWnd = GetConsoleWindow();
104
ShowWindow(hWnd, SW_SHOWDEFAULT);
105
// disable console close button
106
HMENU hMenu = GetSystemMenu(hWnd, false);
107
EnableMenuItem(hMenu,SC_CLOSE,MF_GRAYED|MF_BYCOMMAND);
108
// Save the window handle that AllocConsole() created
109
hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
110
// Set console handler
111
if (SetConsoleCtrlHandler((PHANDLER_ROUTINE)ConsoleHandler, TRUE)) {
112
OutputDebugStringA("Console handler is installed!\n");
113
}
114
// Set the console window title
115
SetConsoleTitle(L"PPSSPP Debug Console");
116
SetConsoleCP(CP_UTF8);
117
SetConsoleOutputCP(CP_UTF8);
118
119
// Set letter space
120
LetterSpace(openWidth_, LOG_MAX_DISPLAY_LINES);
121
//MoveWindow(GetConsoleWindow(), 200,200, 800,800, true);
122
} else {
123
hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
124
}
125
126
if (useThread_ && hTriggerEvent != NULL && !thread_.joinable()) {
127
thread_ = std::thread([&] {
128
SetCurrentThreadName("Console");
129
LogWriterThread();
130
});
131
}
132
}
133
134
void ConsoleListener::Show(bool bShow) {
135
if (bShow && hidden_) {
136
if (!IsOpen()) {
137
Open();
138
}
139
ShowWindow(GetConsoleWindow(), SW_SHOW);
140
hidden_ = false;
141
} else if (!bShow && !hidden_) {
142
ShowWindow(GetConsoleWindow(), SW_HIDE);
143
hidden_ = true;
144
}
145
}
146
147
void ConsoleListener::UpdateHandle() {
148
hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
149
}
150
151
// Close the console window and close the eventual file handle
152
void ConsoleListener::Close() {
153
if (thread_.joinable()) {
154
logPendingWritePos_.store((u32)-1, std::memory_order_release);
155
SetEvent(hTriggerEvent);
156
// If we seem hung here, it's just that you made a selection in the debug console, blocking output.
157
thread_.join();
158
}
159
if (hTriggerEvent) {
160
DeleteCriticalSection(&criticalSection);
161
CloseHandle(hTriggerEvent);
162
hTriggerEvent = nullptr;
163
}
164
if (logPending_) {
165
delete [] logPending_;
166
logPending_ = nullptr;
167
}
168
169
if (hConsole) {
170
FreeConsole();
171
hConsole = nullptr;
172
}
173
}
174
175
bool ConsoleListener::IsOpen() {
176
return (hConsole != nullptr);
177
}
178
179
// LetterSpace: SetConsoleScreenBufferSize and SetConsoleWindowInfo are
180
// dependent on each other, that's the reason for the additional checks.
181
void ConsoleListener::BufferWidthHeight(int BufferWidth, int BufferHeight, int ScreenWidth, int ScreenHeight, bool BufferFirst) {
182
_dbg_assert_msg_(IsOpen(), "Don't call this before opening the console.");
183
BOOL SB, SW;
184
if (BufferFirst) {
185
// Change screen buffer size
186
COORD Co = {(SHORT)BufferWidth, (SHORT)BufferHeight};
187
SB = SetConsoleScreenBufferSize(hConsole, Co);
188
// Change the screen buffer window size
189
SMALL_RECT coo = {(SHORT)0, (SHORT)0, (SHORT)ScreenWidth, (SHORT)ScreenHeight}; // top, left, right, bottom
190
SW = SetConsoleWindowInfo(hConsole, TRUE, &coo);
191
} else {
192
// Change the screen buffer window size
193
SMALL_RECT coo = {(SHORT)0, (SHORT)0, (SHORT)ScreenWidth, (SHORT)ScreenHeight}; // top, left, right, bottom
194
SW = SetConsoleWindowInfo(hConsole, TRUE, &coo);
195
// Change screen buffer size
196
COORD Co = {(SHORT)BufferWidth, (SHORT)BufferHeight};
197
SB = SetConsoleScreenBufferSize(hConsole, Co);
198
}
199
}
200
201
void ConsoleListener::LetterSpace(int Width, int Height) {
202
_dbg_assert_msg_(IsOpen(), "Don't call this before opening the console.");
203
// Get console info
204
CONSOLE_SCREEN_BUFFER_INFO ConInfo;
205
GetConsoleScreenBufferInfo(hConsole, &ConInfo);
206
207
int OldBufferWidth = ConInfo.dwSize.X;
208
int OldBufferHeight = ConInfo.dwSize.Y;
209
int OldScreenWidth = (ConInfo.srWindow.Right - ConInfo.srWindow.Left);
210
int OldScreenHeight = (ConInfo.srWindow.Bottom - ConInfo.srWindow.Top);
211
212
int NewBufferWidth = Width;
213
int NewBufferHeight = Height;
214
int NewScreenWidth = NewBufferWidth - 1;
215
int NewScreenHeight = OldScreenHeight;
216
217
// Width
218
BufferWidthHeight(NewBufferWidth, OldBufferHeight, NewScreenWidth, OldScreenHeight, (NewBufferWidth > OldScreenWidth-1));
219
// Height
220
BufferWidthHeight(NewBufferWidth, NewBufferHeight, NewScreenWidth, NewScreenHeight, (NewBufferHeight > OldScreenHeight-1));
221
222
// Resize the window too
223
// MoveWindow(GetConsoleWindow(), 200,200, (Width*8 + 50),(NewScreenHeight*12 + 200), true);
224
}
225
226
COORD ConsoleListener::GetCoordinates(int BytesRead, int BufferWidth) {
227
COORD Ret = {0, 0};
228
// Full rows
229
int Step = (int)floor((float)BytesRead / (float)BufferWidth);
230
Ret.Y += Step;
231
// Partial row
232
Ret.X = BytesRead - (BufferWidth * Step);
233
return Ret;
234
}
235
236
void ConsoleListener::LogWriterThread() {
237
char *logLocal = new char[LOG_PENDING_MAX];
238
int logLocalSize = 0;
239
240
while (true) {
241
WaitForSingleObject(hTriggerEvent, INFINITE);
242
Sleep(LOG_LATENCY_DELAY_MS);
243
244
u32 logRemotePos = logPendingWritePos_.load(std::memory_order_acquire);
245
if (logRemotePos == (u32)-1) {
246
break;
247
} else if (logRemotePos == logPendingReadPos_) {
248
continue;
249
} else {
250
EnterCriticalSection(&criticalSection);
251
logRemotePos = logPendingWritePos_.load(std::memory_order_acquire);
252
253
int start = 0;
254
if (logRemotePos < logPendingReadPos_) {
255
const int count = LOG_PENDING_MAX - logPendingReadPos_;
256
memcpy(logLocal + start, logPending_ + logPendingReadPos_, count);
257
258
start = count;
259
logPendingReadPos_ = 0;
260
}
261
262
const int count = logRemotePos - logPendingReadPos_;
263
memcpy(logLocal + start, logPending_ + logPendingReadPos_, count);
264
265
logPendingReadPos_ += count;
266
LeaveCriticalSection(&criticalSection);
267
268
// Double check.
269
if (logPendingWritePos_ == (u32)-1) {
270
break;
271
}
272
273
logLocalSize = start + count;
274
}
275
276
for (char *Text = logLocal, *End = logLocal + logLocalSize; Text < End; ) {
277
LogLevel Level = LogLevel::LINFO;
278
279
char *next = (char *) memchr(Text + 1, '\033', End - Text);
280
size_t Len = next - Text;
281
if (!next) {
282
Len = End - Text;
283
}
284
285
if (Text[0] == '\033' && Text + 1 < End) {
286
Level = (LogLevel)(Text[1] - '0');
287
Len -= 2;
288
Text += 2;
289
}
290
291
// Make sure we didn't start quitting. This is kinda slow.
292
if (logPendingWritePos_ == (u32)-1) {
293
break;
294
}
295
296
WriteToConsole(Level, Text, Len);
297
Text += Len;
298
}
299
}
300
301
delete [] logLocal;
302
}
303
304
void ConsoleListener::SendToThread(LogLevel Level, const char *Text) {
305
// Oops, we're already quitting. Just do nothing.
306
if (logPendingWritePos_ == (u32)-1) {
307
return;
308
}
309
310
int Len = (int)strlen(Text);
311
if (Len > LOG_PENDING_MAX)
312
Len = LOG_PENDING_MAX - 16;
313
314
char ColorAttr[16] = "";
315
int ColorLen = 0;
316
if (useColor_) {
317
// Not ANSI, since the console doesn't support it, but ANSI-like.
318
snprintf(ColorAttr, 16, "\033%d", Level);
319
// For now, rather than properly support it.
320
_dbg_assert_msg_(strlen(ColorAttr) == 2, "Console logging doesn't support > 9 levels.");
321
ColorLen = (int)strlen(ColorAttr);
322
}
323
324
EnterCriticalSection(&criticalSection);
325
u32 logWritePos = logPendingWritePos_.load();
326
u32 prevLogWritePos = logWritePos;
327
if (logWritePos + ColorLen + Len >= LOG_PENDING_MAX) {
328
for (int i = 0; i < ColorLen; ++i) {
329
logPending_[(logWritePos + i) % LOG_PENDING_MAX] = ColorAttr[i];
330
}
331
logWritePos += ColorLen;
332
if (logWritePos >= LOG_PENDING_MAX)
333
logWritePos -= LOG_PENDING_MAX;
334
335
int start = 0;
336
if (logWritePos < LOG_PENDING_MAX && logWritePos + Len >= LOG_PENDING_MAX) {
337
const int count = LOG_PENDING_MAX - logWritePos;
338
memcpy(logPending_ + logWritePos, Text, count);
339
start = count;
340
logWritePos = 0;
341
}
342
const int count = Len - start;
343
if (count > 0) {
344
memcpy(logPending_ + logWritePos, Text + start, count);
345
logWritePos += count;
346
}
347
} else {
348
memcpy(logPending_ + logWritePos, ColorAttr, ColorLen);
349
memcpy(logPending_ + logWritePos + ColorLen, Text, Len);
350
logWritePos += ColorLen + Len;
351
}
352
353
// Oops, we passed the read pos.
354
if (prevLogWritePos < logPendingReadPos_ && logWritePos >= logPendingReadPos_) {
355
char *nextNewline = (char *) memchr(logPending_ + logWritePos, '\n', LOG_PENDING_MAX - logWritePos);
356
if (nextNewline == NULL && logWritePos > 0)
357
nextNewline = (char *) memchr(logPending_, '\n', logWritePos);
358
359
// Okay, have it go right after the next newline.
360
if (nextNewline != NULL)
361
logPendingReadPos_ = (u32)(nextNewline - logPending_ + 1);
362
}
363
364
// Double check we didn't start quitting.
365
if (logPendingWritePos_ == (u32) -1) {
366
LeaveCriticalSection(&criticalSection);
367
return;
368
}
369
370
logPendingWritePos_.store(logWritePos, std::memory_order::memory_order_release);
371
LeaveCriticalSection(&criticalSection);
372
373
SetEvent(hTriggerEvent);
374
}
375
376
void ConsoleListener::WriteToConsole(LogLevel Level, const char *Text, size_t Len) {
377
_dbg_assert_msg_(IsOpen(), "Don't call this before opening the console.");
378
379
/*
380
const int MAX_BYTES = 1024*10;
381
char Str[MAX_BYTES];
382
va_list ArgPtr;
383
int Cnt;
384
va_start(ArgPtr, Text);
385
Cnt = vsnprintf(Str, MAX_BYTES, Text, ArgPtr);
386
va_end(ArgPtr);
387
*/
388
DWORD cCharsWritten;
389
WORD Color;
390
static wchar_t tempBuf[2048];
391
392
switch (Level) {
393
case LogLevel::LNOTICE: // light green
394
Color = FOREGROUND_GREEN | FOREGROUND_INTENSITY;
395
break;
396
case LogLevel::LERROR: // light red
397
Color = FOREGROUND_RED | FOREGROUND_INTENSITY;
398
break;
399
case LogLevel::LWARNING: // light yellow
400
Color = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY;
401
break;
402
case LogLevel::LINFO: // cyan
403
Color = FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_INTENSITY;
404
break;
405
case LogLevel::LDEBUG: // gray
406
Color = FOREGROUND_INTENSITY;
407
break;
408
default: // off-white
409
Color = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE;
410
break;
411
}
412
if (Len > 10) {
413
// First 10 chars white
414
SetConsoleTextAttribute(hConsole, FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_INTENSITY);
415
int wlen = MultiByteToWideChar(CP_UTF8, 0, Text, (int)Len, NULL, 0);
416
MultiByteToWideChar(CP_UTF8, 0, Text, (int)Len, tempBuf, wlen);
417
WriteConsole(hConsole, tempBuf, 10, &cCharsWritten, NULL);
418
Text += 10;
419
Len -= 10;
420
}
421
SetConsoleTextAttribute(hConsole, Color);
422
int wlen = MultiByteToWideChar(CP_UTF8, 0, Text, (int)Len, NULL, 0);
423
MultiByteToWideChar(CP_UTF8, 0, Text, (int)Len, tempBuf, wlen);
424
WriteConsole(hConsole, tempBuf, (DWORD)wlen, &cCharsWritten, NULL);
425
}
426
427
void ConsoleListener::PixelSpace(int Left, int Top, int Width, int Height, bool Resize) {
428
_dbg_assert_msg_(IsOpen(), "Don't call this before opening the console.");
429
// Check size
430
if (Width < 8 || Height < 12) return;
431
432
std::string SLog = "";
433
434
// Get console info
435
CONSOLE_SCREEN_BUFFER_INFO ConInfo;
436
GetConsoleScreenBufferInfo(hConsole, &ConInfo);
437
DWORD BufferSize = ConInfo.dwSize.X * ConInfo.dwSize.Y;
438
439
// ---------------------------------------------------------------------
440
// Save the current text
441
// ------------------------
442
DWORD cCharsRead = 0;
443
COORD coordScreen = { 0, 0 };
444
445
static const int MAX_BYTES = 1024 * 16;
446
447
std::vector<std::array<wchar_t, MAX_BYTES>> Str;
448
std::vector<std::array<WORD, MAX_BYTES>> Attr;
449
450
// ReadConsoleOutputAttribute seems to have a limit at this level
451
static const int ReadBufferSize = MAX_BYTES - 32;
452
453
DWORD cAttrRead = ReadBufferSize;
454
DWORD BytesRead = 0;
455
while (BytesRead < BufferSize) {
456
Str.resize(Str.size() + 1);
457
if (!ReadConsoleOutputCharacter(hConsole, Str.back().data(), ReadBufferSize, coordScreen, &cCharsRead))
458
SLog += StringFromFormat("WriteConsoleOutputCharacter error");
459
460
Attr.resize(Attr.size() + 1);
461
if (!ReadConsoleOutputAttribute(hConsole, Attr.back().data(), ReadBufferSize, coordScreen, &cAttrRead))
462
SLog += StringFromFormat("WriteConsoleOutputAttribute error");
463
464
// Break on error
465
if (cAttrRead == 0) break;
466
BytesRead += cAttrRead;
467
coordScreen = GetCoordinates(BytesRead, ConInfo.dwSize.X);
468
}
469
// Letter space
470
int LWidth = (int)(floor((float)Width / 8.0f) - 1.0f);
471
int LHeight = (int)(floor((float)Height / 12.0f) - 1.0f);
472
int LBufWidth = LWidth + 1;
473
int LBufHeight = (int)floor((float)BufferSize / (float)LBufWidth);
474
// Change screen buffer size
475
LetterSpace(LBufWidth, LBufHeight);
476
477
478
ClearScreen(true);
479
coordScreen.Y = 0;
480
coordScreen.X = 0;
481
DWORD cCharsWritten = 0;
482
483
int BytesWritten = 0;
484
DWORD cAttrWritten = 0;
485
for (size_t i = 0; i < Attr.size(); i++) {
486
if (!WriteConsoleOutputCharacter(hConsole, Str[i].data(), ReadBufferSize, coordScreen, &cCharsWritten))
487
SLog += StringFromFormat("WriteConsoleOutputCharacter error");
488
if (!WriteConsoleOutputAttribute(hConsole, Attr[i].data(), ReadBufferSize, coordScreen, &cAttrWritten))
489
SLog += StringFromFormat("WriteConsoleOutputAttribute error");
490
491
BytesWritten += cAttrWritten;
492
coordScreen = GetCoordinates(BytesWritten, LBufWidth);
493
}
494
495
const int OldCursor = ConInfo.dwCursorPosition.Y * ConInfo.dwSize.X + ConInfo.dwCursorPosition.X;
496
COORD Coo = GetCoordinates(OldCursor, LBufWidth);
497
SetConsoleCursorPosition(hConsole, Coo);
498
499
// if (SLog.length() > 0) Log(LogLevel::LNOTICE, SLog.c_str());
500
501
// Resize the window too
502
if (Resize) {
503
MoveWindow(GetConsoleWindow(), Left, Top, (Width + 100), Height, true);
504
}
505
}
506
507
void ConsoleListener::Log(const LogMessage &msg) {
508
char buf[2048];
509
snprintf(buf, sizeof(buf), "%s %s %s", msg.timestamp, msg.header, msg.msg.c_str());
510
buf[sizeof(buf) - 2] = '\n';
511
buf[sizeof(buf) - 1] = '\0';
512
513
if (!useThread_ && IsOpen())
514
WriteToConsole(msg.level, buf, strlen(buf));
515
else
516
SendToThread(msg.level, buf);
517
}
518
519
// Clear console screen
520
void ConsoleListener::ClearScreen(bool Cursor) {
521
_dbg_assert_msg_(IsOpen(), "Don't call this before opening the console.");
522
523
CONSOLE_SCREEN_BUFFER_INFO csbi;
524
GetConsoleScreenBufferInfo(hConsole, &csbi);
525
DWORD dwConSize = csbi.dwSize.X * csbi.dwSize.Y;
526
// Write space to the entire console
527
DWORD cCharsWritten;
528
COORD coordScreen = { 0, 0 };
529
FillConsoleOutputCharacter(hConsole, TEXT(' '), dwConSize, coordScreen, &cCharsWritten);
530
GetConsoleScreenBufferInfo(hConsole, &csbi);
531
FillConsoleOutputAttribute(hConsole, csbi.wAttributes, dwConSize, coordScreen, &cCharsWritten);
532
// Reset cursor
533
if (Cursor) {
534
SetConsoleCursorPosition(hConsole, coordScreen);
535
}
536
}
537
538
#endif // PPSSPP_PLATFORM(WINDOWS)
539
540