mirror of
https://github.com/RetroDECK/Duckstation.git
synced 2024-11-29 00:55:41 +00:00
Qt: Add log window
This commit is contained in:
parent
8afccdd590
commit
4ad777f54f
|
@ -7,7 +7,7 @@
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <pthread.h>
|
#include <pthread.h>
|
||||||
#include <signal.h>
|
#include <signal.h>
|
||||||
Log_SetChannel(HTTPDownloaderCurl);
|
Log_SetChannel(HTTPDownloader);
|
||||||
|
|
||||||
namespace Common {
|
namespace Common {
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
#include "timer.h"
|
#include "timer.h"
|
||||||
#include <VersionHelpers.h>
|
#include <VersionHelpers.h>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
Log_SetChannel(HTTPDownloaderWinHttp);
|
Log_SetChannel(HTTPDownloader);
|
||||||
|
|
||||||
namespace Common {
|
namespace Common {
|
||||||
|
|
||||||
|
|
|
@ -23,40 +23,65 @@
|
||||||
|
|
||||||
using namespace std::string_view_literals;
|
using namespace std::string_view_literals;
|
||||||
|
|
||||||
static const char s_log_level_characters[LOGLEVEL_COUNT] = {'X', 'E', 'W', 'P', 'I', 'V', 'D', 'R', 'B', 'T'};
|
namespace Log {
|
||||||
|
namespace {
|
||||||
struct RegisteredCallback
|
struct RegisteredCallback
|
||||||
{
|
{
|
||||||
Log::CallbackFunctionType Function;
|
Log::CallbackFunctionType Function;
|
||||||
void* Parameter;
|
void* Parameter;
|
||||||
};
|
};
|
||||||
|
} // namespace
|
||||||
|
|
||||||
std::vector<RegisteredCallback> s_callbacks;
|
static void RegisterCallback(CallbackFunctionType callbackFunction, void* pUserParam,
|
||||||
|
const std::unique_lock<std::mutex>& lock);
|
||||||
|
static void UnregisterCallback(CallbackFunctionType callbackFunction, void* pUserParam,
|
||||||
|
const std::unique_lock<std::mutex>& lock);
|
||||||
|
static bool FilterTest(LOGLEVEL level, const char* channelName, const std::unique_lock<std::mutex>& lock);
|
||||||
|
static void ExecuteCallbacks(const char* channelName, const char* functionName, LOGLEVEL level,
|
||||||
|
std::string_view message, const std::unique_lock<std::mutex>& lock);
|
||||||
|
static void FormatLogMessageForDisplay(fmt::memory_buffer& buffer, const char* channelName, const char* functionName,
|
||||||
|
LOGLEVEL level, std::string_view message, bool timestamp, bool ansi_color_code,
|
||||||
|
bool newline);
|
||||||
|
static void ConsoleOutputLogCallback(void* pUserParam, const char* channelName, const char* functionName,
|
||||||
|
LOGLEVEL level, std::string_view message);
|
||||||
|
static void DebugOutputLogCallback(void* pUserParam, const char* channelName, const char* functionName, LOGLEVEL level,
|
||||||
|
std::string_view message);
|
||||||
|
static void FileOutputLogCallback(void* pUserParam, const char* channelName, const char* functionName, LOGLEVEL level,
|
||||||
|
std::string_view message);
|
||||||
|
template<typename T>
|
||||||
|
static void FormatLogMessageAndPrint(const char* channelName, const char* functionName, LOGLEVEL level,
|
||||||
|
std::string_view message, bool timestamp, bool ansi_color_code, bool newline,
|
||||||
|
const T& callback);
|
||||||
|
#ifdef _WIN32
|
||||||
|
template<typename T>
|
||||||
|
static void FormatLogMessageAndPrintW(const char* channelName, const char* functionName, LOGLEVEL level,
|
||||||
|
std::string_view message, bool timestamp, bool ansi_color_code, bool newline,
|
||||||
|
const T& callback);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static const char s_log_level_characters[LOGLEVEL_COUNT] = {'X', 'E', 'W', 'P', 'I', 'V', 'D', 'R', 'B', 'T'};
|
||||||
|
|
||||||
|
static std::vector<RegisteredCallback> s_callbacks;
|
||||||
static std::mutex s_callback_mutex;
|
static std::mutex s_callback_mutex;
|
||||||
|
|
||||||
static LOGLEVEL s_filter_level = LOGLEVEL_TRACE;
|
static Common::Timer::Value s_start_timestamp = Common::Timer::GetCurrentValue();
|
||||||
|
|
||||||
static Common::Timer::Value s_startTimeStamp = Common::Timer::GetCurrentValue();
|
|
||||||
|
|
||||||
|
static std::string s_log_filter;
|
||||||
|
static LOGLEVEL s_log_level = LOGLEVEL_TRACE;
|
||||||
static bool s_console_output_enabled = false;
|
static bool s_console_output_enabled = false;
|
||||||
static std::string s_console_output_channel_filter;
|
static bool s_console_output_timestamps = true;
|
||||||
static LOGLEVEL s_console_output_level_filter = LOGLEVEL_TRACE;
|
static bool s_file_output_enabled = false;
|
||||||
|
static bool s_file_output_timestamp = false;
|
||||||
|
static bool s_debug_output_enabled = false;
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
static HANDLE s_hConsoleStdIn = NULL;
|
static HANDLE s_hConsoleStdIn = NULL;
|
||||||
static HANDLE s_hConsoleStdOut = NULL;
|
static HANDLE s_hConsoleStdOut = NULL;
|
||||||
static HANDLE s_hConsoleStdErr = NULL;
|
static HANDLE s_hConsoleStdErr = NULL;
|
||||||
#endif
|
#endif
|
||||||
|
} // namespace Log
|
||||||
|
|
||||||
static bool s_debug_output_enabled = false;
|
std::unique_ptr<std::FILE, void (*)(std::FILE*)> s_file_handle(nullptr, [](std::FILE* fp) {
|
||||||
static std::string s_debug_output_channel_filter;
|
|
||||||
static LOGLEVEL s_debug_output_level_filter = LOGLEVEL_TRACE;
|
|
||||||
|
|
||||||
static bool s_file_output_enabled = false;
|
|
||||||
static bool s_file_output_timestamp = false;
|
|
||||||
static std::string s_file_output_channel_filter;
|
|
||||||
static LOGLEVEL s_file_output_level_filter = LOGLEVEL_TRACE;
|
|
||||||
std::unique_ptr<std::FILE, void (*)(std::FILE*)> s_fileOutputHandle(nullptr, [](std::FILE* fp) {
|
|
||||||
if (fp)
|
if (fp)
|
||||||
{
|
{
|
||||||
std::fclose(fp);
|
std::fclose(fp);
|
||||||
|
@ -64,19 +89,30 @@ std::unique_ptr<std::FILE, void (*)(std::FILE*)> s_fileOutputHandle(nullptr, [](
|
||||||
});
|
});
|
||||||
|
|
||||||
void Log::RegisterCallback(CallbackFunctionType callbackFunction, void* pUserParam)
|
void Log::RegisterCallback(CallbackFunctionType callbackFunction, void* pUserParam)
|
||||||
|
{
|
||||||
|
std::unique_lock lock(s_callback_mutex);
|
||||||
|
RegisterCallback(callbackFunction, pUserParam, lock);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Log::RegisterCallback(CallbackFunctionType callbackFunction, void* pUserParam,
|
||||||
|
const std::unique_lock<std::mutex>& lock)
|
||||||
{
|
{
|
||||||
RegisteredCallback Callback;
|
RegisteredCallback Callback;
|
||||||
Callback.Function = callbackFunction;
|
Callback.Function = callbackFunction;
|
||||||
Callback.Parameter = pUserParam;
|
Callback.Parameter = pUserParam;
|
||||||
|
|
||||||
std::lock_guard<std::mutex> guard(s_callback_mutex);
|
|
||||||
s_callbacks.push_back(std::move(Callback));
|
s_callbacks.push_back(std::move(Callback));
|
||||||
}
|
}
|
||||||
|
|
||||||
void Log::UnregisterCallback(CallbackFunctionType callbackFunction, void* pUserParam)
|
void Log::UnregisterCallback(CallbackFunctionType callbackFunction, void* pUserParam)
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> guard(s_callback_mutex);
|
std::unique_lock lock(s_callback_mutex);
|
||||||
|
UnregisterCallback(callbackFunction, pUserParam, lock);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Log::UnregisterCallback(CallbackFunctionType callbackFunction, void* pUserParam,
|
||||||
|
const std::unique_lock<std::mutex>& lock)
|
||||||
|
{
|
||||||
for (auto iter = s_callbacks.begin(); iter != s_callbacks.end(); ++iter)
|
for (auto iter = s_callbacks.begin(); iter != s_callbacks.end(); ++iter)
|
||||||
{
|
{
|
||||||
if (iter->Function == callbackFunction && iter->Parameter == pUserParam)
|
if (iter->Function == callbackFunction && iter->Parameter == pUserParam)
|
||||||
|
@ -87,6 +123,11 @@ void Log::UnregisterCallback(CallbackFunctionType callbackFunction, void* pUserP
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
float Log::GetCurrentMessageTime()
|
||||||
|
{
|
||||||
|
return static_cast<float>(Common::Timer::ConvertValueToSeconds(Common::Timer::GetCurrentValue() - s_start_timestamp));
|
||||||
|
}
|
||||||
|
|
||||||
bool Log::IsConsoleOutputEnabled()
|
bool Log::IsConsoleOutputEnabled()
|
||||||
{
|
{
|
||||||
return s_console_output_enabled;
|
return s_console_output_enabled;
|
||||||
|
@ -97,18 +138,17 @@ bool Log::IsDebugOutputEnabled()
|
||||||
return s_debug_output_enabled;
|
return s_debug_output_enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void ExecuteCallbacks(const char* channelName, const char* functionName, LOGLEVEL level,
|
void Log::ExecuteCallbacks(const char* channelName, const char* functionName, LOGLEVEL level, std::string_view message,
|
||||||
std::string_view message)
|
const std::unique_lock<std::mutex>& lock)
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> guard(s_callback_mutex);
|
|
||||||
for (RegisteredCallback& callback : s_callbacks)
|
for (RegisteredCallback& callback : s_callbacks)
|
||||||
callback.Function(callback.Parameter, channelName, functionName, level, message);
|
callback.Function(callback.Parameter, channelName, functionName, level, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
ALWAYS_INLINE_RELEASE static void FormatLogMessageForDisplay(fmt::memory_buffer& buffer, const char* channelName,
|
ALWAYS_INLINE_RELEASE void Log::FormatLogMessageForDisplay(fmt::memory_buffer& buffer, const char* channelName,
|
||||||
const char* functionName, LOGLEVEL level,
|
const char* functionName, LOGLEVEL level,
|
||||||
std::string_view message, bool timestamp,
|
std::string_view message, bool timestamp,
|
||||||
bool ansi_color_code, bool newline)
|
bool ansi_color_code, bool newline)
|
||||||
{
|
{
|
||||||
static constexpr std::string_view s_ansi_color_codes[LOGLEVEL_COUNT] = {
|
static constexpr std::string_view s_ansi_color_codes[LOGLEVEL_COUNT] = {
|
||||||
"\033[0m"sv, // NONE
|
"\033[0m"sv, // NONE
|
||||||
|
@ -132,8 +172,7 @@ ALWAYS_INLINE_RELEASE static void FormatLogMessageForDisplay(fmt::memory_buffer&
|
||||||
if (timestamp)
|
if (timestamp)
|
||||||
{
|
{
|
||||||
// find time since start of process
|
// find time since start of process
|
||||||
const float message_time =
|
const float message_time = Log::GetCurrentMessageTime();
|
||||||
static_cast<float>(Common::Timer::ConvertValueToSeconds(Common::Timer::GetCurrentValue() - s_startTimeStamp));
|
|
||||||
|
|
||||||
if (level <= LOGLEVEL_PERF)
|
if (level <= LOGLEVEL_PERF)
|
||||||
{
|
{
|
||||||
|
@ -162,24 +201,26 @@ ALWAYS_INLINE_RELEASE static void FormatLogMessageForDisplay(fmt::memory_buffer&
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename T>
|
template<typename T>
|
||||||
ALWAYS_INLINE_RELEASE static void FormatLogMessageAndPrint(const char* channelName, const char* functionName,
|
ALWAYS_INLINE_RELEASE void Log::FormatLogMessageAndPrint(const char* channelName, const char* functionName,
|
||||||
LOGLEVEL level, std::string_view message, bool timestamp,
|
LOGLEVEL level, std::string_view message, bool timestamp,
|
||||||
bool ansi_color_code, bool newline, const T& callback)
|
bool ansi_color_code, bool newline, const T& callback)
|
||||||
{
|
{
|
||||||
fmt::memory_buffer buffer;
|
fmt::memory_buffer buffer;
|
||||||
FormatLogMessageForDisplay(buffer, channelName, functionName, level, message, timestamp, ansi_color_code, newline);
|
Log::FormatLogMessageForDisplay(buffer, channelName, functionName, level, message, timestamp, ansi_color_code,
|
||||||
|
newline);
|
||||||
callback(std::string_view(buffer.data(), buffer.size()));
|
callback(std::string_view(buffer.data(), buffer.size()));
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
|
|
||||||
template<typename T>
|
template<typename T>
|
||||||
ALWAYS_INLINE_RELEASE static void FormatLogMessageAndPrintW(const char* channelName, const char* functionName,
|
ALWAYS_INLINE_RELEASE void Log::FormatLogMessageAndPrintW(const char* channelName, const char* functionName,
|
||||||
LOGLEVEL level, std::string_view message, bool timestamp,
|
LOGLEVEL level, std::string_view message, bool timestamp,
|
||||||
bool ansi_color_code, bool newline, const T& callback)
|
bool ansi_color_code, bool newline, const T& callback)
|
||||||
{
|
{
|
||||||
fmt::memory_buffer buffer;
|
fmt::memory_buffer buffer;
|
||||||
FormatLogMessageForDisplay(buffer, channelName, functionName, level, message, timestamp, ansi_color_code, newline);
|
Log::FormatLogMessageForDisplay(buffer, channelName, functionName, level, message, timestamp, ansi_color_code,
|
||||||
|
newline);
|
||||||
|
|
||||||
// Convert to UTF-16 first so unicode characters display correctly. NT is going to do it
|
// Convert to UTF-16 first so unicode characters display correctly. NT is going to do it
|
||||||
// anyway...
|
// anyway...
|
||||||
|
@ -219,24 +260,22 @@ static bool EnableVirtualTerminalProcessing(HANDLE hConsole)
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
static void ConsoleOutputLogCallback(void* pUserParam, const char* channelName, const char* functionName,
|
void Log::ConsoleOutputLogCallback(void* pUserParam, const char* channelName, const char* functionName, LOGLEVEL level,
|
||||||
LOGLEVEL level, std::string_view message)
|
std::string_view message)
|
||||||
{
|
{
|
||||||
if (!s_console_output_enabled || level > s_console_output_level_filter ||
|
if (!s_console_output_enabled)
|
||||||
s_console_output_channel_filter.find(channelName) != std::string::npos)
|
|
||||||
{
|
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
#if defined(_WIN32)
|
#if defined(_WIN32)
|
||||||
FormatLogMessageAndPrintW(
|
FormatLogMessageAndPrintW(channelName, functionName, level, message, s_console_output_timestamps, true, true,
|
||||||
channelName, functionName, level, message, true, true, true, [level](const std::wstring_view& message) {
|
[level](const std::wstring_view& message) {
|
||||||
HANDLE hOutput = (level <= LOGLEVEL_WARNING) ? s_hConsoleStdErr : s_hConsoleStdOut;
|
HANDLE hOutput = (level <= LOGLEVEL_WARNING) ? s_hConsoleStdErr : s_hConsoleStdOut;
|
||||||
DWORD chars_written;
|
DWORD chars_written;
|
||||||
WriteConsoleW(hOutput, message.data(), static_cast<DWORD>(message.length()), &chars_written, nullptr);
|
WriteConsoleW(hOutput, message.data(), static_cast<DWORD>(message.length()),
|
||||||
});
|
&chars_written, nullptr);
|
||||||
|
});
|
||||||
#elif !defined(__ANDROID__)
|
#elif !defined(__ANDROID__)
|
||||||
FormatLogMessageAndPrint(channelName, functionName, level, message, true, true, true,
|
FormatLogMessageAndPrint(channelName, functionName, level, message, s_console_output_timestamps, true, true,
|
||||||
[level](const std::string_view& message) {
|
[level](const std::string_view& message) {
|
||||||
const int outputFd = (level <= LOGLEVEL_WARNING) ? STDERR_FILENO : STDOUT_FILENO;
|
const int outputFd = (level <= LOGLEVEL_WARNING) ? STDERR_FILENO : STDOUT_FILENO;
|
||||||
write(outputFd, message.data(), message.length());
|
write(outputFd, message.data(), message.length());
|
||||||
|
@ -244,17 +283,14 @@ static void ConsoleOutputLogCallback(void* pUserParam, const char* channelName,
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
static void DebugOutputLogCallback(void* pUserParam, const char* channelName, const char* functionName, LOGLEVEL level,
|
void Log::DebugOutputLogCallback(void* pUserParam, const char* channelName, const char* functionName, LOGLEVEL level,
|
||||||
std::string_view message)
|
std::string_view message)
|
||||||
{
|
{
|
||||||
if (!s_debug_output_enabled || level > s_debug_output_level_filter ||
|
if (!s_debug_output_enabled)
|
||||||
s_debug_output_channel_filter.find(functionName) != std::string::npos)
|
|
||||||
{
|
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
#if defined(_WIN32)
|
#if defined(_WIN32)
|
||||||
FormatLogMessageAndPrintW(channelName, functionName, level, message, true, false, true,
|
FormatLogMessageAndPrintW(channelName, functionName, level, message, false, false, true,
|
||||||
[](const std::wstring_view& message) { OutputDebugStringW(message.data()); });
|
[](const std::wstring_view& message) { OutputDebugStringW(message.data()); });
|
||||||
#elif defined(__ANDROID__)
|
#elif defined(__ANDROID__)
|
||||||
static const int logPriority[LOGLEVEL_COUNT] = {
|
static const int logPriority[LOGLEVEL_COUNT] = {
|
||||||
|
@ -275,15 +311,15 @@ static void DebugOutputLogCallback(void* pUserParam, const char* channelName, co
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void Log::SetConsoleOutputParams(bool Enabled, const char* ChannelFilter, LOGLEVEL LevelFilter)
|
void Log::SetConsoleOutputParams(bool enabled, bool timestamps)
|
||||||
{
|
{
|
||||||
s_console_output_channel_filter = (ChannelFilter != NULL) ? ChannelFilter : "";
|
std::unique_lock lock(s_callback_mutex);
|
||||||
s_console_output_level_filter = LevelFilter;
|
|
||||||
|
|
||||||
if (s_console_output_enabled == Enabled)
|
s_console_output_enabled = timestamps;
|
||||||
|
if (s_console_output_enabled == enabled)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
s_console_output_enabled = Enabled;
|
s_console_output_enabled = enabled;
|
||||||
|
|
||||||
#if defined(_WIN32)
|
#if defined(_WIN32)
|
||||||
// On windows, no console is allocated by default on a windows based application
|
// On windows, no console is allocated by default on a windows based application
|
||||||
|
@ -292,7 +328,7 @@ void Log::SetConsoleOutputParams(bool Enabled, const char* ChannelFilter, LOGLEV
|
||||||
static HANDLE old_stdout = NULL;
|
static HANDLE old_stdout = NULL;
|
||||||
static HANDLE old_stderr = NULL;
|
static HANDLE old_stderr = NULL;
|
||||||
|
|
||||||
if (Enabled)
|
if (enabled)
|
||||||
{
|
{
|
||||||
old_stdin = GetStdHandle(STD_INPUT_HANDLE);
|
old_stdin = GetStdHandle(STD_INPUT_HANDLE);
|
||||||
old_stdout = GetStdHandle(STD_OUTPUT_HANDLE);
|
old_stdout = GetStdHandle(STD_OUTPUT_HANDLE);
|
||||||
|
@ -349,90 +385,95 @@ void Log::SetConsoleOutputParams(bool Enabled, const char* ChannelFilter, LOGLEV
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (Enabled)
|
if (enabled)
|
||||||
RegisterCallback(ConsoleOutputLogCallback, nullptr);
|
RegisterCallback(ConsoleOutputLogCallback, nullptr, lock);
|
||||||
else
|
else
|
||||||
UnregisterCallback(ConsoleOutputLogCallback, nullptr);
|
UnregisterCallback(ConsoleOutputLogCallback, nullptr, lock);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Log::SetDebugOutputParams(bool enabled, const char* channelFilter /* = nullptr */,
|
void Log::SetDebugOutputParams(bool enabled)
|
||||||
LOGLEVEL levelFilter /* = LOGLEVEL_TRACE */)
|
|
||||||
{
|
{
|
||||||
if (s_debug_output_enabled != enabled)
|
std::unique_lock lock(s_callback_mutex);
|
||||||
{
|
if (s_debug_output_enabled == enabled)
|
||||||
s_debug_output_enabled = enabled;
|
return;
|
||||||
if (enabled)
|
|
||||||
RegisterCallback(DebugOutputLogCallback, nullptr);
|
|
||||||
else
|
|
||||||
UnregisterCallback(DebugOutputLogCallback, nullptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
s_debug_output_channel_filter = (channelFilter != nullptr) ? channelFilter : "";
|
s_debug_output_enabled = enabled;
|
||||||
s_debug_output_level_filter = levelFilter;
|
if (enabled)
|
||||||
|
RegisterCallback(DebugOutputLogCallback, nullptr, lock);
|
||||||
|
else
|
||||||
|
UnregisterCallback(DebugOutputLogCallback, nullptr, lock);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void FileOutputLogCallback(void* pUserParam, const char* channelName, const char* functionName, LOGLEVEL level,
|
void Log::FileOutputLogCallback(void* pUserParam, const char* channelName, const char* functionName, LOGLEVEL level,
|
||||||
std::string_view message)
|
std::string_view message)
|
||||||
{
|
{
|
||||||
if (level > s_file_output_level_filter || s_file_output_channel_filter.find(channelName) != std::string::npos)
|
if (!s_file_output_enabled)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
FormatLogMessageAndPrint(
|
FormatLogMessageAndPrint(
|
||||||
channelName, functionName, level, message, true, false, true,
|
channelName, functionName, level, message, true, false, true,
|
||||||
[](const std::string_view& message) { std::fwrite(message.data(), 1, message.size(), s_fileOutputHandle.get()); });
|
[](const std::string_view& message) { std::fwrite(message.data(), 1, message.size(), s_file_handle.get()); });
|
||||||
}
|
}
|
||||||
|
|
||||||
void Log::SetFileOutputParams(bool enabled, const char* filename, bool timestamps /* = true */,
|
void Log::SetFileOutputParams(bool enabled, const char* filename, bool timestamps /* = true */)
|
||||||
const char* channelFilter /* = nullptr */, LOGLEVEL levelFilter /* = LOGLEVEL_TRACE */)
|
|
||||||
{
|
{
|
||||||
if (s_file_output_enabled != enabled)
|
std::unique_lock lock(s_callback_mutex);
|
||||||
|
if (s_file_output_enabled == enabled)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (enabled)
|
||||||
{
|
{
|
||||||
if (enabled)
|
s_file_handle.reset(FileSystem::OpenCFile(filename, "wb"));
|
||||||
|
if (!s_file_handle)
|
||||||
{
|
{
|
||||||
s_fileOutputHandle.reset(FileSystem::OpenCFile(filename, "wb"));
|
Log::Writef("Log", __FUNCTION__, LOGLEVEL_ERROR, "Failed to open log file '%s'", filename);
|
||||||
if (!s_fileOutputHandle)
|
return;
|
||||||
{
|
|
||||||
Log::Writef("Log", __FUNCTION__, LOGLEVEL_ERROR, "Failed to open log file '%s'", filename);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
RegisterCallback(FileOutputLogCallback, nullptr);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
UnregisterCallback(FileOutputLogCallback, nullptr);
|
|
||||||
s_fileOutputHandle.reset();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
s_file_output_enabled = enabled;
|
RegisterCallback(FileOutputLogCallback, nullptr, lock);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
UnregisterCallback(FileOutputLogCallback, nullptr, lock);
|
||||||
|
s_file_handle.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
std::lock_guard<std::mutex> guard(s_callback_mutex);
|
s_file_output_enabled = enabled;
|
||||||
s_file_output_channel_filter = (channelFilter != nullptr) ? channelFilter : "";
|
|
||||||
s_file_output_level_filter = levelFilter;
|
|
||||||
s_file_output_timestamp = timestamps;
|
s_file_output_timestamp = timestamps;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Log::SetFilterLevel(LOGLEVEL level)
|
void Log::SetLogLevel(LOGLEVEL level)
|
||||||
{
|
{
|
||||||
|
std::unique_lock lock(s_callback_mutex);
|
||||||
DebugAssert(level < LOGLEVEL_COUNT);
|
DebugAssert(level < LOGLEVEL_COUNT);
|
||||||
s_filter_level = level;
|
s_log_level = level;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Log::SetLogfilter(std::string_view filter)
|
||||||
|
{
|
||||||
|
std::unique_lock lock(s_callback_mutex);
|
||||||
|
if (s_log_filter != filter)
|
||||||
|
s_log_filter = filter;
|
||||||
|
}
|
||||||
|
|
||||||
|
ALWAYS_INLINE_RELEASE bool Log::FilterTest(LOGLEVEL level, const char* channelName,
|
||||||
|
const std::unique_lock<std::mutex>& lock)
|
||||||
|
{
|
||||||
|
return (level <= s_log_level && s_log_filter.find(channelName) == std::string::npos);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Log::Write(const char* channelName, const char* functionName, LOGLEVEL level, std::string_view message)
|
void Log::Write(const char* channelName, const char* functionName, LOGLEVEL level, std::string_view message)
|
||||||
{
|
{
|
||||||
if (level > s_filter_level)
|
std::unique_lock lock(s_callback_mutex);
|
||||||
|
if (!FilterTest(level, channelName, lock))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
ExecuteCallbacks(channelName, functionName, level, message);
|
ExecuteCallbacks(channelName, functionName, level, message, lock);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Log::Writef(const char* channelName, const char* functionName, LOGLEVEL level, const char* format, ...)
|
void Log::Writef(const char* channelName, const char* functionName, LOGLEVEL level, const char* format, ...)
|
||||||
{
|
{
|
||||||
if (level > s_filter_level)
|
std::va_list ap;
|
||||||
return;
|
|
||||||
|
|
||||||
va_list ap;
|
|
||||||
va_start(ap, format);
|
va_start(ap, format);
|
||||||
Writev(channelName, functionName, level, format, ap);
|
Writev(channelName, functionName, level, format, ap);
|
||||||
va_end(ap);
|
va_end(ap);
|
||||||
|
@ -440,10 +481,11 @@ void Log::Writef(const char* channelName, const char* functionName, LOGLEVEL lev
|
||||||
|
|
||||||
void Log::Writev(const char* channelName, const char* functionName, LOGLEVEL level, const char* format, va_list ap)
|
void Log::Writev(const char* channelName, const char* functionName, LOGLEVEL level, const char* format, va_list ap)
|
||||||
{
|
{
|
||||||
if (level > s_filter_level)
|
std::unique_lock lock(s_callback_mutex);
|
||||||
|
if (!FilterTest(level, channelName, lock))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
va_list apCopy;
|
std::va_list apCopy;
|
||||||
va_copy(apCopy, ap);
|
va_copy(apCopy, ap);
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
|
@ -453,17 +495,19 @@ void Log::Writev(const char* channelName, const char* functionName, LOGLEVEL lev
|
||||||
#endif
|
#endif
|
||||||
va_end(apCopy);
|
va_end(apCopy);
|
||||||
|
|
||||||
if (requiredSize < 256)
|
if (requiredSize < 512)
|
||||||
{
|
{
|
||||||
char buffer[256];
|
char buffer[512];
|
||||||
std::vsnprintf(buffer, countof(buffer), format, ap);
|
const int len = std::vsnprintf(buffer, countof(buffer), format, ap);
|
||||||
ExecuteCallbacks(channelName, functionName, level, buffer);
|
if (len > 0)
|
||||||
|
ExecuteCallbacks(channelName, functionName, level, std::string_view(buffer, static_cast<size_t>(len)), lock);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
char* buffer = new char[requiredSize + 1];
|
char* buffer = new char[requiredSize + 1];
|
||||||
std::vsnprintf(buffer, requiredSize + 1, format, ap);
|
const int len = std::vsnprintf(buffer, requiredSize + 1, format, ap);
|
||||||
ExecuteCallbacks(channelName, functionName, level, buffer);
|
if (len > 0)
|
||||||
|
ExecuteCallbacks(channelName, functionName, level, std::string_view(buffer, static_cast<size_t>(len)), lock);
|
||||||
delete[] buffer;
|
delete[] buffer;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -471,11 +515,12 @@ void Log::Writev(const char* channelName, const char* functionName, LOGLEVEL lev
|
||||||
void Log::WriteFmtArgs(const char* channelName, const char* functionName, LOGLEVEL level, fmt::string_view fmt,
|
void Log::WriteFmtArgs(const char* channelName, const char* functionName, LOGLEVEL level, fmt::string_view fmt,
|
||||||
fmt::format_args args)
|
fmt::format_args args)
|
||||||
{
|
{
|
||||||
if (level > s_filter_level)
|
std::unique_lock lock(s_callback_mutex);
|
||||||
|
if (!FilterTest(level, channelName, lock))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
fmt::memory_buffer buffer;
|
fmt::memory_buffer buffer;
|
||||||
fmt::vformat_to(std::back_inserter(buffer), fmt, args);
|
fmt::vformat_to(std::back_inserter(buffer), fmt, args);
|
||||||
|
|
||||||
ExecuteCallbacks(channelName, functionName, level, std::string_view(buffer.data(), buffer.size()));
|
ExecuteCallbacks(channelName, functionName, level, std::string_view(buffer.data(), buffer.size()), lock);
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,20 +38,25 @@ void RegisterCallback(CallbackFunctionType callbackFunction, void* pUserParam);
|
||||||
// unregisters a log callback
|
// unregisters a log callback
|
||||||
void UnregisterCallback(CallbackFunctionType callbackFunction, void* pUserParam);
|
void UnregisterCallback(CallbackFunctionType callbackFunction, void* pUserParam);
|
||||||
|
|
||||||
|
// returns the time in seconds since the start of the process
|
||||||
|
float GetCurrentMessageTime();
|
||||||
|
|
||||||
// adds a standard console output
|
// adds a standard console output
|
||||||
bool IsConsoleOutputEnabled();
|
bool IsConsoleOutputEnabled();
|
||||||
void SetConsoleOutputParams(bool enabled, const char* channelFilter = nullptr, LOGLEVEL levelFilter = LOGLEVEL_TRACE);
|
void SetConsoleOutputParams(bool enabled, bool timestamps = true);
|
||||||
|
|
||||||
// adds a debug console output [win32/android only]
|
// adds a debug console output [win32/android only]
|
||||||
bool IsDebugOutputEnabled();
|
bool IsDebugOutputEnabled();
|
||||||
void SetDebugOutputParams(bool enabled, const char* channelFilter = nullptr, LOGLEVEL levelFilter = LOGLEVEL_TRACE);
|
void SetDebugOutputParams(bool enabled);
|
||||||
|
|
||||||
// adds a file output
|
// adds a file output
|
||||||
void SetFileOutputParams(bool enabled, const char* filename, bool timestamps = true,
|
void SetFileOutputParams(bool enabled, const char* filename, bool timestamps = true);
|
||||||
const char* channelFilter = nullptr, LOGLEVEL levelFilter = LOGLEVEL_TRACE);
|
|
||||||
|
|
||||||
// Sets global filtering level, messages below this level won't be sent to any of the logging sinks.
|
// Sets global filtering level, messages below this level won't be sent to any of the logging sinks.
|
||||||
void SetFilterLevel(LOGLEVEL level);
|
void SetLogLevel(LOGLEVEL level);
|
||||||
|
|
||||||
|
// Sets global filter, any messages from these channels won't be sent to any of the logging sinks.
|
||||||
|
void SetLogfilter(std::string_view filter);
|
||||||
|
|
||||||
// writes a message to the log
|
// writes a message to the log
|
||||||
void Write(const char* channelName, const char* functionName, LOGLEVEL level, std::string_view message);
|
void Write(const char* channelName, const char* functionName, LOGLEVEL level, std::string_view message);
|
||||||
|
@ -70,7 +75,7 @@ ALWAYS_INLINE static void WriteFmt(const char* channelName, const char* function
|
||||||
} // namespace Log
|
} // namespace Log
|
||||||
|
|
||||||
// log wrappers
|
// log wrappers
|
||||||
#define Log_SetChannel(ChannelName) static const char* ___LogChannel___ = #ChannelName;
|
#define Log_SetChannel(ChannelName) [[maybe_unused]] static const char* ___LogChannel___ = #ChannelName;
|
||||||
#define Log_ErrorPrint(msg) Log::Write(___LogChannel___, __func__, LOGLEVEL_ERROR, msg)
|
#define Log_ErrorPrint(msg) Log::Write(___LogChannel___, __func__, LOGLEVEL_ERROR, msg)
|
||||||
#define Log_ErrorPrintf(...) Log::Writef(___LogChannel___, __func__, LOGLEVEL_ERROR, __VA_ARGS__)
|
#define Log_ErrorPrintf(...) Log::Writef(___LogChannel___, __func__, LOGLEVEL_ERROR, __VA_ARGS__)
|
||||||
#define Log_ErrorFmt(...) Log::WriteFmt(___LogChannel___, __func__, LOGLEVEL_ERROR, __VA_ARGS__)
|
#define Log_ErrorFmt(...) Log::WriteFmt(___LogChannel___, __func__, LOGLEVEL_ERROR, __VA_ARGS__)
|
||||||
|
|
|
@ -295,7 +295,7 @@ static const SettingInfo s_settings[] = {
|
||||||
{SettingInfo::Type::String, "CrosshairColor", TRANSLATE_NOOP("GunCon", "Cursor Color"),
|
{SettingInfo::Type::String, "CrosshairColor", TRANSLATE_NOOP("GunCon", "Cursor Color"),
|
||||||
TRANSLATE_NOOP("GunCon", "Applies a color to the chosen crosshair images, can be used for multiple players. Specify "
|
TRANSLATE_NOOP("GunCon", "Applies a color to the chosen crosshair images, can be used for multiple players. Specify "
|
||||||
"in HTML/CSS format (e.g. #aabbcc)"),
|
"in HTML/CSS format (e.g. #aabbcc)"),
|
||||||
"#ffffff"},
|
"#ffffff", nullptr, nullptr, nullptr, nullptr, nullptr, 0.0f},
|
||||||
{SettingInfo::Type::Float, "XScale", TRANSLATE_NOOP("GunCon", "X Scale"),
|
{SettingInfo::Type::Float, "XScale", TRANSLATE_NOOP("GunCon", "X Scale"),
|
||||||
TRANSLATE_NOOP("GunCon", "Scales X coordinates relative to the center of the screen."), "1.0", "0.01", "2.0", "0.01",
|
TRANSLATE_NOOP("GunCon", "Scales X coordinates relative to the center of the screen."), "1.0", "0.01", "2.0", "0.01",
|
||||||
"%.0f%%", nullptr, 100.0f}};
|
"%.0f%%", nullptr, 100.0f}};
|
||||||
|
|
|
@ -355,6 +355,7 @@ void Settings::Load(SettingsInterface& si)
|
||||||
log_level = ParseLogLevelName(si.GetStringValue("Logging", "LogLevel", GetLogLevelName(DEFAULT_LOG_LEVEL)).c_str())
|
log_level = ParseLogLevelName(si.GetStringValue("Logging", "LogLevel", GetLogLevelName(DEFAULT_LOG_LEVEL)).c_str())
|
||||||
.value_or(DEFAULT_LOG_LEVEL);
|
.value_or(DEFAULT_LOG_LEVEL);
|
||||||
log_filter = si.GetStringValue("Logging", "LogFilter", "");
|
log_filter = si.GetStringValue("Logging", "LogFilter", "");
|
||||||
|
log_timestamps = si.GetBoolValue("Logging", "LogTimestamps", true);
|
||||||
log_to_console = si.GetBoolValue("Logging", "LogToConsole", DEFAULT_LOG_TO_CONSOLE);
|
log_to_console = si.GetBoolValue("Logging", "LogToConsole", DEFAULT_LOG_TO_CONSOLE);
|
||||||
log_to_debug = si.GetBoolValue("Logging", "LogToDebug", false);
|
log_to_debug = si.GetBoolValue("Logging", "LogToDebug", false);
|
||||||
log_to_window = si.GetBoolValue("Logging", "LogToWindow", false);
|
log_to_window = si.GetBoolValue("Logging", "LogToWindow", false);
|
||||||
|
@ -555,6 +556,7 @@ void Settings::Save(SettingsInterface& si) const
|
||||||
|
|
||||||
si.SetStringValue("Logging", "LogLevel", GetLogLevelName(log_level));
|
si.SetStringValue("Logging", "LogLevel", GetLogLevelName(log_level));
|
||||||
si.SetStringValue("Logging", "LogFilter", log_filter.c_str());
|
si.SetStringValue("Logging", "LogFilter", log_filter.c_str());
|
||||||
|
si.SetBoolValue("Logging", "LogTimestamps", log_timestamps);
|
||||||
si.SetBoolValue("Logging", "LogToConsole", log_to_console);
|
si.SetBoolValue("Logging", "LogToConsole", log_to_console);
|
||||||
si.SetBoolValue("Logging", "LogToDebug", log_to_debug);
|
si.SetBoolValue("Logging", "LogToDebug", log_to_debug);
|
||||||
si.SetBoolValue("Logging", "LogToWindow", log_to_window);
|
si.SetBoolValue("Logging", "LogToWindow", log_to_window);
|
||||||
|
@ -703,14 +705,15 @@ void Settings::FixIncompatibleSettings(bool display_osd_messages)
|
||||||
|
|
||||||
void Settings::UpdateLogSettings()
|
void Settings::UpdateLogSettings()
|
||||||
{
|
{
|
||||||
Log::SetFilterLevel(log_level);
|
Log::SetLogLevel(log_level);
|
||||||
Log::SetConsoleOutputParams(log_to_console, log_filter.empty() ? nullptr : log_filter.c_str(), log_level);
|
Log::SetLogfilter(log_filter);
|
||||||
Log::SetDebugOutputParams(log_to_debug, log_filter.empty() ? nullptr : log_filter.c_str(), log_level);
|
Log::SetConsoleOutputParams(log_to_console, log_timestamps);
|
||||||
|
Log::SetDebugOutputParams(log_to_debug);
|
||||||
|
|
||||||
if (log_to_file)
|
if (log_to_file)
|
||||||
{
|
{
|
||||||
Log::SetFileOutputParams(log_to_file, Path::Combine(EmuFolders::DataRoot, "duckstation.log").c_str(), true,
|
Log::SetFileOutputParams(log_to_file, Path::Combine(EmuFolders::DataRoot, "duckstation.log").c_str(),
|
||||||
log_filter.empty() ? nullptr : log_filter.c_str(), log_level);
|
log_timestamps);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -1528,3 +1531,132 @@ bool EmuFolders::EnsureFoldersExist()
|
||||||
result = FileSystem::EnsureDirectoryExists(Textures.c_str(), false) && result;
|
result = FileSystem::EnsureDirectoryExists(Textures.c_str(), false) && result;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static const char* s_log_filters[] = {
|
||||||
|
"Achievements",
|
||||||
|
"AnalogController",
|
||||||
|
"AnalogJoystick",
|
||||||
|
"AudioStream",
|
||||||
|
"AutoUpdaterDialog",
|
||||||
|
"BIOS",
|
||||||
|
"Bus",
|
||||||
|
"ByteStream",
|
||||||
|
"CDImage",
|
||||||
|
"CDImageBin",
|
||||||
|
"CDImageCHD",
|
||||||
|
"CDImageCueSheet",
|
||||||
|
"CDImageDevice",
|
||||||
|
"CDImageEcm",
|
||||||
|
"CDImageMds",
|
||||||
|
"CDImageMemory",
|
||||||
|
"CDImagePBP",
|
||||||
|
"CDImagePPF",
|
||||||
|
"CDROM",
|
||||||
|
"CDROMAsyncReader",
|
||||||
|
"CDSubChannelReplacement",
|
||||||
|
"CPU::CodeCache",
|
||||||
|
"CPU::Core",
|
||||||
|
"CPU::Recompiler",
|
||||||
|
"Common::PageFaultHandler",
|
||||||
|
"ControllerBindingWidget",
|
||||||
|
"CueParser",
|
||||||
|
"Cheats",
|
||||||
|
"DMA",
|
||||||
|
"DisplayWidget",
|
||||||
|
"FileSystem",
|
||||||
|
"FullscreenUI",
|
||||||
|
"GDBConnection",
|
||||||
|
"GDBProtocol",
|
||||||
|
"GDBServer",
|
||||||
|
"GPU",
|
||||||
|
"GPUBackend",
|
||||||
|
"GPUDevice",
|
||||||
|
"GPUShaderCache",
|
||||||
|
"GPUTexture",
|
||||||
|
"GPU_HW",
|
||||||
|
"GPU_SW",
|
||||||
|
"GameDatabase",
|
||||||
|
"GameList",
|
||||||
|
"GunCon",
|
||||||
|
"HTTPDownloader",
|
||||||
|
"Host",
|
||||||
|
"HostInterfaceProgressCallback",
|
||||||
|
"INISettingsInterface",
|
||||||
|
"ISOReader",
|
||||||
|
"ImGuiFullscreen",
|
||||||
|
"ImGuiManager",
|
||||||
|
"Image",
|
||||||
|
"InputManager",
|
||||||
|
"InterruptController",
|
||||||
|
"JitCodeBuffer",
|
||||||
|
"MDEC",
|
||||||
|
"MainWindow",
|
||||||
|
"MemoryArena",
|
||||||
|
"MemoryCard",
|
||||||
|
"Multitap",
|
||||||
|
"NoGUIHost",
|
||||||
|
"PCDrv",
|
||||||
|
"PGXP",
|
||||||
|
"PSFLoader",
|
||||||
|
"Pad",
|
||||||
|
"PlatformMisc",
|
||||||
|
"PlayStationMouse",
|
||||||
|
"PostProcessing",
|
||||||
|
"ProgressCallback",
|
||||||
|
"QTTranslations",
|
||||||
|
"QtHost",
|
||||||
|
"ReShadeFXShader",
|
||||||
|
"Recompiler::CodeGenerator",
|
||||||
|
"RegTestHost",
|
||||||
|
"SDLInputSource",
|
||||||
|
"SIO",
|
||||||
|
"SPIRVCompiler",
|
||||||
|
"SPU",
|
||||||
|
"Settings",
|
||||||
|
"ShaderGen",
|
||||||
|
"StateWrapper",
|
||||||
|
"System",
|
||||||
|
"TextureReplacements",
|
||||||
|
"Timers",
|
||||||
|
"TimingEvents",
|
||||||
|
"WAVWriter",
|
||||||
|
"WindowInfo",
|
||||||
|
|
||||||
|
#ifdef ENABLE_CUBEB
|
||||||
|
"CubebAudioStream",
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef ENABLE_OPENGL
|
||||||
|
"GL::Context",
|
||||||
|
"OpenGLDevice",
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef ENABLE_VULKAN
|
||||||
|
"VulkanDevice",
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(_WIN32)
|
||||||
|
"D3D11Device",
|
||||||
|
"D3D12Device",
|
||||||
|
"D3D12StreamBuffer",
|
||||||
|
"D3DCommon",
|
||||||
|
"DInputSource",
|
||||||
|
"Win32ProgressCallback",
|
||||||
|
"Win32RawInputSource",
|
||||||
|
"XAudio2AudioStream",
|
||||||
|
"XInputSource",
|
||||||
|
#elif defined(__APPLE__)
|
||||||
|
"CocoaNoGUIPlatform",
|
||||||
|
"CocoaProgressCallback",
|
||||||
|
"MetalDevice",
|
||||||
|
#else
|
||||||
|
"ContextEGLWayland",
|
||||||
|
"X11NoGUIPlatform",
|
||||||
|
"WaylandNoGUIPlatform",
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
|
std::span<const char*> Settings::GetLogFilters()
|
||||||
|
{
|
||||||
|
return s_log_filters;
|
||||||
|
}
|
||||||
|
|
|
@ -248,6 +248,7 @@ struct Settings
|
||||||
|
|
||||||
LOGLEVEL log_level = DEFAULT_LOG_LEVEL;
|
LOGLEVEL log_level = DEFAULT_LOG_LEVEL;
|
||||||
std::string log_filter;
|
std::string log_filter;
|
||||||
|
bool log_timestamps = true;
|
||||||
bool log_to_console = DEFAULT_LOG_TO_CONSOLE;
|
bool log_to_console = DEFAULT_LOG_TO_CONSOLE;
|
||||||
bool log_to_debug = false;
|
bool log_to_debug = false;
|
||||||
bool log_to_window = false;
|
bool log_to_window = false;
|
||||||
|
@ -345,6 +346,7 @@ struct Settings
|
||||||
static std::optional<LOGLEVEL> ParseLogLevelName(const char* str);
|
static std::optional<LOGLEVEL> ParseLogLevelName(const char* str);
|
||||||
static const char* GetLogLevelName(LOGLEVEL level);
|
static const char* GetLogLevelName(LOGLEVEL level);
|
||||||
static const char* GetLogLevelDisplayName(LOGLEVEL level);
|
static const char* GetLogLevelDisplayName(LOGLEVEL level);
|
||||||
|
static std::span<const char*> GetLogFilters();
|
||||||
|
|
||||||
static std::optional<ConsoleRegion> ParseConsoleRegionName(const char* str);
|
static std::optional<ConsoleRegion> ParseConsoleRegionName(const char* str);
|
||||||
static const char* GetConsoleRegionName(ConsoleRegion region);
|
static const char* GetConsoleRegionName(ConsoleRegion region);
|
||||||
|
|
|
@ -3692,6 +3692,7 @@ void System::CheckForSettingsChanges(const Settings& old_settings)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (g_settings.log_level != old_settings.log_level || g_settings.log_filter != old_settings.log_filter ||
|
if (g_settings.log_level != old_settings.log_level || g_settings.log_filter != old_settings.log_filter ||
|
||||||
|
g_settings.log_timestamps != old_settings.log_timestamps ||
|
||||||
g_settings.log_to_console != old_settings.log_to_console ||
|
g_settings.log_to_console != old_settings.log_to_console ||
|
||||||
g_settings.log_to_debug != old_settings.log_to_debug || g_settings.log_to_window != old_settings.log_to_window ||
|
g_settings.log_to_debug != old_settings.log_to_debug || g_settings.log_to_window != old_settings.log_to_window ||
|
||||||
g_settings.log_to_file != old_settings.log_to_file)
|
g_settings.log_to_file != old_settings.log_to_file)
|
||||||
|
|
|
@ -111,6 +111,8 @@ set(SRCS
|
||||||
inputbindingdialog.ui
|
inputbindingdialog.ui
|
||||||
inputbindingwidgets.cpp
|
inputbindingwidgets.cpp
|
||||||
inputbindingwidgets.h
|
inputbindingwidgets.h
|
||||||
|
logwindow.cpp
|
||||||
|
logwindow.h
|
||||||
mainwindow.cpp
|
mainwindow.cpp
|
||||||
mainwindow.h
|
mainwindow.h
|
||||||
mainwindow.ui
|
mainwindow.ui
|
||||||
|
|
|
@ -30,6 +30,7 @@
|
||||||
<ClCompile Include="hotkeysettingswidget.cpp" />
|
<ClCompile Include="hotkeysettingswidget.cpp" />
|
||||||
<ClCompile Include="inputbindingdialog.cpp" />
|
<ClCompile Include="inputbindingdialog.cpp" />
|
||||||
<ClCompile Include="inputbindingwidgets.cpp" />
|
<ClCompile Include="inputbindingwidgets.cpp" />
|
||||||
|
<ClCompile Include="logwindow.cpp" />
|
||||||
<ClCompile Include="memoryviewwidget.cpp" />
|
<ClCompile Include="memoryviewwidget.cpp" />
|
||||||
<ClCompile Include="displaywidget.cpp" />
|
<ClCompile Include="displaywidget.cpp" />
|
||||||
<ClCompile Include="gamelistsettingswidget.cpp" />
|
<ClCompile Include="gamelistsettingswidget.cpp" />
|
||||||
|
@ -86,6 +87,7 @@
|
||||||
<QtMoc Include="colorpickerbutton.h" />
|
<QtMoc Include="colorpickerbutton.h" />
|
||||||
<ClInclude Include="controllersettingwidgetbinder.h" />
|
<ClInclude Include="controllersettingwidgetbinder.h" />
|
||||||
<QtMoc Include="memoryviewwidget.h" />
|
<QtMoc Include="memoryviewwidget.h" />
|
||||||
|
<QtMoc Include="logwindow.h" />
|
||||||
<ClInclude Include="pch.h" />
|
<ClInclude Include="pch.h" />
|
||||||
<ClInclude Include="resource.h" />
|
<ClInclude Include="resource.h" />
|
||||||
<ClInclude Include="settingwidgetbinder.h" />
|
<ClInclude Include="settingwidgetbinder.h" />
|
||||||
|
@ -258,6 +260,7 @@
|
||||||
<ClCompile Include="$(IntDir)moc_hotkeysettingswidget.cpp" />
|
<ClCompile Include="$(IntDir)moc_hotkeysettingswidget.cpp" />
|
||||||
<ClCompile Include="$(IntDir)moc_inputbindingdialog.cpp" />
|
<ClCompile Include="$(IntDir)moc_inputbindingdialog.cpp" />
|
||||||
<ClCompile Include="$(IntDir)moc_inputbindingwidgets.cpp" />
|
<ClCompile Include="$(IntDir)moc_inputbindingwidgets.cpp" />
|
||||||
|
<ClCompile Include="$(IntDir)moc_logwindow.cpp" />
|
||||||
<ClCompile Include="$(IntDir)moc_mainwindow.cpp" />
|
<ClCompile Include="$(IntDir)moc_mainwindow.cpp" />
|
||||||
<ClCompile Include="$(IntDir)moc_memorycardsettingswidget.cpp" />
|
<ClCompile Include="$(IntDir)moc_memorycardsettingswidget.cpp" />
|
||||||
<ClCompile Include="$(IntDir)moc_memorycardeditordialog.cpp" />
|
<ClCompile Include="$(IntDir)moc_memorycardeditordialog.cpp" />
|
||||||
|
|
|
@ -93,6 +93,9 @@
|
||||||
<ClCompile Include="$(IntDir)moc_colorpickerbutton.cpp" />
|
<ClCompile Include="$(IntDir)moc_colorpickerbutton.cpp" />
|
||||||
<ClCompile Include="pch.cpp" />
|
<ClCompile Include="pch.cpp" />
|
||||||
<ClCompile Include="setupwizarddialog.cpp" />
|
<ClCompile Include="setupwizarddialog.cpp" />
|
||||||
|
<ClCompile Include="$(IntDir)moc_setupwizarddialog.cpp" />
|
||||||
|
<ClCompile Include="logwindow.cpp" />
|
||||||
|
<ClCompile Include="$(IntDir)moc_logwindow.cpp" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClInclude Include="qtutils.h" />
|
<ClInclude Include="qtutils.h" />
|
||||||
|
@ -154,6 +157,7 @@
|
||||||
<QtMoc Include="coverdownloaddialog.h" />
|
<QtMoc Include="coverdownloaddialog.h" />
|
||||||
<QtMoc Include="colorpickerbutton.h" />
|
<QtMoc Include="colorpickerbutton.h" />
|
||||||
<QtMoc Include="setupwizarddialog.h" />
|
<QtMoc Include="setupwizarddialog.h" />
|
||||||
|
<QtMoc Include="logwindow.h" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<QtUi Include="consolesettingswidget.ui" />
|
<QtUi Include="consolesettingswidget.ui" />
|
||||||
|
|
356
src/duckstation-qt/logwindow.cpp
Normal file
356
src/duckstation-qt/logwindow.cpp
Normal file
|
@ -0,0 +1,356 @@
|
||||||
|
// SPDX-FileCopyrightText: 2019-2023 Connor McLaughlin <stenzek@gmail.com>
|
||||||
|
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
|
||||||
|
|
||||||
|
#include "logwindow.h"
|
||||||
|
#include "mainwindow.h"
|
||||||
|
#include "qthost.h"
|
||||||
|
#include "settingwidgetbinder.h"
|
||||||
|
|
||||||
|
#include "util/host.h"
|
||||||
|
|
||||||
|
#include <QtCore/QLatin1StringView>
|
||||||
|
#include <QtCore/QUtf8StringView>
|
||||||
|
#include <QtGui/QIcon>
|
||||||
|
#include <QtWidgets/QMenuBar>
|
||||||
|
#include <QtWidgets/QScrollBar>
|
||||||
|
|
||||||
|
LogWindow* g_log_window;
|
||||||
|
|
||||||
|
LogWindow::LogWindow(bool attach_to_main)
|
||||||
|
: QMainWindow(), m_filter_names(Settings::GetLogFilters()), m_attached_to_main_window(attach_to_main)
|
||||||
|
{
|
||||||
|
// TODO: probably should save the size..
|
||||||
|
resize(700, 400);
|
||||||
|
|
||||||
|
createUi();
|
||||||
|
|
||||||
|
Log::RegisterCallback(&LogWindow::logCallback, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
LogWindow::~LogWindow()
|
||||||
|
{
|
||||||
|
Log::UnregisterCallback(&LogWindow::logCallback, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LogWindow::updateSettings()
|
||||||
|
{
|
||||||
|
const bool new_enabled = Host::GetBaseBoolSettingValue("Logging", "LogToWindow", false);
|
||||||
|
const bool attach_to_main = Host::GetBaseBoolSettingValue("Logging", "AttachLogWindowToMainWindow", true);
|
||||||
|
const bool curr_enabled = (g_log_window != nullptr);
|
||||||
|
if (new_enabled == curr_enabled)
|
||||||
|
{
|
||||||
|
if (g_log_window->m_attached_to_main_window != attach_to_main)
|
||||||
|
{
|
||||||
|
g_log_window->m_attached_to_main_window = attach_to_main;
|
||||||
|
if (attach_to_main)
|
||||||
|
g_log_window->reattachToMainWindow();
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (new_enabled)
|
||||||
|
{
|
||||||
|
g_log_window = new LogWindow(attach_to_main);
|
||||||
|
if (attach_to_main && g_main_window && g_main_window->isVisible())
|
||||||
|
g_log_window->reattachToMainWindow();
|
||||||
|
|
||||||
|
g_log_window->show();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
delete g_log_window;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void LogWindow::reattachToMainWindow()
|
||||||
|
{
|
||||||
|
// Skip when maximized.
|
||||||
|
if (g_main_window->windowState() & (Qt::WindowMaximized | Qt::WindowFullScreen))
|
||||||
|
return;
|
||||||
|
|
||||||
|
resize(width(), g_main_window->height());
|
||||||
|
|
||||||
|
const QPoint new_pos = g_main_window->pos() + QPoint(g_main_window->width() + 10, 0);
|
||||||
|
if (pos() != new_pos)
|
||||||
|
move(new_pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LogWindow::updateWindowTitle()
|
||||||
|
{
|
||||||
|
QString title;
|
||||||
|
|
||||||
|
const QString& serial = QtHost::GetCurrentGameSerial();
|
||||||
|
|
||||||
|
if (QtHost::IsSystemValid() && !serial.isEmpty())
|
||||||
|
{
|
||||||
|
const QFileInfo fi(QtHost::GetCurrentGamePath());
|
||||||
|
title = tr("Log Window - %1 [%2]").arg(serial).arg(fi.fileName());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
title = tr("Log Window");
|
||||||
|
}
|
||||||
|
|
||||||
|
setWindowTitle(title);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LogWindow::createUi()
|
||||||
|
{
|
||||||
|
QIcon icon;
|
||||||
|
icon.addFile(QString::fromUtf8(":/icons/duck.png"), QSize(), QIcon::Normal, QIcon::Off);
|
||||||
|
setWindowIcon(icon);
|
||||||
|
updateWindowTitle();
|
||||||
|
|
||||||
|
QAction* action;
|
||||||
|
|
||||||
|
QMenuBar* menu = new QMenuBar(this);
|
||||||
|
setMenuBar(menu);
|
||||||
|
|
||||||
|
QMenu* log_menu = menu->addMenu("&Log");
|
||||||
|
action = log_menu->addAction(tr("&Clear"));
|
||||||
|
connect(action, &QAction::triggered, this, &LogWindow::onClearTriggered);
|
||||||
|
action = log_menu->addAction(tr("&Save..."));
|
||||||
|
connect(action, &QAction::triggered, this, &LogWindow::onSaveTriggered);
|
||||||
|
|
||||||
|
log_menu->addSeparator();
|
||||||
|
|
||||||
|
action = log_menu->addAction(tr("Cl&ose"));
|
||||||
|
connect(action, &QAction::triggered, this, &LogWindow::close);
|
||||||
|
|
||||||
|
QMenu* settings_menu = menu->addMenu(tr("&Settings"));
|
||||||
|
|
||||||
|
action = settings_menu->addAction(tr("Log To &System Console"));
|
||||||
|
action->setCheckable(true);
|
||||||
|
SettingWidgetBinder::BindWidgetToBoolSetting(nullptr, action, "Logging", "LogToConsole", false);
|
||||||
|
|
||||||
|
action = settings_menu->addAction(tr("Log To &Debug Console"));
|
||||||
|
action->setCheckable(true);
|
||||||
|
SettingWidgetBinder::BindWidgetToBoolSetting(nullptr, action, "Logging", "LogToDebug", false);
|
||||||
|
|
||||||
|
action = settings_menu->addAction(tr("Log To &File"));
|
||||||
|
action->setCheckable(true);
|
||||||
|
SettingWidgetBinder::BindWidgetToBoolSetting(nullptr, action, "Logging", "LogToFile", false);
|
||||||
|
|
||||||
|
settings_menu->addSeparator();
|
||||||
|
|
||||||
|
action = settings_menu->addAction(tr("Attach To &Main Window"));
|
||||||
|
action->setCheckable(true);
|
||||||
|
SettingWidgetBinder::BindWidgetToBoolSetting(nullptr, action, "Logging", "AttachLogWindowToMainWindow", true);
|
||||||
|
|
||||||
|
action = settings_menu->addAction(tr("Show &Timestamps"));
|
||||||
|
action->setCheckable(true);
|
||||||
|
SettingWidgetBinder::BindWidgetToBoolSetting(nullptr, action, "Logging", "LogTimestamps", true);
|
||||||
|
|
||||||
|
settings_menu->addSeparator();
|
||||||
|
|
||||||
|
m_level_menu = settings_menu->addMenu(tr("&Log Level"));
|
||||||
|
for (u32 i = 0; i < static_cast<u32>(LOGLEVEL_COUNT); i++)
|
||||||
|
{
|
||||||
|
action = m_level_menu->addAction(QString::fromUtf8(Settings::GetLogLevelDisplayName(static_cast<LOGLEVEL>(i))));
|
||||||
|
action->setCheckable(true);
|
||||||
|
connect(action, &QAction::triggered, this, [this, i]() { setLogLevel(static_cast<LOGLEVEL>(i)); });
|
||||||
|
}
|
||||||
|
updateLogLevelUi();
|
||||||
|
|
||||||
|
QMenu* filters_menu = menu->addMenu(tr("&Filters"));
|
||||||
|
populateFilters(filters_menu);
|
||||||
|
|
||||||
|
m_text = new QPlainTextEdit(this);
|
||||||
|
m_text->setReadOnly(true);
|
||||||
|
m_text->setUndoRedoEnabled(false);
|
||||||
|
m_text->setTextInteractionFlags(Qt::TextSelectableByKeyboard);
|
||||||
|
m_text->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
|
||||||
|
|
||||||
|
#ifndef _WIN32
|
||||||
|
QFont font("Monospace");
|
||||||
|
font.setStyleHint(QFont::TypeWriter);
|
||||||
|
#else
|
||||||
|
QFont font("Consolas");
|
||||||
|
font.setPointSize(10);
|
||||||
|
#endif
|
||||||
|
m_text->setFont(font);
|
||||||
|
|
||||||
|
setCentralWidget(m_text);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LogWindow::updateLogLevelUi()
|
||||||
|
{
|
||||||
|
const u32 level = Settings::ParseLogLevelName(Host::GetBaseStringSettingValue("Logging", "LogLevel", "").c_str())
|
||||||
|
.value_or(Settings::DEFAULT_LOG_LEVEL);
|
||||||
|
|
||||||
|
const QList<QAction*> actions = m_level_menu->actions();
|
||||||
|
for (u32 i = 0; i < actions.size(); i++)
|
||||||
|
actions[i]->setChecked(i == level);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LogWindow::setLogLevel(LOGLEVEL level)
|
||||||
|
{
|
||||||
|
Host::SetBaseStringSettingValue("Logging", "LogLevel", Settings::GetLogLevelName(level));
|
||||||
|
Host::CommitBaseSettingChanges();
|
||||||
|
g_emu_thread->applySettings(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LogWindow::populateFilters(QMenu* filter_menu)
|
||||||
|
{
|
||||||
|
const std::string filters = Host::GetBaseStringSettingValue("Logging", "LogFilter", "");
|
||||||
|
for (size_t i = 0; i < m_filter_names.size(); i++)
|
||||||
|
{
|
||||||
|
const char* filter = m_filter_names[i];
|
||||||
|
const bool is_currently_filtered = (filters.find(filter) == std::string::npos);
|
||||||
|
QAction* action = filter_menu->addAction(QString::fromUtf8(filter));
|
||||||
|
action->setCheckable(action);
|
||||||
|
action->setChecked(is_currently_filtered);
|
||||||
|
connect(action, &QAction::triggered, this, [this, i](bool checked) { setChannelFiltered(i, !checked); });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void LogWindow::setChannelFiltered(size_t index, bool enabled)
|
||||||
|
{
|
||||||
|
const char* filter = m_filter_names[index];
|
||||||
|
const size_t filter_len = std::strlen(filter);
|
||||||
|
|
||||||
|
std::string filters = Host::GetBaseStringSettingValue("Logging", "LogFilter", "");
|
||||||
|
const std::string::size_type pos = filters.find(filter);
|
||||||
|
|
||||||
|
if (!enabled)
|
||||||
|
{
|
||||||
|
if (pos == std::string::npos)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const size_t erase_count =
|
||||||
|
filter_len + (((pos + filter_len) < filters.length() && filters[pos + filter_len] == ' ') ? 1 : 0);
|
||||||
|
filters.erase(pos, erase_count);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (pos != std::string::npos)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!filters.empty() && filters.back() != ' ')
|
||||||
|
filters.push_back(' ');
|
||||||
|
filters.append(filter);
|
||||||
|
}
|
||||||
|
|
||||||
|
Host::SetBaseStringSettingValue("Logging", "LogFilter", filters.c_str());
|
||||||
|
Host::CommitBaseSettingChanges();
|
||||||
|
g_emu_thread->applySettings(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LogWindow::onClearTriggered()
|
||||||
|
{
|
||||||
|
m_text->clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void LogWindow::onSaveTriggered()
|
||||||
|
{
|
||||||
|
const QString path = QFileDialog::getSaveFileName(this, tr("Select Log File"), QString(), tr("Log Files (*.txt)"));
|
||||||
|
if (path.isEmpty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
QFile file(path);
|
||||||
|
if (!file.open(QFile::WriteOnly | QFile::Text))
|
||||||
|
{
|
||||||
|
QMessageBox::critical(this, tr("Error"), tr("Failed to open file for writing."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
file.write(m_text->toPlainText().toUtf8());
|
||||||
|
file.close();
|
||||||
|
|
||||||
|
appendMessage(QLatin1StringView("LogWindow"), LOGLEVEL_INFO, tr("Log was written to %1.\n").arg(path));
|
||||||
|
}
|
||||||
|
|
||||||
|
void LogWindow::logCallback(void* pUserParam, const char* channelName, const char* functionName, LOGLEVEL level,
|
||||||
|
std::string_view message)
|
||||||
|
{
|
||||||
|
LogWindow* this_ptr = static_cast<LogWindow*>(pUserParam);
|
||||||
|
|
||||||
|
// TODO: Split message based on lines.
|
||||||
|
// I don't like the memory allocations here either...
|
||||||
|
|
||||||
|
QString qmessage;
|
||||||
|
qmessage.reserve(message.length() + 1);
|
||||||
|
qmessage.append(QUtf8StringView(message.data(), message.length()));
|
||||||
|
qmessage.append(QChar('\n'));
|
||||||
|
|
||||||
|
const QLatin1StringView qchannel((level <= LOGLEVEL_PERF) ? functionName : channelName);
|
||||||
|
|
||||||
|
if (g_emu_thread->isOnUIThread())
|
||||||
|
{
|
||||||
|
this_ptr->appendMessage(qchannel, level, qmessage);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
QMetaObject::invokeMethod(this_ptr, "appendMessage", Qt::QueuedConnection,
|
||||||
|
Q_ARG(const QLatin1StringView&, qchannel), Q_ARG(quint32, static_cast<u32>(level)),
|
||||||
|
Q_ARG(const QString&, qmessage));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void LogWindow::appendMessage(const QLatin1StringView& channel, quint32 level, const QString& message)
|
||||||
|
{
|
||||||
|
QTextCursor temp_cursor = m_text->textCursor();
|
||||||
|
QScrollBar* scrollbar = m_text->verticalScrollBar();
|
||||||
|
const bool cursor_at_end = temp_cursor.atEnd();
|
||||||
|
const bool scroll_at_end = scrollbar->sliderPosition() == scrollbar->maximum();
|
||||||
|
|
||||||
|
temp_cursor.movePosition(QTextCursor::End);
|
||||||
|
|
||||||
|
{
|
||||||
|
static constexpr const QChar level_characters[LOGLEVEL_COUNT] = {'X', 'E', 'W', 'P', 'I', 'V', 'D', 'R', 'B', 'T'};
|
||||||
|
static constexpr const QColor level_colors[LOGLEVEL_COUNT] = {
|
||||||
|
QColor(255, 255, 255), // NONE
|
||||||
|
QColor(0xE7, 0x48, 0x56), // ERROR, Red Intensity
|
||||||
|
QColor(0xF9, 0xF1, 0xA5), // WARNING, Yellow Intensity
|
||||||
|
QColor(0xB4, 0x00, 0x9E), // PERF, Purple Intensity
|
||||||
|
QColor(0xF2, 0xF2, 0xF2), // INFO, White Intensity
|
||||||
|
QColor(0x16, 0xC6, 0x0C), // VERBOSE, Green Intensity
|
||||||
|
QColor(0xCC, 0xCC, 0xCC), // DEV, White
|
||||||
|
QColor(0x61, 0xD6, 0xD6), // PROFILE, Cyan Intensity
|
||||||
|
QColor(0x13, 0xA1, 0x0E), // DEBUG, Green
|
||||||
|
QColor(0x00, 0x37, 0xDA), // TRACE, Blue
|
||||||
|
};
|
||||||
|
static constexpr const QColor timestamp_color = QColor(0xcc, 0xcc, 0xcc);
|
||||||
|
static constexpr const QColor channel_color = QColor(0xf2, 0xf2, 0xf2);
|
||||||
|
|
||||||
|
QTextCharFormat format = temp_cursor.charFormat();
|
||||||
|
|
||||||
|
if (g_settings.log_timestamps)
|
||||||
|
{
|
||||||
|
const float message_time = Log::GetCurrentMessageTime();
|
||||||
|
const QString qtimestamp = QStringLiteral("[%1] ").arg(message_time, 10, 'f', 4);
|
||||||
|
format.setForeground(QBrush(timestamp_color));
|
||||||
|
temp_cursor.setCharFormat(format);
|
||||||
|
temp_cursor.insertText(qtimestamp);
|
||||||
|
}
|
||||||
|
|
||||||
|
const QString qchannel = (level <= LOGLEVEL_PERF) ?
|
||||||
|
QStringLiteral("%1(%2): ").arg(level_characters[level]).arg(channel) :
|
||||||
|
QStringLiteral("%1/%2: ").arg(level_characters[level]).arg(channel);
|
||||||
|
format.setForeground(QBrush(channel_color));
|
||||||
|
temp_cursor.setCharFormat(format);
|
||||||
|
temp_cursor.insertText(qchannel);
|
||||||
|
|
||||||
|
// message has \n already
|
||||||
|
format.setForeground(QBrush(level_colors[level]));
|
||||||
|
temp_cursor.setCharFormat(format);
|
||||||
|
temp_cursor.insertText(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cursor_at_end)
|
||||||
|
{
|
||||||
|
if (scroll_at_end)
|
||||||
|
{
|
||||||
|
m_text->setTextCursor(temp_cursor);
|
||||||
|
scrollbar->setSliderPosition(scrollbar->maximum());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Can't let changing the cursor affect the scroll bar...
|
||||||
|
const int pos = scrollbar->sliderPosition();
|
||||||
|
m_text->setTextCursor(temp_cursor);
|
||||||
|
scrollbar->setSliderPosition(pos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
50
src/duckstation-qt/logwindow.h
Normal file
50
src/duckstation-qt/logwindow.h
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
// SPDX-FileCopyrightText: 2019-2023 Connor McLaughlin <stenzek@gmail.com>
|
||||||
|
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "common/log.h"
|
||||||
|
|
||||||
|
#include <span>
|
||||||
|
#include <QtWidgets/QMainWindow>
|
||||||
|
#include <QtWidgets/QPlainTextEdit>
|
||||||
|
|
||||||
|
class LogWindow : public QMainWindow
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
LogWindow(bool attach_to_main);
|
||||||
|
~LogWindow();
|
||||||
|
|
||||||
|
static void updateSettings();
|
||||||
|
|
||||||
|
ALWAYS_INLINE bool isAttachedToMainWindow() const { return m_attached_to_main_window; }
|
||||||
|
void reattachToMainWindow();
|
||||||
|
|
||||||
|
void updateWindowTitle();
|
||||||
|
|
||||||
|
private:
|
||||||
|
void createUi();
|
||||||
|
void updateLogLevelUi();
|
||||||
|
void setLogLevel(LOGLEVEL level);
|
||||||
|
void populateFilters(QMenu* filter_menu);
|
||||||
|
void setChannelFiltered(size_t index, bool state);
|
||||||
|
|
||||||
|
static void logCallback(void* pUserParam, const char* channelName, const char* functionName, LOGLEVEL level,
|
||||||
|
std::string_view message);
|
||||||
|
|
||||||
|
private Q_SLOTS:
|
||||||
|
void onClearTriggered();
|
||||||
|
void onSaveTriggered();
|
||||||
|
void appendMessage(const QLatin1StringView& channel, quint32 level, const QString& message);
|
||||||
|
|
||||||
|
private:
|
||||||
|
QPlainTextEdit* m_text;
|
||||||
|
QMenu* m_level_menu;
|
||||||
|
std::span<const char*> m_filter_names;
|
||||||
|
|
||||||
|
bool m_attached_to_main_window = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
extern LogWindow* g_log_window;
|
|
@ -12,6 +12,7 @@
|
||||||
#include "gamelistsettingswidget.h"
|
#include "gamelistsettingswidget.h"
|
||||||
#include "gamelistwidget.h"
|
#include "gamelistwidget.h"
|
||||||
#include "generalsettingswidget.h"
|
#include "generalsettingswidget.h"
|
||||||
|
#include "logwindow.h"
|
||||||
#include "memorycardeditordialog.h"
|
#include "memorycardeditordialog.h"
|
||||||
#include "qthost.h"
|
#include "qthost.h"
|
||||||
#include "qtutils.h"
|
#include "qtutils.h"
|
||||||
|
@ -83,6 +84,9 @@ static bool s_use_central_widget = false;
|
||||||
// UI thread VM validity.
|
// UI thread VM validity.
|
||||||
static bool s_system_valid = false;
|
static bool s_system_valid = false;
|
||||||
static bool s_system_paused = false;
|
static bool s_system_paused = false;
|
||||||
|
static QString s_current_game_title;
|
||||||
|
static QString s_current_game_serial;
|
||||||
|
static QString s_current_game_path;
|
||||||
|
|
||||||
bool QtHost::IsSystemPaused()
|
bool QtHost::IsSystemPaused()
|
||||||
{
|
{
|
||||||
|
@ -94,6 +98,21 @@ bool QtHost::IsSystemValid()
|
||||||
return s_system_valid;
|
return s_system_valid;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const QString& QtHost::GetCurrentGameTitle()
|
||||||
|
{
|
||||||
|
return s_current_game_title;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QString& QtHost::GetCurrentGameSerial()
|
||||||
|
{
|
||||||
|
return s_current_game_serial;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QString& QtHost::GetCurrentGamePath()
|
||||||
|
{
|
||||||
|
return s_current_game_path;
|
||||||
|
}
|
||||||
|
|
||||||
MainWindow::MainWindow() : QMainWindow(nullptr)
|
MainWindow::MainWindow() : QMainWindow(nullptr)
|
||||||
{
|
{
|
||||||
Assert(!g_main_window);
|
Assert(!g_main_window);
|
||||||
|
@ -102,6 +121,8 @@ MainWindow::MainWindow() : QMainWindow(nullptr)
|
||||||
#if !defined(_WIN32) && !defined(__APPLE__)
|
#if !defined(_WIN32) && !defined(__APPLE__)
|
||||||
s_use_central_widget = DisplayContainer::isRunningOnWayland();
|
s_use_central_widget = DisplayContainer::isRunningOnWayland();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
initialize();
|
||||||
}
|
}
|
||||||
|
|
||||||
MainWindow::~MainWindow()
|
MainWindow::~MainWindow()
|
||||||
|
@ -596,12 +617,11 @@ void MainWindow::onSystemDestroyed()
|
||||||
|
|
||||||
void MainWindow::onRunningGameChanged(const QString& filename, const QString& game_serial, const QString& game_title)
|
void MainWindow::onRunningGameChanged(const QString& filename, const QString& game_serial, const QString& game_title)
|
||||||
{
|
{
|
||||||
m_current_game_path = filename;
|
s_current_game_path = filename;
|
||||||
m_current_game_title = game_title;
|
s_current_game_title = game_title;
|
||||||
m_current_game_serial = game_serial;
|
s_current_game_serial = game_serial;
|
||||||
|
|
||||||
updateWindowTitle();
|
updateWindowTitle();
|
||||||
// updateSaveStateMenus(path, serial, crc);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::onApplicationStateChanged(Qt::ApplicationState state)
|
void MainWindow::onApplicationStateChanged(Qt::ApplicationState state)
|
||||||
|
@ -934,7 +954,7 @@ void MainWindow::populateChangeDiscSubImageMenu(QMenu* menu, QActionGroup* actio
|
||||||
QAction* action = action_group->addAction(QString::fromStdString(title));
|
QAction* action = action_group->addAction(QString::fromStdString(title));
|
||||||
QString path = QString::fromStdString(glentry->path);
|
QString path = QString::fromStdString(glentry->path);
|
||||||
action->setCheckable(true);
|
action->setCheckable(true);
|
||||||
action->setChecked(path == m_current_game_path);
|
action->setChecked(path == s_current_game_path);
|
||||||
connect(action, &QAction::triggered, [path = std::move(path)]() { g_emu_thread->changeDisc(path); });
|
connect(action, &QAction::triggered, [path = std::move(path)]() { g_emu_thread->changeDisc(path); });
|
||||||
menu->addAction(action);
|
menu->addAction(action);
|
||||||
}
|
}
|
||||||
|
@ -1165,12 +1185,12 @@ void MainWindow::onChangeDiscMenuAboutToHide()
|
||||||
|
|
||||||
void MainWindow::onLoadStateMenuAboutToShow()
|
void MainWindow::onLoadStateMenuAboutToShow()
|
||||||
{
|
{
|
||||||
populateLoadStateMenu(m_current_game_serial.toUtf8().constData(), m_ui.menuLoadState);
|
populateLoadStateMenu(s_current_game_serial.toUtf8().constData(), m_ui.menuLoadState);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::onSaveStateMenuAboutToShow()
|
void MainWindow::onSaveStateMenuAboutToShow()
|
||||||
{
|
{
|
||||||
populateSaveStateMenu(m_current_game_serial.toUtf8().constData(), m_ui.menuSaveState);
|
populateSaveStateMenu(s_current_game_serial.toUtf8().constData(), m_ui.menuSaveState);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::onCheatsMenuAboutToShow()
|
void MainWindow::onCheatsMenuAboutToShow()
|
||||||
|
@ -1742,9 +1762,9 @@ void MainWindow::updateWindowTitle()
|
||||||
{
|
{
|
||||||
QString suffix(QtHost::GetAppConfigSuffix());
|
QString suffix(QtHost::GetAppConfigSuffix());
|
||||||
QString main_title(QtHost::GetAppNameAndVersion() + suffix);
|
QString main_title(QtHost::GetAppNameAndVersion() + suffix);
|
||||||
QString display_title(m_current_game_title + suffix);
|
QString display_title(s_current_game_title + suffix);
|
||||||
|
|
||||||
if (!s_system_valid || m_current_game_title.isEmpty())
|
if (!s_system_valid || s_current_game_title.isEmpty())
|
||||||
display_title = main_title;
|
display_title = main_title;
|
||||||
else if (isRenderingToMain())
|
else if (isRenderingToMain())
|
||||||
main_title = display_title;
|
main_title = display_title;
|
||||||
|
@ -1759,6 +1779,9 @@ void MainWindow::updateWindowTitle()
|
||||||
if (container->windowTitle() != display_title)
|
if (container->windowTitle() != display_title)
|
||||||
container->setWindowTitle(display_title);
|
container->setWindowTitle(display_title);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (g_log_window)
|
||||||
|
g_log_window->updateWindowTitle();
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::updateWindowState(bool force_visible)
|
void MainWindow::updateWindowState(bool force_visible)
|
||||||
|
@ -2474,6 +2497,22 @@ void MainWindow::dropEvent(QDropEvent* event)
|
||||||
startFileOrChangeDisc(qfilename);
|
startFileOrChangeDisc(qfilename);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MainWindow::moveEvent(QMoveEvent* event)
|
||||||
|
{
|
||||||
|
QMainWindow::moveEvent(event);
|
||||||
|
|
||||||
|
if (g_log_window && g_log_window->isAttachedToMainWindow())
|
||||||
|
g_log_window->reattachToMainWindow();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWindow::resizeEvent(QResizeEvent* event)
|
||||||
|
{
|
||||||
|
QMainWindow::resizeEvent(event);
|
||||||
|
|
||||||
|
if (g_log_window && g_log_window->isAttachedToMainWindow())
|
||||||
|
g_log_window->reattachToMainWindow();
|
||||||
|
}
|
||||||
|
|
||||||
void MainWindow::startupUpdateCheck()
|
void MainWindow::startupUpdateCheck()
|
||||||
{
|
{
|
||||||
if (!Host::GetBaseBoolSettingValue("AutoUpdater", "CheckAtStartup", true))
|
if (!Host::GetBaseBoolSettingValue("AutoUpdater", "CheckAtStartup", true))
|
||||||
|
@ -2510,7 +2549,7 @@ bool MainWindow::requestShutdown(bool allow_confirm /* = true */, bool allow_sav
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
// If we don't have a serial, we can't save state.
|
// If we don't have a serial, we can't save state.
|
||||||
allow_save_to_state &= !m_current_game_serial.isEmpty();
|
allow_save_to_state &= !s_current_game_serial.isEmpty();
|
||||||
save_state &= allow_save_to_state;
|
save_state &= allow_save_to_state;
|
||||||
|
|
||||||
// Only confirm on UI thread because we need to display a msgbox.
|
// Only confirm on UI thread because we need to display a msgbox.
|
||||||
|
@ -2574,6 +2613,7 @@ void MainWindow::checkForSettingChanges()
|
||||||
m_display_widget->updateRelativeMode(s_system_valid && !s_system_paused);
|
m_display_widget->updateRelativeMode(s_system_valid && !s_system_paused);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
LogWindow::updateSettings();
|
||||||
updateWindowState();
|
updateWindowState();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -78,9 +78,6 @@ public:
|
||||||
/// Sets application theme according to settings.
|
/// Sets application theme according to settings.
|
||||||
static void updateApplicationTheme();
|
static void updateApplicationTheme();
|
||||||
|
|
||||||
/// Initializes the window. Call once at startup.
|
|
||||||
void initialize();
|
|
||||||
|
|
||||||
/// Performs update check if enabled in settings.
|
/// Performs update check if enabled in settings.
|
||||||
void startupUpdateCheck();
|
void startupUpdateCheck();
|
||||||
|
|
||||||
|
@ -186,6 +183,8 @@ protected:
|
||||||
void changeEvent(QEvent* event) override;
|
void changeEvent(QEvent* event) override;
|
||||||
void dragEnterEvent(QDragEnterEvent* event) override;
|
void dragEnterEvent(QDragEnterEvent* event) override;
|
||||||
void dropEvent(QDropEvent* event) override;
|
void dropEvent(QDropEvent* event) override;
|
||||||
|
void moveEvent(QMoveEvent* event) override;
|
||||||
|
void resizeEvent(QResizeEvent* event) override;
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
bool nativeEvent(const QByteArray& eventType, void* message, qintptr* result) override;
|
bool nativeEvent(const QByteArray& eventType, void* message, qintptr* result) override;
|
||||||
|
@ -194,6 +193,10 @@ protected:
|
||||||
private:
|
private:
|
||||||
static void setStyleFromSettings();
|
static void setStyleFromSettings();
|
||||||
static void setIconThemeFromSettings();
|
static void setIconThemeFromSettings();
|
||||||
|
|
||||||
|
/// Initializes the window. Call once at startup.
|
||||||
|
void initialize();
|
||||||
|
|
||||||
void setupAdditionalUi();
|
void setupAdditionalUi();
|
||||||
void connectSignals();
|
void connectSignals();
|
||||||
|
|
||||||
|
@ -285,10 +288,6 @@ private:
|
||||||
CheatManagerDialog* m_cheat_manager_dialog = nullptr;
|
CheatManagerDialog* m_cheat_manager_dialog = nullptr;
|
||||||
DebuggerWindow* m_debugger_window = nullptr;
|
DebuggerWindow* m_debugger_window = nullptr;
|
||||||
|
|
||||||
QString m_current_game_path;
|
|
||||||
QString m_current_game_title;
|
|
||||||
QString m_current_game_serial;
|
|
||||||
|
|
||||||
bool m_was_paused_by_focus_loss = false;
|
bool m_was_paused_by_focus_loss = false;
|
||||||
bool m_open_debugger_on_start = false;
|
bool m_open_debugger_on_start = false;
|
||||||
bool m_relative_mouse_mode = false;
|
bool m_relative_mouse_mode = false;
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
#include "qthost.h"
|
#include "qthost.h"
|
||||||
#include "autoupdaterdialog.h"
|
#include "autoupdaterdialog.h"
|
||||||
#include "displaywidget.h"
|
#include "displaywidget.h"
|
||||||
|
#include "logwindow.h"
|
||||||
#include "mainwindow.h"
|
#include "mainwindow.h"
|
||||||
#include "qtprogresscallback.h"
|
#include "qtprogresscallback.h"
|
||||||
#include "qtutils.h"
|
#include "qtutils.h"
|
||||||
|
@ -201,7 +202,7 @@ bool QtHost::InitializeConfig(std::string settings_filename)
|
||||||
if (!Log::IsConsoleOutputEnabled() &&
|
if (!Log::IsConsoleOutputEnabled() &&
|
||||||
s_base_settings_interface->GetBoolValue("Logging", "LogToConsole", Settings::DEFAULT_LOG_TO_CONSOLE))
|
s_base_settings_interface->GetBoolValue("Logging", "LogToConsole", Settings::DEFAULT_LOG_TO_CONSOLE))
|
||||||
{
|
{
|
||||||
Log::SetConsoleOutputParams(true, nullptr, LOGLEVEL_NONE);
|
Log::SetConsoleOutputParams(true, s_base_settings_interface->GetBoolValue("Logging", "LogTimestamps", true));
|
||||||
}
|
}
|
||||||
|
|
||||||
// TEMPORARY: Migrate controller settings to new interface.
|
// TEMPORARY: Migrate controller settings to new interface.
|
||||||
|
@ -2028,6 +2029,9 @@ int main(int argc, char* argv[])
|
||||||
// Set theme before creating any windows.
|
// Set theme before creating any windows.
|
||||||
MainWindow::updateApplicationTheme();
|
MainWindow::updateApplicationTheme();
|
||||||
|
|
||||||
|
// Start logging early.
|
||||||
|
LogWindow::updateSettings();
|
||||||
|
|
||||||
// Start up the CPU thread.
|
// Start up the CPU thread.
|
||||||
QtHost::HookSignals();
|
QtHost::HookSignals();
|
||||||
EmuThread::start();
|
EmuThread::start();
|
||||||
|
@ -2043,7 +2047,6 @@ int main(int argc, char* argv[])
|
||||||
|
|
||||||
// Create all window objects, the emuthread might still be starting up at this point.
|
// Create all window objects, the emuthread might still be starting up at this point.
|
||||||
main_window = new MainWindow();
|
main_window = new MainWindow();
|
||||||
main_window->initialize();
|
|
||||||
|
|
||||||
// When running in batch mode, ensure game list is loaded, but don't scan for any new files.
|
// When running in batch mode, ensure game list is loaded, but don't scan for any new files.
|
||||||
if (!s_batch_mode)
|
if (!s_batch_mode)
|
||||||
|
|
|
@ -87,6 +87,7 @@ public:
|
||||||
static void stop();
|
static void stop();
|
||||||
|
|
||||||
ALWAYS_INLINE bool isOnThread() const { return QThread::currentThread() == this; }
|
ALWAYS_INLINE bool isOnThread() const { return QThread::currentThread() == this; }
|
||||||
|
ALWAYS_INLINE bool isOnUIThread() const { return QThread::currentThread() == m_ui_thread; }
|
||||||
|
|
||||||
ALWAYS_INLINE QEventLoop* getEventLoop() const { return m_event_loop; }
|
ALWAYS_INLINE QEventLoop* getEventLoop() const { return m_event_loop; }
|
||||||
|
|
||||||
|
@ -276,4 +277,9 @@ void QueueSettingsSave();
|
||||||
/// VM state, safe to access on UI thread.
|
/// VM state, safe to access on UI thread.
|
||||||
bool IsSystemValid();
|
bool IsSystemValid();
|
||||||
bool IsSystemPaused();
|
bool IsSystemPaused();
|
||||||
|
|
||||||
|
/// Accessors for game information.
|
||||||
|
const QString& GetCurrentGameTitle();
|
||||||
|
const QString& GetCurrentGameSerial();
|
||||||
|
const QString& GetCurrentGamePath();
|
||||||
} // namespace QtHost
|
} // namespace QtHost
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
#include "common/assert.h"
|
#include "common/assert.h"
|
||||||
#include "common/log.h"
|
#include "common/log.h"
|
||||||
#include <dlfcn.h>
|
#include <dlfcn.h>
|
||||||
Log_SetChannel(GL::ContextAGL);
|
Log_SetChannel(GL::Context);
|
||||||
|
|
||||||
namespace GL {
|
namespace GL {
|
||||||
ContextAGL::ContextAGL(const WindowInfo& wi) : Context(wi)
|
ContextAGL::ContextAGL(const WindowInfo& wi) : Context(wi)
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
Log_SetChannel(GL::ContextEGL);
|
Log_SetChannel(GL::Context);
|
||||||
|
|
||||||
namespace GL {
|
namespace GL {
|
||||||
ContextEGL::ContextEGL(const WindowInfo& wi) : Context(wi)
|
ContextEGL::ContextEGL(const WindowInfo& wi) : Context(wi)
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
|
|
||||||
#include <dlfcn.h>
|
#include <dlfcn.h>
|
||||||
|
|
||||||
Log_SetChannel(ContextEGLWayland);
|
Log_SetChannel(ContextEGL);
|
||||||
|
|
||||||
namespace GL {
|
namespace GL {
|
||||||
static const char* WAYLAND_EGL_MODNAME = "libwayland-egl.so.1";
|
static const char* WAYLAND_EGL_MODNAME = "libwayland-egl.so.1";
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
#include "common/log.h"
|
#include "common/log.h"
|
||||||
#include "common/scoped_guard.h"
|
#include "common/scoped_guard.h"
|
||||||
|
|
||||||
Log_SetChannel(GL::ContextWGL);
|
Log_SetChannel(GL::Context);
|
||||||
|
|
||||||
#ifdef __clang__
|
#ifdef __clang__
|
||||||
#pragma clang diagnostic ignored "-Wmicrosoft-cast"
|
#pragma clang diagnostic ignored "-Wmicrosoft-cast"
|
||||||
|
|
|
@ -21,7 +21,7 @@
|
||||||
|
|
||||||
#include <cerrno>
|
#include <cerrno>
|
||||||
|
|
||||||
Log_SetChannel(OpenGLPipeline);
|
Log_SetChannel(OpenGLDevice);
|
||||||
|
|
||||||
struct PipelineDiskCacheFooter
|
struct PipelineDiskCacheFooter
|
||||||
{
|
{
|
||||||
|
|
Loading…
Reference in a new issue