mirror of
https://github.com/Const-me/Whisper.git
synced 2026-10-18 13:10:52 +08:00
290 lines
6.7 KiB
C++
290 lines
6.7 KiB
C++
// https://github.com/Const-me/vis_avs_dx/blob/master/avs_dx/DxVisuals/Interop/ConsoleLogger.cpp
|
|
#include "stdafx.h"
|
|
#include "DebugConsole.h"
|
|
#include "miscUtils.h"
|
|
#include "../AppState.h"
|
|
#include "logger.h"
|
|
#include <string>
|
|
|
|
namespace
|
|
{
|
|
using Whisper::eLogLevel;
|
|
|
|
constexpr uint16_t defaultAttributes = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE;
|
|
|
|
inline uint16_t textAttributes( eLogLevel lvl )
|
|
{
|
|
switch( lvl )
|
|
{
|
|
case eLogLevel::Error:
|
|
return FOREGROUND_RED | FOREGROUND_INTENSITY;
|
|
case eLogLevel::Warning:
|
|
return FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY;
|
|
case eLogLevel::Info:
|
|
return FOREGROUND_GREEN | FOREGROUND_INTENSITY;
|
|
case eLogLevel::Debug:
|
|
return FOREGROUND_BLUE | FOREGROUND_INTENSITY;
|
|
}
|
|
return defaultAttributes;
|
|
}
|
|
|
|
// Background stuff: accumulate messages in a small buffer, in case user will want to see them in the console.
|
|
// Ideally, we should accumulate them in a more efficient data structure, maybe a circular buffer.
|
|
// However, we don't have that many messages per second, this simple solution that uses std::deque is probably good enough for the job.
|
|
static constexpr uint16_t bufferSize = 64;
|
|
|
|
using Lock = CComCritSecLock<CComAutoCriticalSection>;
|
|
#define LOCK() Lock __lock{ critSec }
|
|
|
|
thread_local std::string threadError;
|
|
}
|
|
|
|
HRESULT DebugConsole::Entry::print( HANDLE hConsole, CString& tempString ) const
|
|
{
|
|
if( !SetConsoleTextAttribute( hConsole, textAttributes( level ) ) )
|
|
return getLastHr();
|
|
|
|
makeUtf16( tempString, message );
|
|
tempString += L"\r\n";
|
|
if( !WriteConsoleW( hConsole, tempString, (DWORD)tempString.GetLength(), nullptr, nullptr ) )
|
|
return getLastHr();
|
|
return S_OK;
|
|
}
|
|
|
|
void clearLastError()
|
|
{
|
|
threadError.clear();
|
|
}
|
|
|
|
bool getLastError( CString& rdi )
|
|
{
|
|
if( threadError.empty() )
|
|
{
|
|
rdi = L"";
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
makeUtf16( rdi, threadError.c_str() );
|
|
threadError.clear();
|
|
return true;
|
|
}
|
|
}
|
|
|
|
inline void DebugConsole::logSink( eLogLevel lvl, const char* message )
|
|
{
|
|
LOCK();
|
|
|
|
// Add to the buffer
|
|
while( buffer.size() >= bufferSize )
|
|
buffer.pop_front();
|
|
buffer.emplace_back( Entry{ lvl, message } );
|
|
|
|
// If the console window is shown, print there, too.
|
|
if( output )
|
|
buffer.rbegin()->print( output, tempString );
|
|
}
|
|
|
|
void __stdcall DebugConsole::logSinkStatic( void* context, eLogLevel lvl, const char* message )
|
|
{
|
|
if( lvl == eLogLevel::Error )
|
|
threadError = message;
|
|
|
|
DebugConsole* con = (DebugConsole*)context;
|
|
con->logSink( lvl, message );
|
|
}
|
|
|
|
HRESULT DebugConsole::initialize( Whisper::eLogLevel level )
|
|
{
|
|
if( nullptr != pGlobalInstance )
|
|
return HRESULT_FROM_WIN32( ERROR_ALREADY_INITIALIZED );
|
|
pGlobalInstance = this;
|
|
|
|
Whisper::sLoggerSetup setup;
|
|
setup.sink = &logSinkStatic;
|
|
setup.context = this;
|
|
setup.level = level;
|
|
setup.flags = Whisper::eLoggerFlags::SkipFormatMessage;
|
|
return Whisper::setupLogger( setup );
|
|
}
|
|
|
|
DebugConsole::~DebugConsole()
|
|
{
|
|
hide();
|
|
|
|
Whisper::sLoggerSetup setup;
|
|
memset( &setup, 0, sizeof( setup ) );
|
|
Whisper::setupLogger( setup );
|
|
|
|
pGlobalInstance = nullptr;
|
|
}
|
|
|
|
DebugConsole* DebugConsole::pGlobalInstance = nullptr;
|
|
|
|
void DebugConsole::windowClosed()
|
|
{
|
|
LOCK();
|
|
if( FreeConsole() )
|
|
{
|
|
// Apparently, FreeConsole already closes that handle: https://stackoverflow.com/q/12676312/126995
|
|
output.Detach();
|
|
}
|
|
output.Close();
|
|
|
|
for( CButton* b : checkboxes )
|
|
{
|
|
if( !*b )
|
|
continue;
|
|
if( !b->IsWindow() )
|
|
continue;
|
|
PostMessage( *b, BM_SETCHECK, BST_UNCHECKED, 0 );
|
|
}
|
|
}
|
|
|
|
BOOL __stdcall DebugConsole::consoleHandlerRoutine( DWORD dwCtrlType )
|
|
{
|
|
switch( dwCtrlType )
|
|
{
|
|
case CTRL_CLOSE_EVENT:
|
|
case CTRL_C_EVENT:
|
|
case CTRL_BREAK_EVENT:
|
|
pGlobalInstance->windowClosed();
|
|
return TRUE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
HRESULT DebugConsole::show()
|
|
{
|
|
HWND hWnd = GetConsoleWindow();
|
|
if( nullptr != hWnd )
|
|
{
|
|
ShowWindow( hWnd, SW_RESTORE );
|
|
SetForegroundWindow( hWnd );
|
|
return S_FALSE;
|
|
}
|
|
|
|
if( !AllocConsole() )
|
|
return getLastHr();
|
|
|
|
output.Close();
|
|
output.Attach( GetStdHandle( STD_OUTPUT_HANDLE ) );
|
|
if( !output )
|
|
return getLastHr();
|
|
|
|
constexpr UINT cp = CP_UTF8;
|
|
if( IsValidCodePage( cp ) )
|
|
SetConsoleOutputCP( cp );
|
|
|
|
// Enable ANSI color coding
|
|
DWORD mode = 0;
|
|
if( !GetConsoleMode( output, &mode ) )
|
|
return getLastHr();
|
|
if( 0 == ( mode & ENABLE_VIRTUAL_TERMINAL_PROCESSING ) )
|
|
{
|
|
mode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
|
|
if( !SetConsoleMode( output, mode ) )
|
|
return getLastHr();
|
|
}
|
|
|
|
SetConsoleTitle( L"Whisper Desktop Debug Console" );
|
|
|
|
SetConsoleCtrlHandler( &consoleHandlerRoutine, TRUE );
|
|
|
|
// Disable close command in the sys.menu of the new console, otherwise the whole process will quit: https://stackoverflow.com/a/12015131/126995
|
|
HWND hwnd = ::GetConsoleWindow();
|
|
if( hwnd != nullptr )
|
|
{
|
|
HMENU hMenu = ::GetSystemMenu( hwnd, FALSE );
|
|
if( hMenu != NULL )
|
|
DeleteMenu( hMenu, SC_CLOSE, MF_BYCOMMAND );
|
|
}
|
|
|
|
// Print old log entries
|
|
for( const auto& e : buffer )
|
|
CHECK( e.print( output, tempString ) );
|
|
|
|
const CStringA msg = "Press Control+C or Control+Break to close this window\r\n";
|
|
if( !SetConsoleTextAttribute( output, FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_INTENSITY ) )
|
|
return getLastHr();
|
|
if( !WriteConsoleA( output, cstr( msg ), msg.GetLength(), nullptr, nullptr ) )
|
|
return getLastHr();
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT DebugConsole::hide()
|
|
{
|
|
LOCK();
|
|
if( !output )
|
|
return S_FALSE;
|
|
windowClosed();
|
|
return S_OK;
|
|
}
|
|
|
|
void DebugConsole::addCheckbox( CButton& cb )
|
|
{
|
|
checkboxes.emplace( &cb );
|
|
}
|
|
void DebugConsole::removeCheckbox( CButton& cb )
|
|
{
|
|
checkboxes.erase( &cb );
|
|
}
|
|
|
|
HRESULT ConsoleCheckbox::initialize( HWND dialog, int idc, AppState& state )
|
|
{
|
|
control = GetDlgItem( dialog, idc );
|
|
assert( control );
|
|
|
|
console = &state.console;
|
|
if( state.console.isVisible() )
|
|
control.SetCheck( BST_CHECKED );
|
|
|
|
state.console.addCheckbox( control );
|
|
return S_OK;
|
|
}
|
|
|
|
void ConsoleCheckbox::click()
|
|
{
|
|
const int state = control.GetCheck();
|
|
if( state == BST_CHECKED )
|
|
console->show();
|
|
else
|
|
console->hide();
|
|
}
|
|
|
|
void ConsoleCheckbox::ensureChecked()
|
|
{
|
|
const int state = control.GetCheck();
|
|
if( state == BST_CHECKED )
|
|
return;
|
|
control.SetCheck( BST_CHECKED );
|
|
console->show();
|
|
}
|
|
|
|
void DebugConsole::log( eLogLevel lvl, const char* pszFormat, va_list args )
|
|
{
|
|
LOCK();
|
|
// Add to the buffer
|
|
while( buffer.size() >= bufferSize )
|
|
buffer.pop_front();
|
|
|
|
tempStringA.FormatV( pszFormat, args );
|
|
buffer.emplace_back( Entry{ lvl, tempStringA } );
|
|
|
|
// If the console window is shown, print there, too.
|
|
if( output )
|
|
buffer.rbegin()->print( output, tempString );
|
|
}
|
|
|
|
void DebugConsole::logMessage( eLogLevel lvl, const char* pszFormat, va_list args )
|
|
{
|
|
if( nullptr == pGlobalInstance )
|
|
return;
|
|
pGlobalInstance->log( lvl, pszFormat, args );
|
|
}
|
|
|
|
void logMessage( Whisper::eLogLevel lvl, const char8_t* pczFormat, va_list args )
|
|
{
|
|
DebugConsole::logMessage( lvl, (const char*)pczFormat, args );
|
|
} |