From 4ad777f54f8a3190ad5e9aa248a24f4c56584ab8 Mon Sep 17 00:00:00 2001 From: Stenzek Date: Sat, 30 Sep 2023 14:40:50 +1000 Subject: [PATCH] Qt: Add log window --- src/common/http_downloader_curl.cpp | 2 +- src/common/http_downloader_winhttp.cpp | 2 +- src/common/log.cpp | 285 ++++++++------ src/common/log.h | 17 +- src/core/guncon.cpp | 2 +- src/core/settings.cpp | 142 ++++++- src/core/settings.h | 2 + src/core/system.cpp | 1 + src/duckstation-qt/CMakeLists.txt | 2 + src/duckstation-qt/duckstation-qt.vcxproj | 5 +- .../duckstation-qt.vcxproj.filters | 6 +- src/duckstation-qt/logwindow.cpp | 356 ++++++++++++++++++ src/duckstation-qt/logwindow.h | 50 +++ src/duckstation-qt/mainwindow.cpp | 60 ++- src/duckstation-qt/mainwindow.h | 13 +- src/duckstation-qt/qthost.cpp | 7 +- src/duckstation-qt/qthost.h | 6 + src/util/gl/context_agl.mm | 2 +- src/util/gl/context_egl.cpp | 2 +- src/util/gl/context_egl_wayland.cpp | 2 +- src/util/gl/context_wgl.cpp | 2 +- src/util/opengl_pipeline.cpp | 2 +- 22 files changed, 808 insertions(+), 160 deletions(-) create mode 100644 src/duckstation-qt/logwindow.cpp create mode 100644 src/duckstation-qt/logwindow.h diff --git a/src/common/http_downloader_curl.cpp b/src/common/http_downloader_curl.cpp index b055451fa..828ba772a 100644 --- a/src/common/http_downloader_curl.cpp +++ b/src/common/http_downloader_curl.cpp @@ -7,7 +7,7 @@ #include #include #include -Log_SetChannel(HTTPDownloaderCurl); +Log_SetChannel(HTTPDownloader); namespace Common { diff --git a/src/common/http_downloader_winhttp.cpp b/src/common/http_downloader_winhttp.cpp index f746daedf..18d381bec 100644 --- a/src/common/http_downloader_winhttp.cpp +++ b/src/common/http_downloader_winhttp.cpp @@ -8,7 +8,7 @@ #include "timer.h" #include #include -Log_SetChannel(HTTPDownloaderWinHttp); +Log_SetChannel(HTTPDownloader); namespace Common { diff --git a/src/common/log.cpp b/src/common/log.cpp index ed9e58f71..8473b1daf 100644 --- a/src/common/log.cpp +++ b/src/common/log.cpp @@ -23,40 +23,65 @@ 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 { Log::CallbackFunctionType Function; void* Parameter; }; +} // namespace -std::vector s_callbacks; +static void RegisterCallback(CallbackFunctionType callbackFunction, void* pUserParam, + const std::unique_lock& lock); +static void UnregisterCallback(CallbackFunctionType callbackFunction, void* pUserParam, + const std::unique_lock& lock); +static bool FilterTest(LOGLEVEL level, const char* channelName, const std::unique_lock& lock); +static void ExecuteCallbacks(const char* channelName, const char* functionName, LOGLEVEL level, + std::string_view message, const std::unique_lock& 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 +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 +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 s_callbacks; static std::mutex s_callback_mutex; -static LOGLEVEL s_filter_level = LOGLEVEL_TRACE; - -static Common::Timer::Value s_startTimeStamp = Common::Timer::GetCurrentValue(); +static Common::Timer::Value s_start_timestamp = Common::Timer::GetCurrentValue(); +static std::string s_log_filter; +static LOGLEVEL s_log_level = LOGLEVEL_TRACE; static bool s_console_output_enabled = false; -static std::string s_console_output_channel_filter; -static LOGLEVEL s_console_output_level_filter = LOGLEVEL_TRACE; +static bool s_console_output_timestamps = true; +static bool s_file_output_enabled = false; +static bool s_file_output_timestamp = false; +static bool s_debug_output_enabled = false; #ifdef _WIN32 static HANDLE s_hConsoleStdIn = NULL; static HANDLE s_hConsoleStdOut = NULL; static HANDLE s_hConsoleStdErr = NULL; #endif +} // namespace Log -static bool s_debug_output_enabled = false; -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 s_fileOutputHandle(nullptr, [](std::FILE* fp) { +std::unique_ptr s_file_handle(nullptr, [](std::FILE* fp) { if (fp) { std::fclose(fp); @@ -64,19 +89,30 @@ std::unique_ptr s_fileOutputHandle(nullptr, []( }); 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& lock) { RegisteredCallback Callback; Callback.Function = callbackFunction; Callback.Parameter = pUserParam; - std::lock_guard guard(s_callback_mutex); s_callbacks.push_back(std::move(Callback)); } void Log::UnregisterCallback(CallbackFunctionType callbackFunction, void* pUserParam) { - std::lock_guard 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& lock) +{ for (auto iter = s_callbacks.begin(); iter != s_callbacks.end(); ++iter) { if (iter->Function == callbackFunction && iter->Parameter == pUserParam) @@ -87,6 +123,11 @@ void Log::UnregisterCallback(CallbackFunctionType callbackFunction, void* pUserP } } +float Log::GetCurrentMessageTime() +{ + return static_cast(Common::Timer::ConvertValueToSeconds(Common::Timer::GetCurrentValue() - s_start_timestamp)); +} + bool Log::IsConsoleOutputEnabled() { return s_console_output_enabled; @@ -97,18 +138,17 @@ bool Log::IsDebugOutputEnabled() return s_debug_output_enabled; } -static void ExecuteCallbacks(const char* channelName, const char* functionName, LOGLEVEL level, - std::string_view message) +void Log::ExecuteCallbacks(const char* channelName, const char* functionName, LOGLEVEL level, std::string_view message, + const std::unique_lock& lock) { - std::lock_guard guard(s_callback_mutex); for (RegisteredCallback& callback : s_callbacks) callback.Function(callback.Parameter, channelName, functionName, level, message); } -ALWAYS_INLINE_RELEASE 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) +ALWAYS_INLINE_RELEASE void Log::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 constexpr std::string_view s_ansi_color_codes[LOGLEVEL_COUNT] = { "\033[0m"sv, // NONE @@ -132,8 +172,7 @@ ALWAYS_INLINE_RELEASE static void FormatLogMessageForDisplay(fmt::memory_buffer& if (timestamp) { // find time since start of process - const float message_time = - static_cast(Common::Timer::ConvertValueToSeconds(Common::Timer::GetCurrentValue() - s_startTimeStamp)); + const float message_time = Log::GetCurrentMessageTime(); if (level <= LOGLEVEL_PERF) { @@ -162,24 +201,26 @@ ALWAYS_INLINE_RELEASE static void FormatLogMessageForDisplay(fmt::memory_buffer& } template -ALWAYS_INLINE_RELEASE 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) +ALWAYS_INLINE_RELEASE void Log::FormatLogMessageAndPrint(const char* channelName, const char* functionName, + LOGLEVEL level, std::string_view message, bool timestamp, + bool ansi_color_code, bool newline, const T& callback) { 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())); } #ifdef _WIN32 template -ALWAYS_INLINE_RELEASE 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) +ALWAYS_INLINE_RELEASE void Log::FormatLogMessageAndPrintW(const char* channelName, const char* functionName, + LOGLEVEL level, std::string_view message, bool timestamp, + bool ansi_color_code, bool newline, const T& callback) { 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 // anyway... @@ -219,24 +260,22 @@ static bool EnableVirtualTerminalProcessing(HANDLE hConsole) #endif -static void ConsoleOutputLogCallback(void* pUserParam, const char* channelName, const char* functionName, - LOGLEVEL level, std::string_view message) +void Log::ConsoleOutputLogCallback(void* pUserParam, const char* channelName, const char* functionName, LOGLEVEL level, + std::string_view message) { - if (!s_console_output_enabled || level > s_console_output_level_filter || - s_console_output_channel_filter.find(channelName) != std::string::npos) - { + if (!s_console_output_enabled) return; - } #if defined(_WIN32) - FormatLogMessageAndPrintW( - channelName, functionName, level, message, true, true, true, [level](const std::wstring_view& message) { - HANDLE hOutput = (level <= LOGLEVEL_WARNING) ? s_hConsoleStdErr : s_hConsoleStdOut; - DWORD chars_written; - WriteConsoleW(hOutput, message.data(), static_cast(message.length()), &chars_written, nullptr); - }); + FormatLogMessageAndPrintW(channelName, functionName, level, message, s_console_output_timestamps, true, true, + [level](const std::wstring_view& message) { + HANDLE hOutput = (level <= LOGLEVEL_WARNING) ? s_hConsoleStdErr : s_hConsoleStdOut; + DWORD chars_written; + WriteConsoleW(hOutput, message.data(), static_cast(message.length()), + &chars_written, nullptr); + }); #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) { const int outputFd = (level <= LOGLEVEL_WARNING) ? STDERR_FILENO : STDOUT_FILENO; write(outputFd, message.data(), message.length()); @@ -244,17 +283,14 @@ static void ConsoleOutputLogCallback(void* pUserParam, const char* channelName, #endif } -static void DebugOutputLogCallback(void* pUserParam, const char* channelName, const char* functionName, LOGLEVEL level, - std::string_view message) +void Log::DebugOutputLogCallback(void* pUserParam, const char* channelName, const char* functionName, LOGLEVEL level, + std::string_view message) { - if (!s_debug_output_enabled || level > s_debug_output_level_filter || - s_debug_output_channel_filter.find(functionName) != std::string::npos) - { + if (!s_debug_output_enabled) return; - } #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()); }); #elif defined(__ANDROID__) static const int logPriority[LOGLEVEL_COUNT] = { @@ -275,15 +311,15 @@ static void DebugOutputLogCallback(void* pUserParam, const char* channelName, co #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 : ""; - s_console_output_level_filter = LevelFilter; + std::unique_lock lock(s_callback_mutex); - if (s_console_output_enabled == Enabled) + s_console_output_enabled = timestamps; + if (s_console_output_enabled == enabled) return; - s_console_output_enabled = Enabled; + s_console_output_enabled = enabled; #if defined(_WIN32) // 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_stderr = NULL; - if (Enabled) + if (enabled) { old_stdin = GetStdHandle(STD_INPUT_HANDLE); old_stdout = GetStdHandle(STD_OUTPUT_HANDLE); @@ -349,90 +385,95 @@ void Log::SetConsoleOutputParams(bool Enabled, const char* ChannelFilter, LOGLEV } #endif - if (Enabled) - RegisterCallback(ConsoleOutputLogCallback, nullptr); + if (enabled) + RegisterCallback(ConsoleOutputLogCallback, nullptr, lock); else - UnregisterCallback(ConsoleOutputLogCallback, nullptr); + UnregisterCallback(ConsoleOutputLogCallback, nullptr, lock); } -void Log::SetDebugOutputParams(bool enabled, const char* channelFilter /* = nullptr */, - LOGLEVEL levelFilter /* = LOGLEVEL_TRACE */) +void Log::SetDebugOutputParams(bool enabled) { - if (s_debug_output_enabled != enabled) - { - s_debug_output_enabled = enabled; - if (enabled) - RegisterCallback(DebugOutputLogCallback, nullptr); - else - UnregisterCallback(DebugOutputLogCallback, nullptr); - } + std::unique_lock lock(s_callback_mutex); + if (s_debug_output_enabled == enabled) + return; - s_debug_output_channel_filter = (channelFilter != nullptr) ? channelFilter : ""; - s_debug_output_level_filter = levelFilter; + s_debug_output_enabled = enabled; + if (enabled) + RegisterCallback(DebugOutputLogCallback, nullptr, lock); + else + UnregisterCallback(DebugOutputLogCallback, nullptr, lock); } -static void FileOutputLogCallback(void* pUserParam, const char* channelName, const char* functionName, LOGLEVEL level, - std::string_view message) +void Log::FileOutputLogCallback(void* pUserParam, const char* channelName, const char* functionName, LOGLEVEL level, + 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; FormatLogMessageAndPrint( 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 */, - const char* channelFilter /* = nullptr */, LOGLEVEL levelFilter /* = LOGLEVEL_TRACE */) +void Log::SetFileOutputParams(bool enabled, const char* filename, bool timestamps /* = true */) { - 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")); - if (!s_fileOutputHandle) - { - Log::Writef("Log", __FUNCTION__, LOGLEVEL_ERROR, "Failed to open log file '%s'", filename); - return; - } - - RegisterCallback(FileOutputLogCallback, nullptr); - } - else - { - UnregisterCallback(FileOutputLogCallback, nullptr); - s_fileOutputHandle.reset(); + Log::Writef("Log", __FUNCTION__, LOGLEVEL_ERROR, "Failed to open log file '%s'", filename); + return; } - s_file_output_enabled = enabled; + RegisterCallback(FileOutputLogCallback, nullptr, lock); + } + else + { + UnregisterCallback(FileOutputLogCallback, nullptr, lock); + s_file_handle.reset(); } - std::lock_guard guard(s_callback_mutex); - s_file_output_channel_filter = (channelFilter != nullptr) ? channelFilter : ""; - s_file_output_level_filter = levelFilter; + s_file_output_enabled = enabled; 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); - 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& 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) { - if (level > s_filter_level) + std::unique_lock lock(s_callback_mutex); + if (!FilterTest(level, channelName, lock)) 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, ...) { - if (level > s_filter_level) - return; - - va_list ap; + std::va_list ap; va_start(ap, format); Writev(channelName, functionName, level, format, 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) { - if (level > s_filter_level) + std::unique_lock lock(s_callback_mutex); + if (!FilterTest(level, channelName, lock)) return; - va_list apCopy; + std::va_list apCopy; va_copy(apCopy, ap); #ifdef _WIN32 @@ -453,17 +495,19 @@ void Log::Writev(const char* channelName, const char* functionName, LOGLEVEL lev #endif va_end(apCopy); - if (requiredSize < 256) + if (requiredSize < 512) { - char buffer[256]; - std::vsnprintf(buffer, countof(buffer), format, ap); - ExecuteCallbacks(channelName, functionName, level, buffer); + char buffer[512]; + const int len = std::vsnprintf(buffer, countof(buffer), format, ap); + if (len > 0) + ExecuteCallbacks(channelName, functionName, level, std::string_view(buffer, static_cast(len)), lock); } else { char* buffer = new char[requiredSize + 1]; - std::vsnprintf(buffer, requiredSize + 1, format, ap); - ExecuteCallbacks(channelName, functionName, level, buffer); + const int len = std::vsnprintf(buffer, requiredSize + 1, format, ap); + if (len > 0) + ExecuteCallbacks(channelName, functionName, level, std::string_view(buffer, static_cast(len)), lock); 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, fmt::format_args args) { - if (level > s_filter_level) + std::unique_lock lock(s_callback_mutex); + if (!FilterTest(level, channelName, lock)) return; fmt::memory_buffer buffer; 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); } diff --git a/src/common/log.h b/src/common/log.h index f036ed117..5daf909a6 100644 --- a/src/common/log.h +++ b/src/common/log.h @@ -38,20 +38,25 @@ void RegisterCallback(CallbackFunctionType callbackFunction, void* pUserParam); // unregisters a log callback void UnregisterCallback(CallbackFunctionType callbackFunction, void* pUserParam); +// returns the time in seconds since the start of the process +float GetCurrentMessageTime(); + // adds a standard console output 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] bool IsDebugOutputEnabled(); -void SetDebugOutputParams(bool enabled, const char* channelFilter = nullptr, LOGLEVEL levelFilter = LOGLEVEL_TRACE); +void SetDebugOutputParams(bool enabled); // adds a file output -void SetFileOutputParams(bool enabled, const char* filename, bool timestamps = true, - const char* channelFilter = nullptr, LOGLEVEL levelFilter = LOGLEVEL_TRACE); +void SetFileOutputParams(bool enabled, const char* filename, bool timestamps = true); // 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 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 // 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_ErrorPrintf(...) Log::Writef(___LogChannel___, __func__, LOGLEVEL_ERROR, __VA_ARGS__) #define Log_ErrorFmt(...) Log::WriteFmt(___LogChannel___, __func__, LOGLEVEL_ERROR, __VA_ARGS__) diff --git a/src/core/guncon.cpp b/src/core/guncon.cpp index 9e461c12d..c6c59c732 100644 --- a/src/core/guncon.cpp +++ b/src/core/guncon.cpp @@ -295,7 +295,7 @@ static const SettingInfo s_settings[] = { {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 " "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"), 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}}; diff --git a/src/core/settings.cpp b/src/core/settings.cpp index 0caf5e31c..b3ef6f667 100644 --- a/src/core/settings.cpp +++ b/src/core/settings.cpp @@ -355,6 +355,7 @@ void Settings::Load(SettingsInterface& si) log_level = ParseLogLevelName(si.GetStringValue("Logging", "LogLevel", GetLogLevelName(DEFAULT_LOG_LEVEL)).c_str()) .value_or(DEFAULT_LOG_LEVEL); 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_debug = si.GetBoolValue("Logging", "LogToDebug", 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", "LogFilter", log_filter.c_str()); + si.SetBoolValue("Logging", "LogTimestamps", log_timestamps); si.SetBoolValue("Logging", "LogToConsole", log_to_console); si.SetBoolValue("Logging", "LogToDebug", log_to_debug); si.SetBoolValue("Logging", "LogToWindow", log_to_window); @@ -703,14 +705,15 @@ void Settings::FixIncompatibleSettings(bool display_osd_messages) void Settings::UpdateLogSettings() { - Log::SetFilterLevel(log_level); - Log::SetConsoleOutputParams(log_to_console, log_filter.empty() ? nullptr : log_filter.c_str(), log_level); - Log::SetDebugOutputParams(log_to_debug, log_filter.empty() ? nullptr : log_filter.c_str(), log_level); + Log::SetLogLevel(log_level); + Log::SetLogfilter(log_filter); + Log::SetConsoleOutputParams(log_to_console, log_timestamps); + Log::SetDebugOutputParams(log_to_debug); if (log_to_file) { - Log::SetFileOutputParams(log_to_file, Path::Combine(EmuFolders::DataRoot, "duckstation.log").c_str(), true, - log_filter.empty() ? nullptr : log_filter.c_str(), log_level); + Log::SetFileOutputParams(log_to_file, Path::Combine(EmuFolders::DataRoot, "duckstation.log").c_str(), + log_timestamps); } else { @@ -1528,3 +1531,132 @@ bool EmuFolders::EnsureFoldersExist() result = FileSystem::EnsureDirectoryExists(Textures.c_str(), false) && 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 Settings::GetLogFilters() +{ + return s_log_filters; +} diff --git a/src/core/settings.h b/src/core/settings.h index 35e3665df..efaf0da48 100644 --- a/src/core/settings.h +++ b/src/core/settings.h @@ -248,6 +248,7 @@ struct Settings LOGLEVEL log_level = DEFAULT_LOG_LEVEL; std::string log_filter; + bool log_timestamps = true; bool log_to_console = DEFAULT_LOG_TO_CONSOLE; bool log_to_debug = false; bool log_to_window = false; @@ -345,6 +346,7 @@ struct Settings static std::optional ParseLogLevelName(const char* str); static const char* GetLogLevelName(LOGLEVEL level); static const char* GetLogLevelDisplayName(LOGLEVEL level); + static std::span GetLogFilters(); static std::optional ParseConsoleRegionName(const char* str); static const char* GetConsoleRegionName(ConsoleRegion region); diff --git a/src/core/system.cpp b/src/core/system.cpp index 472f95bdc..669181dbb 100644 --- a/src/core/system.cpp +++ b/src/core/system.cpp @@ -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 || + g_settings.log_timestamps != old_settings.log_timestamps || 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_file != old_settings.log_to_file) diff --git a/src/duckstation-qt/CMakeLists.txt b/src/duckstation-qt/CMakeLists.txt index 25654dc07..6917524e8 100644 --- a/src/duckstation-qt/CMakeLists.txt +++ b/src/duckstation-qt/CMakeLists.txt @@ -111,6 +111,8 @@ set(SRCS inputbindingdialog.ui inputbindingwidgets.cpp inputbindingwidgets.h + logwindow.cpp + logwindow.h mainwindow.cpp mainwindow.h mainwindow.ui diff --git a/src/duckstation-qt/duckstation-qt.vcxproj b/src/duckstation-qt/duckstation-qt.vcxproj index b860d8d24..7933e84ba 100644 --- a/src/duckstation-qt/duckstation-qt.vcxproj +++ b/src/duckstation-qt/duckstation-qt.vcxproj @@ -30,6 +30,7 @@ + @@ -86,6 +87,7 @@ + @@ -258,6 +260,7 @@ + @@ -388,4 +391,4 @@ - + \ No newline at end of file diff --git a/src/duckstation-qt/duckstation-qt.vcxproj.filters b/src/duckstation-qt/duckstation-qt.vcxproj.filters index c11d09ab6..32813400a 100644 --- a/src/duckstation-qt/duckstation-qt.vcxproj.filters +++ b/src/duckstation-qt/duckstation-qt.vcxproj.filters @@ -93,6 +93,9 @@ + + + @@ -154,6 +157,7 @@ + @@ -262,4 +266,4 @@ translations - + \ No newline at end of file diff --git a/src/duckstation-qt/logwindow.cpp b/src/duckstation-qt/logwindow.cpp new file mode 100644 index 000000000..3decd0821 --- /dev/null +++ b/src/duckstation-qt/logwindow.cpp @@ -0,0 +1,356 @@ +// SPDX-FileCopyrightText: 2019-2023 Connor McLaughlin +// 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 +#include +#include +#include +#include + +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(LOGLEVEL_COUNT); i++) + { + action = m_level_menu->addAction(QString::fromUtf8(Settings::GetLogLevelDisplayName(static_cast(i)))); + action->setCheckable(true); + connect(action, &QAction::triggered, this, [this, i]() { setLogLevel(static_cast(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 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(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(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); + } + } +} diff --git a/src/duckstation-qt/logwindow.h b/src/duckstation-qt/logwindow.h new file mode 100644 index 000000000..45efd7059 --- /dev/null +++ b/src/duckstation-qt/logwindow.h @@ -0,0 +1,50 @@ +// SPDX-FileCopyrightText: 2019-2023 Connor McLaughlin +// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0) + +#pragma once + +#include "common/log.h" + +#include +#include +#include + +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 m_filter_names; + + bool m_attached_to_main_window = true; +}; + +extern LogWindow* g_log_window; diff --git a/src/duckstation-qt/mainwindow.cpp b/src/duckstation-qt/mainwindow.cpp index 98eebe17d..8d75c56ec 100644 --- a/src/duckstation-qt/mainwindow.cpp +++ b/src/duckstation-qt/mainwindow.cpp @@ -12,6 +12,7 @@ #include "gamelistsettingswidget.h" #include "gamelistwidget.h" #include "generalsettingswidget.h" +#include "logwindow.h" #include "memorycardeditordialog.h" #include "qthost.h" #include "qtutils.h" @@ -83,6 +84,9 @@ static bool s_use_central_widget = false; // UI thread VM validity. static bool s_system_valid = 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() { @@ -94,6 +98,21 @@ bool QtHost::IsSystemValid() 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) { Assert(!g_main_window); @@ -102,6 +121,8 @@ MainWindow::MainWindow() : QMainWindow(nullptr) #if !defined(_WIN32) && !defined(__APPLE__) s_use_central_widget = DisplayContainer::isRunningOnWayland(); #endif + + initialize(); } MainWindow::~MainWindow() @@ -596,12 +617,11 @@ void MainWindow::onSystemDestroyed() void MainWindow::onRunningGameChanged(const QString& filename, const QString& game_serial, const QString& game_title) { - m_current_game_path = filename; - m_current_game_title = game_title; - m_current_game_serial = game_serial; + s_current_game_path = filename; + s_current_game_title = game_title; + s_current_game_serial = game_serial; updateWindowTitle(); - // updateSaveStateMenus(path, serial, crc); } 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)); QString path = QString::fromStdString(glentry->path); 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); }); menu->addAction(action); } @@ -1165,12 +1185,12 @@ void MainWindow::onChangeDiscMenuAboutToHide() 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() { - populateSaveStateMenu(m_current_game_serial.toUtf8().constData(), m_ui.menuSaveState); + populateSaveStateMenu(s_current_game_serial.toUtf8().constData(), m_ui.menuSaveState); } void MainWindow::onCheatsMenuAboutToShow() @@ -1742,9 +1762,9 @@ void MainWindow::updateWindowTitle() { QString suffix(QtHost::GetAppConfigSuffix()); 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; else if (isRenderingToMain()) main_title = display_title; @@ -1759,6 +1779,9 @@ void MainWindow::updateWindowTitle() if (container->windowTitle() != display_title) container->setWindowTitle(display_title); } + + if (g_log_window) + g_log_window->updateWindowTitle(); } void MainWindow::updateWindowState(bool force_visible) @@ -2474,6 +2497,22 @@ void MainWindow::dropEvent(QDropEvent* event) 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() { if (!Host::GetBaseBoolSettingValue("AutoUpdater", "CheckAtStartup", true)) @@ -2510,7 +2549,7 @@ bool MainWindow::requestShutdown(bool allow_confirm /* = true */, bool allow_sav return true; // 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; // 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); #endif + LogWindow::updateSettings(); updateWindowState(); } diff --git a/src/duckstation-qt/mainwindow.h b/src/duckstation-qt/mainwindow.h index 2493b27d4..d52a3cd6a 100644 --- a/src/duckstation-qt/mainwindow.h +++ b/src/duckstation-qt/mainwindow.h @@ -78,9 +78,6 @@ public: /// Sets application theme according to settings. static void updateApplicationTheme(); - /// Initializes the window. Call once at startup. - void initialize(); - /// Performs update check if enabled in settings. void startupUpdateCheck(); @@ -186,6 +183,8 @@ protected: void changeEvent(QEvent* event) override; void dragEnterEvent(QDragEnterEvent* event) override; void dropEvent(QDropEvent* event) override; + void moveEvent(QMoveEvent* event) override; + void resizeEvent(QResizeEvent* event) override; #ifdef _WIN32 bool nativeEvent(const QByteArray& eventType, void* message, qintptr* result) override; @@ -194,6 +193,10 @@ protected: private: static void setStyleFromSettings(); static void setIconThemeFromSettings(); + + /// Initializes the window. Call once at startup. + void initialize(); + void setupAdditionalUi(); void connectSignals(); @@ -285,10 +288,6 @@ private: CheatManagerDialog* m_cheat_manager_dialog = 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_open_debugger_on_start = false; bool m_relative_mouse_mode = false; diff --git a/src/duckstation-qt/qthost.cpp b/src/duckstation-qt/qthost.cpp index 4f9651881..8f75534ed 100644 --- a/src/duckstation-qt/qthost.cpp +++ b/src/duckstation-qt/qthost.cpp @@ -4,6 +4,7 @@ #include "qthost.h" #include "autoupdaterdialog.h" #include "displaywidget.h" +#include "logwindow.h" #include "mainwindow.h" #include "qtprogresscallback.h" #include "qtutils.h" @@ -201,7 +202,7 @@ bool QtHost::InitializeConfig(std::string settings_filename) if (!Log::IsConsoleOutputEnabled() && 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. @@ -2028,6 +2029,9 @@ int main(int argc, char* argv[]) // Set theme before creating any windows. MainWindow::updateApplicationTheme(); + // Start logging early. + LogWindow::updateSettings(); + // Start up the CPU thread. QtHost::HookSignals(); 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. 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. if (!s_batch_mode) diff --git a/src/duckstation-qt/qthost.h b/src/duckstation-qt/qthost.h index 6fe2ebd2e..81329e674 100644 --- a/src/duckstation-qt/qthost.h +++ b/src/duckstation-qt/qthost.h @@ -87,6 +87,7 @@ public: static void stop(); 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; } @@ -276,4 +277,9 @@ void QueueSettingsSave(); /// VM state, safe to access on UI thread. bool IsSystemValid(); bool IsSystemPaused(); + +/// Accessors for game information. +const QString& GetCurrentGameTitle(); +const QString& GetCurrentGameSerial(); +const QString& GetCurrentGamePath(); } // namespace QtHost diff --git a/src/util/gl/context_agl.mm b/src/util/gl/context_agl.mm index 4cd76a711..17a3c333d 100644 --- a/src/util/gl/context_agl.mm +++ b/src/util/gl/context_agl.mm @@ -5,7 +5,7 @@ #include "common/assert.h" #include "common/log.h" #include -Log_SetChannel(GL::ContextAGL); +Log_SetChannel(GL::Context); namespace GL { ContextAGL::ContextAGL(const WindowInfo& wi) : Context(wi) diff --git a/src/util/gl/context_egl.cpp b/src/util/gl/context_egl.cpp index f5576e376..fccf7fc67 100644 --- a/src/util/gl/context_egl.cpp +++ b/src/util/gl/context_egl.cpp @@ -9,7 +9,7 @@ #include #include -Log_SetChannel(GL::ContextEGL); +Log_SetChannel(GL::Context); namespace GL { ContextEGL::ContextEGL(const WindowInfo& wi) : Context(wi) diff --git a/src/util/gl/context_egl_wayland.cpp b/src/util/gl/context_egl_wayland.cpp index bca5d5bf1..d5b08e89b 100644 --- a/src/util/gl/context_egl_wayland.cpp +++ b/src/util/gl/context_egl_wayland.cpp @@ -7,7 +7,7 @@ #include -Log_SetChannel(ContextEGLWayland); +Log_SetChannel(ContextEGL); namespace GL { static const char* WAYLAND_EGL_MODNAME = "libwayland-egl.so.1"; diff --git a/src/util/gl/context_wgl.cpp b/src/util/gl/context_wgl.cpp index fe4c878b9..c89604d4c 100644 --- a/src/util/gl/context_wgl.cpp +++ b/src/util/gl/context_wgl.cpp @@ -8,7 +8,7 @@ #include "common/log.h" #include "common/scoped_guard.h" -Log_SetChannel(GL::ContextWGL); +Log_SetChannel(GL::Context); #ifdef __clang__ #pragma clang diagnostic ignored "-Wmicrosoft-cast" diff --git a/src/util/opengl_pipeline.cpp b/src/util/opengl_pipeline.cpp index 0677a0a3e..4f894be17 100644 --- a/src/util/opengl_pipeline.cpp +++ b/src/util/opengl_pipeline.cpp @@ -21,7 +21,7 @@ #include -Log_SetChannel(OpenGLPipeline); +Log_SetChannel(OpenGLDevice); struct PipelineDiskCacheFooter {