mirror of
				https://github.com/PabloMK7/citra.git
				synced 2025-10-31 05:40:04 +00:00 
			
		
		
		
	Logging: Add customizable logging backends and fmtlib based macros
* Change the logging backend to support multiple sinks through the Backend Interface * Add a new set of logging macros to use fmtlib instead. * Qt: Compile as GUI application on windows to make the console hidden by default. Add filter configuration and a button to open log location. * SDL: Migrate to the new logging macros
This commit is contained in:
		
							parent
							
								
									3cda637cb1
								
							
						
					
					
						commit
						0daac3020e
					
				
					 25 changed files with 527 additions and 140 deletions
				
			
		|  | @ -25,6 +25,7 @@ | ||||||
| 
 | 
 | ||||||
| #include "citra/config.h" | #include "citra/config.h" | ||||||
| #include "citra/emu_window/emu_window_sdl2.h" | #include "citra/emu_window/emu_window_sdl2.h" | ||||||
|  | #include "common/common_paths.h" | ||||||
| #include "common/file_util.h" | #include "common/file_util.h" | ||||||
| #include "common/logging/backend.h" | #include "common/logging/backend.h" | ||||||
| #include "common/logging/filter.h" | #include "common/logging/filter.h" | ||||||
|  | @ -60,39 +61,39 @@ static void PrintVersion() { | ||||||
| static void OnStateChanged(const Network::RoomMember::State& state) { | static void OnStateChanged(const Network::RoomMember::State& state) { | ||||||
|     switch (state) { |     switch (state) { | ||||||
|     case Network::RoomMember::State::Idle: |     case Network::RoomMember::State::Idle: | ||||||
|         LOG_DEBUG(Network, "Network is idle"); |         NGLOG_DEBUG(Network, "Network is idle"); | ||||||
|         break; |         break; | ||||||
|     case Network::RoomMember::State::Joining: |     case Network::RoomMember::State::Joining: | ||||||
|         LOG_DEBUG(Network, "Connection sequence to room started"); |         NGLOG_DEBUG(Network, "Connection sequence to room started"); | ||||||
|         break; |         break; | ||||||
|     case Network::RoomMember::State::Joined: |     case Network::RoomMember::State::Joined: | ||||||
|         LOG_DEBUG(Network, "Successfully joined to the room"); |         NGLOG_DEBUG(Network, "Successfully joined to the room"); | ||||||
|         break; |         break; | ||||||
|     case Network::RoomMember::State::LostConnection: |     case Network::RoomMember::State::LostConnection: | ||||||
|         LOG_DEBUG(Network, "Lost connection to the room"); |         NGLOG_DEBUG(Network, "Lost connection to the room"); | ||||||
|         break; |         break; | ||||||
|     case Network::RoomMember::State::CouldNotConnect: |     case Network::RoomMember::State::CouldNotConnect: | ||||||
|         LOG_ERROR(Network, "State: CouldNotConnect"); |         NGLOG_ERROR(Network, "State: CouldNotConnect"); | ||||||
|         exit(1); |         exit(1); | ||||||
|         break; |         break; | ||||||
|     case Network::RoomMember::State::NameCollision: |     case Network::RoomMember::State::NameCollision: | ||||||
|         LOG_ERROR( |         NGLOG_ERROR( | ||||||
|             Network, |             Network, | ||||||
|             "You tried to use the same nickname then another user that is connected to the Room"); |             "You tried to use the same nickname then another user that is connected to the Room"); | ||||||
|         exit(1); |         exit(1); | ||||||
|         break; |         break; | ||||||
|     case Network::RoomMember::State::MacCollision: |     case Network::RoomMember::State::MacCollision: | ||||||
|         LOG_ERROR(Network, "You tried to use the same MAC-Address then another user that is " |         NGLOG_ERROR(Network, "You tried to use the same MAC-Address then another user that is " | ||||||
|                            "connected to the Room"); |                              "connected to the Room"); | ||||||
|         exit(1); |         exit(1); | ||||||
|         break; |         break; | ||||||
|     case Network::RoomMember::State::WrongPassword: |     case Network::RoomMember::State::WrongPassword: | ||||||
|         LOG_ERROR(Network, "Room replied with: Wrong password"); |         NGLOG_ERROR(Network, "Room replied with: Wrong password"); | ||||||
|         exit(1); |         exit(1); | ||||||
|         break; |         break; | ||||||
|     case Network::RoomMember::State::WrongVersion: |     case Network::RoomMember::State::WrongVersion: | ||||||
|         LOG_ERROR(Network, |         NGLOG_ERROR(Network, | ||||||
|                   "You are using a different version then the room you are trying to connect to"); |                     "You are using a different version then the room you are trying to connect to"); | ||||||
|         exit(1); |         exit(1); | ||||||
|         break; |         break; | ||||||
|     default: |     default: | ||||||
|  | @ -119,7 +120,7 @@ int main(int argc, char** argv) { | ||||||
|     auto argv_w = CommandLineToArgvW(GetCommandLineW(), &argc_w); |     auto argv_w = CommandLineToArgvW(GetCommandLineW(), &argc_w); | ||||||
| 
 | 
 | ||||||
|     if (argv_w == nullptr) { |     if (argv_w == nullptr) { | ||||||
|         LOG_CRITICAL(Frontend, "Failed to get command line arguments"); |         NGLOG_CRITICAL(Frontend, "Failed to get command line arguments"); | ||||||
|         return -1; |         return -1; | ||||||
|     } |     } | ||||||
| #endif | #endif | ||||||
|  | @ -155,7 +156,7 @@ int main(int argc, char** argv) { | ||||||
|                 break; |                 break; | ||||||
|             case 'i': { |             case 'i': { | ||||||
|                 const auto cia_progress = [](size_t written, size_t total) { |                 const auto cia_progress = [](size_t written, size_t total) { | ||||||
|                     LOG_INFO(Frontend, "%02zu%%", (written * 100 / total)); |                     NGLOG_INFO(Frontend, "{:02d}%", (written * 100 / total)); | ||||||
|                 }; |                 }; | ||||||
|                 if (Service::AM::InstallCIA(std::string(optarg), cia_progress) != |                 if (Service::AM::InstallCIA(std::string(optarg), cia_progress) != | ||||||
|                     Service::AM::InstallStatus::Success) |                     Service::AM::InstallStatus::Success) | ||||||
|  | @ -223,23 +224,27 @@ int main(int argc, char** argv) { | ||||||
|     LocalFree(argv_w); |     LocalFree(argv_w); | ||||||
| #endif | #endif | ||||||
| 
 | 
 | ||||||
|     Log::Filter log_filter(Log::Level::Debug); |  | ||||||
|     Log::SetFilter(&log_filter); |  | ||||||
| 
 |  | ||||||
|     MicroProfileOnThreadCreate("EmuThread"); |     MicroProfileOnThreadCreate("EmuThread"); | ||||||
|     SCOPE_EXIT({ MicroProfileShutdown(); }); |     SCOPE_EXIT({ MicroProfileShutdown(); }); | ||||||
| 
 | 
 | ||||||
|     if (filepath.empty()) { |     if (filepath.empty()) { | ||||||
|         LOG_CRITICAL(Frontend, "Failed to load ROM: No ROM specified"); |         NGLOG_CRITICAL(Frontend, "Failed to load ROM: No ROM specified"); | ||||||
|         return -1; |         return -1; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     if (!movie_record.empty() && !movie_play.empty()) { |     if (!movie_record.empty() && !movie_play.empty()) { | ||||||
|         LOG_CRITICAL(Frontend, "Cannot both play and record a movie"); |         NGLOG_CRITICAL(Frontend, "Cannot both play and record a movie"); | ||||||
|         return -1; |         return -1; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     Log::Filter log_filter; | ||||||
|     log_filter.ParseFilterString(Settings::values.log_filter); |     log_filter.ParseFilterString(Settings::values.log_filter); | ||||||
|  |     Log::SetGlobalFilter(log_filter); | ||||||
|  | 
 | ||||||
|  |     Log::AddBackend(std::make_unique<Log::ColorConsoleBackend>()); | ||||||
|  |     FileUtil::CreateFullPath(FileUtil::GetUserPath(D_LOGS_IDX)); | ||||||
|  |     Log::AddBackend( | ||||||
|  |         std::make_unique<Log::FileBackend>(FileUtil::GetUserPath(D_LOGS_IDX) + LOG_FILE)); | ||||||
| 
 | 
 | ||||||
|     // Apply the command line arguments
 |     // Apply the command line arguments
 | ||||||
|     Settings::values.gdbstub_port = gdb_port; |     Settings::values.gdbstub_port = gdb_port; | ||||||
|  | @ -258,28 +263,28 @@ int main(int argc, char** argv) { | ||||||
| 
 | 
 | ||||||
|     switch (load_result) { |     switch (load_result) { | ||||||
|     case Core::System::ResultStatus::ErrorGetLoader: |     case Core::System::ResultStatus::ErrorGetLoader: | ||||||
|         LOG_CRITICAL(Frontend, "Failed to obtain loader for %s!", filepath.c_str()); |         NGLOG_CRITICAL(Frontend, "Failed to obtain loader for {}!", filepath); | ||||||
|         return -1; |         return -1; | ||||||
|     case Core::System::ResultStatus::ErrorLoader: |     case Core::System::ResultStatus::ErrorLoader: | ||||||
|         LOG_CRITICAL(Frontend, "Failed to load ROM!"); |         NGLOG_CRITICAL(Frontend, "Failed to load ROM!"); | ||||||
|         return -1; |         return -1; | ||||||
|     case Core::System::ResultStatus::ErrorLoader_ErrorEncrypted: |     case Core::System::ResultStatus::ErrorLoader_ErrorEncrypted: | ||||||
|         LOG_CRITICAL(Frontend, "The game that you are trying to load must be decrypted before " |         NGLOG_CRITICAL(Frontend, "The game that you are trying to load must be decrypted before " | ||||||
|                                "being used with Citra. \n\n For more information on dumping and " |                                  "being used with Citra. \n\n For more information on dumping and " | ||||||
|                                "decrypting games, please refer to: " |                                  "decrypting games, please refer to: " | ||||||
|                                "https://citra-emu.org/wiki/dumping-game-cartridges/"); |                                  "https://citra-emu.org/wiki/dumping-game-cartridges/"); | ||||||
|         return -1; |         return -1; | ||||||
|     case Core::System::ResultStatus::ErrorLoader_ErrorInvalidFormat: |     case Core::System::ResultStatus::ErrorLoader_ErrorInvalidFormat: | ||||||
|         LOG_CRITICAL(Frontend, "Error while loading ROM: The ROM format is not supported."); |         NGLOG_CRITICAL(Frontend, "Error while loading ROM: The ROM format is not supported."); | ||||||
|         return -1; |         return -1; | ||||||
|     case Core::System::ResultStatus::ErrorNotInitialized: |     case Core::System::ResultStatus::ErrorNotInitialized: | ||||||
|         LOG_CRITICAL(Frontend, "CPUCore not initialized"); |         NGLOG_CRITICAL(Frontend, "CPUCore not initialized"); | ||||||
|         return -1; |         return -1; | ||||||
|     case Core::System::ResultStatus::ErrorSystemMode: |     case Core::System::ResultStatus::ErrorSystemMode: | ||||||
|         LOG_CRITICAL(Frontend, "Failed to determine system mode!"); |         NGLOG_CRITICAL(Frontend, "Failed to determine system mode!"); | ||||||
|         return -1; |         return -1; | ||||||
|     case Core::System::ResultStatus::ErrorVideoCore: |     case Core::System::ResultStatus::ErrorVideoCore: | ||||||
|         LOG_CRITICAL(Frontend, "VideoCore not initialized"); |         NGLOG_CRITICAL(Frontend, "VideoCore not initialized"); | ||||||
|         return -1; |         return -1; | ||||||
|     case Core::System::ResultStatus::Success: |     case Core::System::ResultStatus::Success: | ||||||
|         break; // Expected case
 |         break; // Expected case
 | ||||||
|  | @ -291,11 +296,11 @@ int main(int argc, char** argv) { | ||||||
|         if (auto member = Network::GetRoomMember().lock()) { |         if (auto member = Network::GetRoomMember().lock()) { | ||||||
|             member->BindOnChatMessageRecieved(OnMessageReceived); |             member->BindOnChatMessageRecieved(OnMessageReceived); | ||||||
|             member->BindOnStateChanged(OnStateChanged); |             member->BindOnStateChanged(OnStateChanged); | ||||||
|             LOG_DEBUG(Network, "Start connection to %s:%u with nickname %s", address.c_str(), port, |             NGLOG_DEBUG(Network, "Start connection to {}:{} with nickname {}", address, port, | ||||||
|                       nickname.c_str()); |                         nickname); | ||||||
|             member->Join(nickname, address.c_str(), port, 0, Network::NoPreferredMac, password); |             member->Join(nickname, address.c_str(), port, 0, Network::NoPreferredMac, password); | ||||||
|         } else { |         } else { | ||||||
|             LOG_ERROR(Network, "Could not access RoomMember"); |             NGLOG_ERROR(Network, "Could not access RoomMember"); | ||||||
|             return 0; |             return 0; | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  | @ -27,17 +27,17 @@ bool Config::LoadINI(const std::string& default_contents, bool retry) { | ||||||
|     const char* location = this->sdl2_config_loc.c_str(); |     const char* location = this->sdl2_config_loc.c_str(); | ||||||
|     if (sdl2_config->ParseError() < 0) { |     if (sdl2_config->ParseError() < 0) { | ||||||
|         if (retry) { |         if (retry) { | ||||||
|             LOG_WARNING(Config, "Failed to load %s. Creating file from defaults...", location); |             NGLOG_WARNING(Config, "Failed to load {}. Creating file from defaults...", location); | ||||||
|             FileUtil::CreateFullPath(location); |             FileUtil::CreateFullPath(location); | ||||||
|             FileUtil::WriteStringToFile(true, default_contents, location); |             FileUtil::WriteStringToFile(true, default_contents, location); | ||||||
|             sdl2_config = std::make_unique<INIReader>(location); // Reopen file
 |             sdl2_config = std::make_unique<INIReader>(location); // Reopen file
 | ||||||
| 
 | 
 | ||||||
|             return LoadINI(default_contents, false); |             return LoadINI(default_contents, false); | ||||||
|         } |         } | ||||||
|         LOG_ERROR(Config, "Failed."); |         NGLOG_ERROR(Config, "Failed."); | ||||||
|         return false; |         return false; | ||||||
|     } |     } | ||||||
|     LOG_INFO(Config, "Successfully loaded %s", location); |     NGLOG_INFO(Config, "Successfully loaded {}", location); | ||||||
|     return true; |     return true; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -49,10 +49,18 @@ static const std::array<int, Settings::NativeButton::NumButtons> default_buttons | ||||||
| 
 | 
 | ||||||
| static const std::array<std::array<int, 5>, Settings::NativeAnalog::NumAnalogs> default_analogs{{ | static const std::array<std::array<int, 5>, Settings::NativeAnalog::NumAnalogs> default_analogs{{ | ||||||
|     { |     { | ||||||
|         SDL_SCANCODE_UP, SDL_SCANCODE_DOWN, SDL_SCANCODE_LEFT, SDL_SCANCODE_RIGHT, SDL_SCANCODE_D, |         SDL_SCANCODE_UP, | ||||||
|  |         SDL_SCANCODE_DOWN, | ||||||
|  |         SDL_SCANCODE_LEFT, | ||||||
|  |         SDL_SCANCODE_RIGHT, | ||||||
|  |         SDL_SCANCODE_D, | ||||||
|     }, |     }, | ||||||
|     { |     { | ||||||
|         SDL_SCANCODE_I, SDL_SCANCODE_K, SDL_SCANCODE_J, SDL_SCANCODE_L, SDL_SCANCODE_D, |         SDL_SCANCODE_I, | ||||||
|  |         SDL_SCANCODE_K, | ||||||
|  |         SDL_SCANCODE_J, | ||||||
|  |         SDL_SCANCODE_L, | ||||||
|  |         SDL_SCANCODE_D, | ||||||
|     }, |     }, | ||||||
| }}; | }}; | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -66,7 +66,7 @@ EmuWindow_SDL2::EmuWindow_SDL2() { | ||||||
| 
 | 
 | ||||||
|     // Initialize the window
 |     // Initialize the window
 | ||||||
|     if (SDL_Init(SDL_INIT_VIDEO) < 0) { |     if (SDL_Init(SDL_INIT_VIDEO) < 0) { | ||||||
|         LOG_CRITICAL(Frontend, "Failed to initialize SDL2! Exiting..."); |         NGLOG_CRITICAL(Frontend, "Failed to initialize SDL2! Exiting..."); | ||||||
|         exit(1); |         exit(1); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | @ -89,19 +89,19 @@ EmuWindow_SDL2::EmuWindow_SDL2() { | ||||||
|                          SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI); |                          SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI); | ||||||
| 
 | 
 | ||||||
|     if (render_window == nullptr) { |     if (render_window == nullptr) { | ||||||
|         LOG_CRITICAL(Frontend, "Failed to create SDL2 window: %s", SDL_GetError()); |         NGLOG_CRITICAL(Frontend, "Failed to create SDL2 window: {}", SDL_GetError()); | ||||||
|         exit(1); |         exit(1); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     gl_context = SDL_GL_CreateContext(render_window); |     gl_context = SDL_GL_CreateContext(render_window); | ||||||
| 
 | 
 | ||||||
|     if (gl_context == nullptr) { |     if (gl_context == nullptr) { | ||||||
|         LOG_CRITICAL(Frontend, "Failed to create SDL2 GL context: %s", SDL_GetError()); |         NGLOG_CRITICAL(Frontend, "Failed to create SDL2 GL context: {}", SDL_GetError()); | ||||||
|         exit(1); |         exit(1); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     if (!gladLoadGLLoader(static_cast<GLADloadproc>(SDL_GL_GetProcAddress))) { |     if (!gladLoadGLLoader(static_cast<GLADloadproc>(SDL_GL_GetProcAddress))) { | ||||||
|         LOG_CRITICAL(Frontend, "Failed to initialize GL functions: %s", SDL_GetError()); |         NGLOG_CRITICAL(Frontend, "Failed to initialize GL functions: {}", SDL_GetError()); | ||||||
|         exit(1); |         exit(1); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -28,6 +28,8 @@ add_executable(citra-qt | ||||||
|     configuration/configure_system.h |     configuration/configure_system.h | ||||||
|     configuration/configure_web.cpp |     configuration/configure_web.cpp | ||||||
|     configuration/configure_web.h |     configuration/configure_web.h | ||||||
|  |     debugger/console.h | ||||||
|  |     debugger/console.cpp | ||||||
|     debugger/graphics/graphics.cpp |     debugger/graphics/graphics.cpp | ||||||
|     debugger/graphics/graphics.h |     debugger/graphics/graphics.h | ||||||
|     debugger/graphics/graphics_breakpoint_observer.cpp |     debugger/graphics/graphics_breakpoint_observer.cpp | ||||||
|  | @ -138,6 +140,10 @@ if (APPLE) | ||||||
|     target_sources(citra-qt PRIVATE ${MACOSX_ICON}) |     target_sources(citra-qt PRIVATE ${MACOSX_ICON}) | ||||||
|     set_target_properties(citra-qt PROPERTIES MACOSX_BUNDLE TRUE) |     set_target_properties(citra-qt PROPERTIES MACOSX_BUNDLE TRUE) | ||||||
|     set_target_properties(citra-qt PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/Info.plist) |     set_target_properties(citra-qt PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/Info.plist) | ||||||
|  | elseif(WIN32) | ||||||
|  |     # compile as a win32 gui application instead of a console application | ||||||
|  |     target_link_libraries(citra-qt PRIVATE Qt5::WinMain) | ||||||
|  |     set_target_properties(citra-qt PROPERTIES LINK_FLAGS_RELEASE "/SUBSYSTEM:WINDOWS") | ||||||
| endif() | endif() | ||||||
| 
 | 
 | ||||||
| create_target_directory_groups(citra-qt) | create_target_directory_groups(citra-qt) | ||||||
|  |  | ||||||
|  | @ -24,10 +24,18 @@ const std::array<int, Settings::NativeButton::NumButtons> Config::default_button | ||||||
| 
 | 
 | ||||||
| const std::array<std::array<int, 5>, Settings::NativeAnalog::NumAnalogs> Config::default_analogs{{ | const std::array<std::array<int, 5>, Settings::NativeAnalog::NumAnalogs> Config::default_analogs{{ | ||||||
|     { |     { | ||||||
|         Qt::Key_Up, Qt::Key_Down, Qt::Key_Left, Qt::Key_Right, Qt::Key_D, |         Qt::Key_Up, | ||||||
|  |         Qt::Key_Down, | ||||||
|  |         Qt::Key_Left, | ||||||
|  |         Qt::Key_Right, | ||||||
|  |         Qt::Key_D, | ||||||
|     }, |     }, | ||||||
|     { |     { | ||||||
|         Qt::Key_I, Qt::Key_K, Qt::Key_J, Qt::Key_L, Qt::Key_D, |         Qt::Key_I, | ||||||
|  |         Qt::Key_K, | ||||||
|  |         Qt::Key_J, | ||||||
|  |         Qt::Key_L, | ||||||
|  |         Qt::Key_D, | ||||||
|     }, |     }, | ||||||
| }}; | }}; | ||||||
| 
 | 
 | ||||||
|  | @ -216,6 +224,7 @@ void Config::ReadValues() { | ||||||
|     UISettings::values.confirm_before_closing = qt_config->value("confirmClose", true).toBool(); |     UISettings::values.confirm_before_closing = qt_config->value("confirmClose", true).toBool(); | ||||||
|     UISettings::values.first_start = qt_config->value("firstStart", true).toBool(); |     UISettings::values.first_start = qt_config->value("firstStart", true).toBool(); | ||||||
|     UISettings::values.callout_flags = qt_config->value("calloutFlags", 0).toUInt(); |     UISettings::values.callout_flags = qt_config->value("calloutFlags", 0).toUInt(); | ||||||
|  |     UISettings::values.show_console = qt_config->value("showConsole", false).toBool(); | ||||||
| 
 | 
 | ||||||
|     qt_config->endGroup(); |     qt_config->endGroup(); | ||||||
| } | } | ||||||
|  | @ -357,6 +366,7 @@ void Config::SaveValues() { | ||||||
|     qt_config->setValue("confirmClose", UISettings::values.confirm_before_closing); |     qt_config->setValue("confirmClose", UISettings::values.confirm_before_closing); | ||||||
|     qt_config->setValue("firstStart", UISettings::values.first_start); |     qt_config->setValue("firstStart", UISettings::values.first_start); | ||||||
|     qt_config->setValue("calloutFlags", UISettings::values.callout_flags); |     qt_config->setValue("calloutFlags", UISettings::values.callout_flags); | ||||||
|  |     qt_config->setValue("showConsole", UISettings::values.show_console); | ||||||
| 
 | 
 | ||||||
|     qt_config->endGroup(); |     qt_config->endGroup(); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -2,13 +2,27 @@ | ||||||
| // Licensed under GPLv2 or any later version
 | // Licensed under GPLv2 or any later version
 | ||||||
| // Refer to the license.txt file included.
 | // Refer to the license.txt file included.
 | ||||||
| 
 | 
 | ||||||
|  | #include <QDesktopServices> | ||||||
|  | #include <QUrl> | ||||||
|  | 
 | ||||||
| #include "citra_qt/configuration/configure_debug.h" | #include "citra_qt/configuration/configure_debug.h" | ||||||
|  | #include "citra_qt/debugger/console.h" | ||||||
|  | #include "citra_qt/ui_settings.h" | ||||||
|  | #include "common/file_util.h" | ||||||
|  | #include "common/logging/backend.h" | ||||||
|  | #include "common/logging/filter.h" | ||||||
|  | #include "common/logging/log.h" | ||||||
|  | #include "core/core.h" | ||||||
| #include "core/settings.h" | #include "core/settings.h" | ||||||
| #include "ui_configure_debug.h" | #include "ui_configure_debug.h" | ||||||
| 
 | 
 | ||||||
| ConfigureDebug::ConfigureDebug(QWidget* parent) : QWidget(parent), ui(new Ui::ConfigureDebug) { | ConfigureDebug::ConfigureDebug(QWidget* parent) : QWidget(parent), ui(new Ui::ConfigureDebug) { | ||||||
|     ui->setupUi(this); |     ui->setupUi(this); | ||||||
|     this->setConfiguration(); |     this->setConfiguration(); | ||||||
|  |     connect(ui->open_log_button, &QPushButton::pressed, []() { | ||||||
|  |         QString path = QString::fromStdString(FileUtil::GetUserPath(D_LOGS_IDX)); | ||||||
|  |         QDesktopServices::openUrl(QUrl::fromLocalFile(path)); | ||||||
|  |     }); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| ConfigureDebug::~ConfigureDebug() {} | ConfigureDebug::~ConfigureDebug() {} | ||||||
|  | @ -17,11 +31,20 @@ void ConfigureDebug::setConfiguration() { | ||||||
|     ui->toggle_gdbstub->setChecked(Settings::values.use_gdbstub); |     ui->toggle_gdbstub->setChecked(Settings::values.use_gdbstub); | ||||||
|     ui->gdbport_spinbox->setEnabled(Settings::values.use_gdbstub); |     ui->gdbport_spinbox->setEnabled(Settings::values.use_gdbstub); | ||||||
|     ui->gdbport_spinbox->setValue(Settings::values.gdbstub_port); |     ui->gdbport_spinbox->setValue(Settings::values.gdbstub_port); | ||||||
|  |     ui->toggle_console->setEnabled(!Core::System::GetInstance().IsPoweredOn()); | ||||||
|  |     ui->toggle_console->setChecked(UISettings::values.show_console); | ||||||
|  |     ui->log_filter_edit->setText(QString::fromStdString(Settings::values.log_filter)); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void ConfigureDebug::applyConfiguration() { | void ConfigureDebug::applyConfiguration() { | ||||||
|     Settings::values.use_gdbstub = ui->toggle_gdbstub->isChecked(); |     Settings::values.use_gdbstub = ui->toggle_gdbstub->isChecked(); | ||||||
|     Settings::values.gdbstub_port = ui->gdbport_spinbox->value(); |     Settings::values.gdbstub_port = ui->gdbport_spinbox->value(); | ||||||
|  |     UISettings::values.show_console = ui->toggle_console->isChecked(); | ||||||
|  |     Settings::values.log_filter = ui->log_filter_edit->text().toStdString(); | ||||||
|  |     Debugger::ToggleConsole(); | ||||||
|  |     Log::Filter filter; | ||||||
|  |     filter.ParseFilterString(Settings::values.log_filter); | ||||||
|  |     Log::SetGlobalFilter(filter); | ||||||
|     Settings::Apply(); |     Settings::Apply(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -72,6 +72,47 @@ | ||||||
|      </item> |      </item> | ||||||
|     </layout> |     </layout> | ||||||
|    </item> |    </item> | ||||||
|  |    <item> | ||||||
|  |     <widget class="QGroupBox" name="groupBox_2"> | ||||||
|  |      <property name="title"> | ||||||
|  |       <string>Logging</string> | ||||||
|  |      </property> | ||||||
|  |      <layout class="QVBoxLayout" name="verticalLayout"> | ||||||
|  |       <item> | ||||||
|  |        <layout class="QHBoxLayout" name="horizontalLayout"> | ||||||
|  |         <item> | ||||||
|  |          <widget class="QLabel" name="label"> | ||||||
|  |           <property name="text"> | ||||||
|  |            <string>Global Log Filter</string> | ||||||
|  |           </property> | ||||||
|  |          </widget> | ||||||
|  |         </item> | ||||||
|  |         <item> | ||||||
|  |          <widget class="QLineEdit" name="log_filter_edit"/> | ||||||
|  |         </item> | ||||||
|  |        </layout> | ||||||
|  |       </item> | ||||||
|  |       <item> | ||||||
|  |        <layout class="QHBoxLayout" name="horizontalLayout_2"> | ||||||
|  |         <item> | ||||||
|  |          <widget class="QCheckBox" name="toggle_console"> | ||||||
|  |           <property name="text"> | ||||||
|  |            <string>Show Log Console (Windows Only)</string> | ||||||
|  |           </property> | ||||||
|  |          </widget> | ||||||
|  |         </item> | ||||||
|  |         <item> | ||||||
|  |          <widget class="QPushButton" name="open_log_button"> | ||||||
|  |           <property name="text"> | ||||||
|  |            <string>Open Log Location</string> | ||||||
|  |           </property> | ||||||
|  |          </widget> | ||||||
|  |         </item> | ||||||
|  |        </layout> | ||||||
|  |       </item> | ||||||
|  |      </layout> | ||||||
|  |     </widget> | ||||||
|  |    </item> | ||||||
|    <item> |    <item> | ||||||
|     <spacer name="verticalSpacer"> |     <spacer name="verticalSpacer"> | ||||||
|      <property name="orientation"> |      <property name="orientation"> | ||||||
|  |  | ||||||
							
								
								
									
										34
									
								
								src/citra_qt/debugger/console.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								src/citra_qt/debugger/console.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,34 @@ | ||||||
|  | // Copyright 2018 Citra Emulator Project
 | ||||||
|  | // Licensed under GPLv2 or any later version
 | ||||||
|  | // Refer to the license.txt file included.
 | ||||||
|  | 
 | ||||||
|  | #ifdef _WIN32 | ||||||
|  | #include <windows.h> | ||||||
|  | 
 | ||||||
|  | #include <wincon.h> | ||||||
|  | #endif | ||||||
|  | 
 | ||||||
|  | #include "citra_qt/debugger/console.h" | ||||||
|  | #include "citra_qt/ui_settings.h" | ||||||
|  | 
 | ||||||
|  | namespace Debugger { | ||||||
|  | void ToggleConsole() { | ||||||
|  | #ifdef _WIN32 | ||||||
|  |     if (UISettings::values.show_console) { | ||||||
|  |         if (AllocConsole()) { | ||||||
|  |             freopen_s((FILE**)stdin, "CONIN$", "r", stdin); | ||||||
|  |             freopen_s((FILE**)stdout, "CONOUT$", "w", stdout); | ||||||
|  |             freopen_s((FILE**)stderr, "CONOUT$", "w", stderr); | ||||||
|  |         } | ||||||
|  |     } else { | ||||||
|  |         if (FreeConsole()) { | ||||||
|  |             // In order to close the console, we have to also detach the streams on it.
 | ||||||
|  |             // Just redirect them to NUL if there is no console window
 | ||||||
|  |             freopen_s((FILE**)stdin, "NUL", "r", stdin); | ||||||
|  |             freopen_s((FILE**)stdout, "NUL", "w", stdout); | ||||||
|  |             freopen_s((FILE**)stderr, "NUL", "w", stderr); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | #endif | ||||||
|  | } | ||||||
|  | } // namespace Debugger
 | ||||||
							
								
								
									
										14
									
								
								src/citra_qt/debugger/console.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								src/citra_qt/debugger/console.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,14 @@ | ||||||
|  | // Copyright 2018 Citra Emulator Project
 | ||||||
|  | // Licensed under GPLv2 or any later version
 | ||||||
|  | // Refer to the license.txt file included.
 | ||||||
|  | 
 | ||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | namespace Debugger { | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * Uses the WINAPI to hide or show the stderr console. This function is a placeholder until we can | ||||||
|  |  * get a real qt logging window which would work for all platforms. | ||||||
|  |  */ | ||||||
|  | void ToggleConsole(); | ||||||
|  | } // namespace Debugger
 | ||||||
|  | @ -20,6 +20,7 @@ | ||||||
| #include "citra_qt/compatdb.h" | #include "citra_qt/compatdb.h" | ||||||
| #include "citra_qt/configuration/config.h" | #include "citra_qt/configuration/config.h" | ||||||
| #include "citra_qt/configuration/configure_dialog.h" | #include "citra_qt/configuration/configure_dialog.h" | ||||||
|  | #include "citra_qt/debugger/console.h" | ||||||
| #include "citra_qt/debugger/graphics/graphics.h" | #include "citra_qt/debugger/graphics/graphics.h" | ||||||
| #include "citra_qt/debugger/graphics/graphics_breakpoints.h" | #include "citra_qt/debugger/graphics/graphics_breakpoints.h" | ||||||
| #include "citra_qt/debugger/graphics/graphics_cmdlists.h" | #include "citra_qt/debugger/graphics/graphics_cmdlists.h" | ||||||
|  | @ -34,6 +35,7 @@ | ||||||
| #include "citra_qt/main.h" | #include "citra_qt/main.h" | ||||||
| #include "citra_qt/ui_settings.h" | #include "citra_qt/ui_settings.h" | ||||||
| #include "citra_qt/updater/updater.h" | #include "citra_qt/updater/updater.h" | ||||||
|  | #include "common/common_paths.h" | ||||||
| #include "common/logging/backend.h" | #include "common/logging/backend.h" | ||||||
| #include "common/logging/filter.h" | #include "common/logging/filter.h" | ||||||
| #include "common/logging/log.h" | #include "common/logging/log.h" | ||||||
|  | @ -369,6 +371,7 @@ void GMainWindow::RestoreUIState() { | ||||||
| 
 | 
 | ||||||
|     ui.action_Show_Status_Bar->setChecked(UISettings::values.show_status_bar); |     ui.action_Show_Status_Bar->setChecked(UISettings::values.show_status_bar); | ||||||
|     statusBar()->setVisible(ui.action_Show_Status_Bar->isChecked()); |     statusBar()->setVisible(ui.action_Show_Status_Bar->isChecked()); | ||||||
|  |     Debugger::ToggleConsole(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void GMainWindow::ConnectWidgetEvents() { | void GMainWindow::ConnectWidgetEvents() { | ||||||
|  | @ -1295,8 +1298,7 @@ void GMainWindow::SyncMenuUISettings() { | ||||||
| #endif | #endif | ||||||
| 
 | 
 | ||||||
| int main(int argc, char* argv[]) { | int main(int argc, char* argv[]) { | ||||||
|     Log::Filter log_filter(Log::Level::Info); |     Log::AddBackend(std::make_unique<Log::ColorConsoleBackend>()); | ||||||
|     Log::SetFilter(&log_filter); |  | ||||||
| 
 | 
 | ||||||
|     MicroProfileOnThreadCreate("Frontend"); |     MicroProfileOnThreadCreate("Frontend"); | ||||||
|     SCOPE_EXIT({ MicroProfileShutdown(); }); |     SCOPE_EXIT({ MicroProfileShutdown(); }); | ||||||
|  | @ -1314,7 +1316,12 @@ int main(int argc, char* argv[]) { | ||||||
| 
 | 
 | ||||||
|     GMainWindow main_window; |     GMainWindow main_window; | ||||||
|     // After settings have been loaded by GMainWindow, apply the filter
 |     // After settings have been loaded by GMainWindow, apply the filter
 | ||||||
|  |     Log::Filter log_filter; | ||||||
|     log_filter.ParseFilterString(Settings::values.log_filter); |     log_filter.ParseFilterString(Settings::values.log_filter); | ||||||
|  |     Log::SetGlobalFilter(log_filter); | ||||||
|  |     FileUtil::CreateFullPath(FileUtil::GetUserPath(D_LOGS_IDX)); | ||||||
|  |     Log::AddBackend( | ||||||
|  |         std::make_unique<Log::FileBackend>(FileUtil::GetUserPath(D_LOGS_IDX) + LOG_FILE)); | ||||||
| 
 | 
 | ||||||
|     main_window.show(); |     main_window.show(); | ||||||
|     return app.exec(); |     return app.exec(); | ||||||
|  |  | ||||||
|  | @ -56,6 +56,8 @@ struct Values { | ||||||
|     std::vector<Shortcut> shortcuts; |     std::vector<Shortcut> shortcuts; | ||||||
| 
 | 
 | ||||||
|     uint32_t callout_flags; |     uint32_t callout_flags; | ||||||
|  | 
 | ||||||
|  |     bool show_console; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| extern Values values; | extern Values values; | ||||||
|  |  | ||||||
|  | @ -93,7 +93,7 @@ endif() | ||||||
| 
 | 
 | ||||||
| create_target_directory_groups(common) | create_target_directory_groups(common) | ||||||
| 
 | 
 | ||||||
| target_link_libraries(common PUBLIC Boost::boost microprofile) | target_link_libraries(common PUBLIC Boost::boost fmt microprofile) | ||||||
| if (ARCHITECTURE_x86_64) | if (ARCHITECTURE_x86_64) | ||||||
|     target_link_libraries(common PRIVATE xbyak) |     target_link_libraries(common PRIVATE xbyak) | ||||||
| endif() | endif() | ||||||
|  |  | ||||||
|  | @ -36,8 +36,12 @@ | ||||||
| #define SDMC_DIR "sdmc" | #define SDMC_DIR "sdmc" | ||||||
| #define NAND_DIR "nand" | #define NAND_DIR "nand" | ||||||
| #define SYSDATA_DIR "sysdata" | #define SYSDATA_DIR "sysdata" | ||||||
|  | #define LOG_DIR "log" | ||||||
| 
 | 
 | ||||||
| // Filenames
 | // Filenames
 | ||||||
|  | // Files in the directory returned by GetUserPath(D_LOGS_IDX)
 | ||||||
|  | #define LOG_FILE "citra_log.txt" | ||||||
|  | 
 | ||||||
| // Files in the directory returned by GetUserPath(D_CONFIG_IDX)
 | // Files in the directory returned by GetUserPath(D_CONFIG_IDX)
 | ||||||
| #define EMU_CONFIG "emu.ini" | #define EMU_CONFIG "emu.ini" | ||||||
| #define DEBUGGER_CONFIG "debugger.ini" | #define DEBUGGER_CONFIG "debugger.ini" | ||||||
|  |  | ||||||
|  | @ -715,6 +715,8 @@ const std::string& GetUserPath(const unsigned int DirIDX, const std::string& new | ||||||
|         paths[D_SDMC_IDX] = paths[D_USER_IDX] + SDMC_DIR DIR_SEP; |         paths[D_SDMC_IDX] = paths[D_USER_IDX] + SDMC_DIR DIR_SEP; | ||||||
|         paths[D_NAND_IDX] = paths[D_USER_IDX] + NAND_DIR DIR_SEP; |         paths[D_NAND_IDX] = paths[D_USER_IDX] + NAND_DIR DIR_SEP; | ||||||
|         paths[D_SYSDATA_IDX] = paths[D_USER_IDX] + SYSDATA_DIR DIR_SEP; |         paths[D_SYSDATA_IDX] = paths[D_USER_IDX] + SYSDATA_DIR DIR_SEP; | ||||||
|  |         // TODO: Put the logs in a better location for each OS
 | ||||||
|  |         paths[D_LOGS_IDX] = paths[D_USER_IDX] + LOG_DIR DIR_SEP; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     if (!newPath.empty()) { |     if (!newPath.empty()) { | ||||||
|  | @ -873,20 +875,19 @@ bool IOFile::Flush() { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| bool IOFile::Resize(u64 size) { | bool IOFile::Resize(u64 size) { | ||||||
|     if (!IsOpen() || |     if (!IsOpen() || 0 != | ||||||
|         0 != |  | ||||||
| #ifdef _WIN32 | #ifdef _WIN32 | ||||||
|             // ector: _chsize sucks, not 64-bit safe
 |                          // ector: _chsize sucks, not 64-bit safe
 | ||||||
|             // F|RES: changed to _chsize_s. i think it is 64-bit safe
 |                          // F|RES: changed to _chsize_s. i think it is 64-bit safe
 | ||||||
|             _chsize_s(_fileno(m_file), size) |                          _chsize_s(_fileno(m_file), size) | ||||||
| #else | #else | ||||||
|             // TODO: handle 64bit and growing
 |                          // TODO: handle 64bit and growing
 | ||||||
|             ftruncate(fileno(m_file), size) |                          ftruncate(fileno(m_file), size) | ||||||
| #endif | #endif | ||||||
|             ) |     ) | ||||||
|         m_good = false; |         m_good = false; | ||||||
| 
 | 
 | ||||||
|     return m_good; |     return m_good; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| } // namespace
 | } // namespace FileUtil
 | ||||||
|  |  | ||||||
|  | @ -224,6 +224,10 @@ public: | ||||||
|         return WriteArray(&object, 1); |         return WriteArray(&object, 1); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     size_t WriteString(const std::string& str) { | ||||||
|  |         return WriteArray(str.c_str(), str.length()); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     bool IsOpen() const { |     bool IsOpen() const { | ||||||
|         return nullptr != m_file; |         return nullptr != m_file; | ||||||
|     } |     } | ||||||
|  | @ -253,7 +257,7 @@ private: | ||||||
|     bool m_good = true; |     bool m_good = true; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| } // namespace
 | } // namespace FileUtil
 | ||||||
| 
 | 
 | ||||||
| // To deal with Windows being dumb at unicode:
 | // To deal with Windows being dumb at unicode:
 | ||||||
| template <typename T> | template <typename T> | ||||||
|  |  | ||||||
|  | @ -4,16 +4,108 @@ | ||||||
| 
 | 
 | ||||||
| #include <algorithm> | #include <algorithm> | ||||||
| #include <array> | #include <array> | ||||||
|  | #include <chrono> | ||||||
| #include <cstdio> | #include <cstdio> | ||||||
|  | #include <future> | ||||||
|  | #include <memory> | ||||||
|  | #include <thread> | ||||||
| #include "common/assert.h" | #include "common/assert.h" | ||||||
| #include "common/common_funcs.h" // snprintf compatibility define
 | #include "common/common_funcs.h" // snprintf compatibility define
 | ||||||
| #include "common/logging/backend.h" | #include "common/logging/backend.h" | ||||||
| #include "common/logging/filter.h" |  | ||||||
| #include "common/logging/log.h" | #include "common/logging/log.h" | ||||||
| #include "common/logging/text_formatter.h" | #include "common/logging/text_formatter.h" | ||||||
|  | #include "common/string_util.h" | ||||||
|  | #include "common/threadsafe_queue.h" | ||||||
| 
 | 
 | ||||||
| namespace Log { | namespace Log { | ||||||
| 
 | 
 | ||||||
|  | /**
 | ||||||
|  |  * Static state as a singleton. | ||||||
|  |  */ | ||||||
|  | class Impl { | ||||||
|  | public: | ||||||
|  |     static Impl& Instance() { | ||||||
|  |         static Impl backend; | ||||||
|  |         return backend; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     Impl(Impl const&) = delete; | ||||||
|  |     const Impl& operator=(Impl const&) = delete; | ||||||
|  | 
 | ||||||
|  |     Common::MPSCQueue<Log::Entry>& GetQueue() { | ||||||
|  |         return message_queue; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     void AddBackend(std::unique_ptr<Backend> backend) { | ||||||
|  |         backends.push_back(std::move(backend)); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     void RemoveBackend(const std::string& backend_name) { | ||||||
|  |         std::remove_if(backends.begin(), backends.end(), | ||||||
|  |                        [&backend_name](const auto& i) { return i->GetName() == backend_name; }); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     const Filter& GetGlobalFilter() const { | ||||||
|  |         return filter; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     void SetGlobalFilter(const Filter& f) { | ||||||
|  |         filter = f; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     Backend* GetBackend(const std::string& backend_name) { | ||||||
|  |         auto it = std::find_if(backends.begin(), backends.end(), [&backend_name](const auto& i) { | ||||||
|  |             return i->GetName() == backend_name; | ||||||
|  |         }); | ||||||
|  |         if (it == backends.end()) | ||||||
|  |             return nullptr; | ||||||
|  |         return it->get(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | private: | ||||||
|  |     Impl() : running(true) { | ||||||
|  |         backend_thread = std::async(std::launch::async, [&] { | ||||||
|  |             using namespace std::chrono_literals; | ||||||
|  |             Entry entry; | ||||||
|  |             while (running) { | ||||||
|  |                 if (!message_queue.Pop(entry)) { | ||||||
|  |                     std::this_thread::sleep_for(1ms); | ||||||
|  |                     continue; | ||||||
|  |                 } | ||||||
|  |                 for (const auto& backend : backends) { | ||||||
|  |                     backend->Write(entry); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  |     ~Impl() { | ||||||
|  |         if (running) { | ||||||
|  |             running = false; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     std::atomic_bool running; | ||||||
|  |     std::future<void> backend_thread; | ||||||
|  |     std::vector<std::unique_ptr<Backend>> backends; | ||||||
|  |     Common::MPSCQueue<Log::Entry> message_queue; | ||||||
|  |     Filter filter; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | void ConsoleBackend::Write(const Entry& entry) { | ||||||
|  |     PrintMessage(entry); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void ColorConsoleBackend::Write(const Entry& entry) { | ||||||
|  |     PrintColoredMessage(entry); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void FileBackend::Write(const Entry& entry) { | ||||||
|  |     if (!file.IsOpen()) { | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  |     file.WriteString(FormatLogMessage(entry) + "\n"); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| /// Macro listing all log classes. Code should define CLS and SUB as desired before invoking this.
 | /// Macro listing all log classes. Code should define CLS and SUB as desired before invoking this.
 | ||||||
| #define ALL_LOG_CLASSES()                                                                          \ | #define ALL_LOG_CLASSES()                                                                          \ | ||||||
|     CLS(Log)                                                                                       \ |     CLS(Log)                                                                                       \ | ||||||
|  | @ -113,45 +205,65 @@ const char* GetLevelName(Level log_level) { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| Entry CreateEntry(Class log_class, Level log_level, const char* filename, unsigned int line_nr, | Entry CreateEntry(Class log_class, Level log_level, const char* filename, unsigned int line_nr, | ||||||
|                   const char* function, const char* format, va_list args) { |                   const char* function, const std::string& message) { | ||||||
|     using std::chrono::steady_clock; |  | ||||||
|     using std::chrono::duration_cast; |     using std::chrono::duration_cast; | ||||||
|  |     using std::chrono::steady_clock; | ||||||
| 
 | 
 | ||||||
|     static steady_clock::time_point time_origin = steady_clock::now(); |     static steady_clock::time_point time_origin = steady_clock::now(); | ||||||
| 
 | 
 | ||||||
|     std::array<char, 4 * 1024> formatting_buffer; |  | ||||||
| 
 |  | ||||||
|     Entry entry; |     Entry entry; | ||||||
|     entry.timestamp = duration_cast<std::chrono::microseconds>(steady_clock::now() - time_origin); |     entry.timestamp = duration_cast<std::chrono::microseconds>(steady_clock::now() - time_origin); | ||||||
|     entry.log_class = log_class; |     entry.log_class = log_class; | ||||||
|     entry.log_level = log_level; |     entry.log_level = log_level; | ||||||
| 
 |     entry.filename = std::string(Common::TrimSourcePath(filename)); | ||||||
|     snprintf(formatting_buffer.data(), formatting_buffer.size(), "%s:%s:%u", filename, function, |     entry.line_num = line_nr; | ||||||
|              line_nr); |     entry.function = std::string(function); | ||||||
|     entry.location = std::string(formatting_buffer.data()); |     entry.message = message; | ||||||
| 
 |  | ||||||
|     vsnprintf(formatting_buffer.data(), formatting_buffer.size(), format, args); |  | ||||||
|     entry.message = std::string(formatting_buffer.data()); |  | ||||||
| 
 | 
 | ||||||
|     return entry; |     return entry; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static Filter* filter = nullptr; | void SetGlobalFilter(const Filter& filter) { | ||||||
| 
 |     Impl::Instance().SetGlobalFilter(filter); | ||||||
| void SetFilter(Filter* new_filter) { |  | ||||||
|     filter = new_filter; |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void LogMessage(Class log_class, Level log_level, const char* filename, unsigned int line_nr, | void AddBackend(std::unique_ptr<Backend> backend) { | ||||||
|                 const char* function, const char* format, ...) { |     Impl::Instance().AddBackend(std::move(backend)); | ||||||
|     if (filter != nullptr && !filter->CheckMessage(log_class, log_level)) | } | ||||||
|         return; |  | ||||||
| 
 | 
 | ||||||
|  | void RemoveBackend(const std::string& backend_name) { | ||||||
|  |     Impl::Instance().RemoveBackend(backend_name); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | Backend* GetBackend(const std::string& backend_name) { | ||||||
|  |     return Impl::Instance().GetBackend(backend_name); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void LogMessage(Class log_class, Level log_level, const char* filename, unsigned int line_num, | ||||||
|  |                 const char* function, const char* format, ...) { | ||||||
|  |     auto filter = Impl::Instance().GetGlobalFilter(); | ||||||
|  |     if (!filter.CheckMessage(log_class, log_level)) | ||||||
|  |         return; | ||||||
|  |     std::array<char, 4 * 1024> formatting_buffer; | ||||||
|     va_list args; |     va_list args; | ||||||
|     va_start(args, format); |     va_start(args, format); | ||||||
|     Entry entry = CreateEntry(log_class, log_level, filename, line_nr, function, format, args); |     vsnprintf(formatting_buffer.data(), formatting_buffer.size(), format, args); | ||||||
|     va_end(args); |     va_end(args); | ||||||
|  |     Entry entry = CreateEntry(log_class, log_level, filename, line_num, function, | ||||||
|  |                               std::string(formatting_buffer.data())); | ||||||
| 
 | 
 | ||||||
|     PrintColoredMessage(entry); |     Impl::Instance().GetQueue().Push(std::move(entry)); | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | void FmtLogMessage(Class log_class, Level log_level, const char* filename, unsigned int line_num, | ||||||
|  |                    const char* function, const char* format, fmt::ArgList args) { | ||||||
|  |     auto filter = Impl::Instance().GetGlobalFilter(); | ||||||
|  |     if (!filter.CheckMessage(log_class, log_level)) | ||||||
|  |         return; | ||||||
|  | 
 | ||||||
|  |     Entry entry = | ||||||
|  |         CreateEntry(log_class, log_level, filename, line_num, function, fmt::format(format, args)); | ||||||
|  | 
 | ||||||
|  |     Impl::Instance().GetQueue().Push(std::move(entry)); | ||||||
| } | } | ||||||
|  | } // namespace Log
 | ||||||
|  |  | ||||||
|  | @ -6,8 +6,11 @@ | ||||||
| 
 | 
 | ||||||
| #include <chrono> | #include <chrono> | ||||||
| #include <cstdarg> | #include <cstdarg> | ||||||
|  | #include <memory> | ||||||
| #include <string> | #include <string> | ||||||
| #include <utility> | #include <utility> | ||||||
|  | #include "common/file_util.h" | ||||||
|  | #include "common/logging/filter.h" | ||||||
| #include "common/logging/log.h" | #include "common/logging/log.h" | ||||||
| 
 | 
 | ||||||
| namespace Log { | namespace Log { | ||||||
|  | @ -22,15 +25,80 @@ struct Entry { | ||||||
|     std::chrono::microseconds timestamp; |     std::chrono::microseconds timestamp; | ||||||
|     Class log_class; |     Class log_class; | ||||||
|     Level log_level; |     Level log_level; | ||||||
|     std::string location; |     std::string filename; | ||||||
|  |     unsigned int line_num; | ||||||
|  |     std::string function; | ||||||
|     std::string message; |     std::string message; | ||||||
| 
 | 
 | ||||||
|     Entry() = default; |     Entry() = default; | ||||||
|     Entry(Entry&& o) = default; |     Entry(Entry&& o) = default; | ||||||
| 
 | 
 | ||||||
|     Entry& operator=(Entry&& o) = default; |     Entry& operator=(Entry&& o) = default; | ||||||
|  |     Entry& operator=(const Entry& o) = default; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | /**
 | ||||||
|  |  * Interface for logging backends. As loggers can be created and removed at runtime, this can be | ||||||
|  |  * used by a frontend for adding a custom logging backend as needed | ||||||
|  |  */ | ||||||
|  | class Backend { | ||||||
|  | public: | ||||||
|  |     virtual ~Backend() = default; | ||||||
|  |     virtual void SetFilter(const Filter& new_filter) { | ||||||
|  |         filter = new_filter; | ||||||
|  |     } | ||||||
|  |     virtual const char* GetName() const = 0; | ||||||
|  |     virtual void Write(const Entry& entry) = 0; | ||||||
|  | 
 | ||||||
|  | private: | ||||||
|  |     Filter filter; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * Backend that writes to stderr without any color commands | ||||||
|  |  */ | ||||||
|  | class ConsoleBackend : public Backend { | ||||||
|  | public: | ||||||
|  |     const char* GetName() const override { | ||||||
|  |         return "console"; | ||||||
|  |     } | ||||||
|  |     void Write(const Entry& entry) override; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * Backend that writes to stderr and with color | ||||||
|  |  */ | ||||||
|  | class ColorConsoleBackend : public Backend { | ||||||
|  | public: | ||||||
|  |     const char* GetName() const override { | ||||||
|  |         return "color_console"; | ||||||
|  |     } | ||||||
|  |     void Write(const Entry& entry) override; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * Backend that writes to a file passed into the constructor | ||||||
|  |  */ | ||||||
|  | class FileBackend : public Backend { | ||||||
|  | public: | ||||||
|  |     FileBackend(const std::string& filename) : file(filename, "w") {} | ||||||
|  | 
 | ||||||
|  |     const char* GetName() const override { | ||||||
|  |         return "file"; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     void Write(const Entry& entry) override; | ||||||
|  | 
 | ||||||
|  | private: | ||||||
|  |     FileUtil::IOFile file; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | void AddBackend(std::unique_ptr<Backend> backend); | ||||||
|  | 
 | ||||||
|  | void RemoveBackend(const std::string& backend_name); | ||||||
|  | 
 | ||||||
|  | Backend* GetBackend(const std::string& backend_name); | ||||||
|  | 
 | ||||||
| /**
 | /**
 | ||||||
|  * Returns the name of the passed log class as a C-string. Subclasses are separated by periods |  * Returns the name of the passed log class as a C-string. Subclasses are separated by periods | ||||||
|  * instead of underscores as in the enumeration. |  * instead of underscores as in the enumeration. | ||||||
|  | @ -44,7 +112,13 @@ const char* GetLevelName(Level log_level); | ||||||
| 
 | 
 | ||||||
| /// Creates a log entry by formatting the given source location, and message.
 | /// Creates a log entry by formatting the given source location, and message.
 | ||||||
| Entry CreateEntry(Class log_class, Level log_level, const char* filename, unsigned int line_nr, | Entry CreateEntry(Class log_class, Level log_level, const char* filename, unsigned int line_nr, | ||||||
|                   const char* function, const char* format, va_list args); |                   const char* function, const char* format, const std::string& message); | ||||||
| 
 | 
 | ||||||
| void SetFilter(Filter* filter); | /**
 | ||||||
| } |  * The global filter will prevent any messages from even being processed if they are filtered. Each | ||||||
|  |  * backend can have a filter, but if the level is lower than the global filter, the backend will | ||||||
|  |  * never get the message | ||||||
|  |  */ | ||||||
|  | void SetGlobalFilter(const Filter& filter); | ||||||
|  | 
 | ||||||
|  | } // namespace Log
 | ||||||
|  |  | ||||||
|  | @ -19,7 +19,7 @@ namespace Log { | ||||||
| class Filter { | class Filter { | ||||||
| public: | public: | ||||||
|     /// Initializes the filter with all classes having `default_level` as the minimum level.
 |     /// Initializes the filter with all classes having `default_level` as the minimum level.
 | ||||||
|     Filter(Level default_level); |     Filter(Level default_level = Level::Info); | ||||||
| 
 | 
 | ||||||
|     /// Resets the filter so that all classes have `level` as the minimum displayed level.
 |     /// Resets the filter so that all classes have `level` as the minimum displayed level.
 | ||||||
|     void ResetAll(Level level); |     void ResetAll(Level level); | ||||||
|  | @ -50,4 +50,4 @@ public: | ||||||
| private: | private: | ||||||
|     std::array<Level, (size_t)Class::Count> class_levels; |     std::array<Level, (size_t)Class::Count> class_levels; | ||||||
| }; | }; | ||||||
| } | } // namespace Log
 | ||||||
|  |  | ||||||
|  | @ -4,6 +4,7 @@ | ||||||
| 
 | 
 | ||||||
| #pragma once | #pragma once | ||||||
| 
 | 
 | ||||||
|  | #include <fmt/format.h> | ||||||
| #include "common/common_types.h" | #include "common/common_types.h" | ||||||
| 
 | 
 | ||||||
| namespace Log { | namespace Log { | ||||||
|  | @ -98,7 +99,7 @@ enum class Class : ClassType { | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| /// Logs a message to the global logger.
 | /// Logs a message to the global logger.
 | ||||||
| void LogMessage(Class log_class, Level log_level, const char* filename, unsigned int line_nr, | void LogMessage(Class log_class, Level log_level, const char* filename, unsigned int line_num, | ||||||
|                 const char* function, |                 const char* function, | ||||||
| #ifdef _MSC_VER | #ifdef _MSC_VER | ||||||
|                 _Printf_format_string_ |                 _Printf_format_string_ | ||||||
|  | @ -110,6 +111,12 @@ void LogMessage(Class log_class, Level log_level, const char* filename, unsigned | ||||||
| #endif | #endif | ||||||
|     ; |     ; | ||||||
| 
 | 
 | ||||||
|  | /// Logs a message to the global logger, this time with 100% moar fmtlib
 | ||||||
|  | void FmtLogMessage(Class log_class, Level log_level, const char* filename, unsigned int line_num, | ||||||
|  |                    const char* function, const char* format, fmt::ArgList); | ||||||
|  | 
 | ||||||
|  | FMT_VARIADIC(void, FmtLogMessage, Class, Level, const char*, unsigned int, const char*, const char*) | ||||||
|  | 
 | ||||||
| } // namespace Log
 | } // namespace Log
 | ||||||
| 
 | 
 | ||||||
| #define LOG_GENERIC(log_class, log_level, ...)                                                     \ | #define LOG_GENERIC(log_class, log_level, ...)                                                     \ | ||||||
|  | @ -132,3 +139,28 @@ void LogMessage(Class log_class, Level log_level, const char* filename, unsigned | ||||||
|     LOG_GENERIC(::Log::Class::log_class, ::Log::Level::Error, __VA_ARGS__) |     LOG_GENERIC(::Log::Class::log_class, ::Log::Level::Error, __VA_ARGS__) | ||||||
| #define LOG_CRITICAL(log_class, ...)                                                               \ | #define LOG_CRITICAL(log_class, ...)                                                               \ | ||||||
|     LOG_GENERIC(::Log::Class::log_class, ::Log::Level::Critical, __VA_ARGS__) |     LOG_GENERIC(::Log::Class::log_class, ::Log::Level::Critical, __VA_ARGS__) | ||||||
|  | 
 | ||||||
|  | // Define the fmt lib macros
 | ||||||
|  | #ifdef _DEBUG | ||||||
|  | #define NGLOG_TRACE(log_class, fmt, ...)                                                           \ | ||||||
|  |     ::Log::FmtLogMessage(::Log::Class::log_class, ::Log::Level::Trace, __FILE__, __LINE__,         \ | ||||||
|  |                          __func__, fmt, ##__VA_ARGS__) | ||||||
|  | #else | ||||||
|  | #define NGLOG_TRACE(log_class, fmt, ...) (void(0)) | ||||||
|  | #endif | ||||||
|  | 
 | ||||||
|  | #define NGLOG_DEBUG(log_class, fmt, ...)                                                           \ | ||||||
|  |     ::Log::FmtLogMessage(::Log::Class::log_class, ::Log::Level::Debug, __FILE__, __LINE__,         \ | ||||||
|  |                          __func__, fmt, ##__VA_ARGS__) | ||||||
|  | #define NGLOG_INFO(log_class, fmt, ...)                                                            \ | ||||||
|  |     ::Log::FmtLogMessage(::Log::Class::log_class, ::Log::Level::Info, __FILE__, __LINE__,          \ | ||||||
|  |                          __func__, fmt, ##__VA_ARGS__) | ||||||
|  | #define NGLOG_WARNING(log_class, fmt, ...)                                                         \ | ||||||
|  |     ::Log::FmtLogMessage(::Log::Class::log_class, ::Log::Level::Warning, __FILE__, __LINE__,       \ | ||||||
|  |                          __func__, fmt, ##__VA_ARGS__) | ||||||
|  | #define NGLOG_ERROR(log_class, fmt, ...)                                                           \ | ||||||
|  |     ::Log::FmtLogMessage(::Log::Class::log_class, ::Log::Level::Error, __FILE__, __LINE__,         \ | ||||||
|  |                          __func__, fmt, ##__VA_ARGS__) | ||||||
|  | #define NGLOG_CRITICAL(log_class, fmt, ...)                                                        \ | ||||||
|  |     ::Log::FmtLogMessage(::Log::Class::log_class, ::Log::Level::Critical, __FILE__, __LINE__,      \ | ||||||
|  |                          __func__, fmt, ##__VA_ARGS__) | ||||||
|  |  | ||||||
|  | @ -18,50 +18,26 @@ | ||||||
| 
 | 
 | ||||||
| namespace Log { | namespace Log { | ||||||
| 
 | 
 | ||||||
| // TODO(bunnei): This should be moved to a generic path manipulation library
 | std::string FormatLogMessage(const Entry& entry) { | ||||||
| const char* TrimSourcePath(const char* path, const char* root) { |  | ||||||
|     const char* p = path; |  | ||||||
| 
 |  | ||||||
|     while (*p != '\0') { |  | ||||||
|         const char* next_slash = p; |  | ||||||
|         while (*next_slash != '\0' && *next_slash != '/' && *next_slash != '\\') { |  | ||||||
|             ++next_slash; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         bool is_src = Common::ComparePartialString(p, next_slash, root); |  | ||||||
|         p = next_slash; |  | ||||||
| 
 |  | ||||||
|         if (*p != '\0') { |  | ||||||
|             ++p; |  | ||||||
|         } |  | ||||||
|         if (is_src) { |  | ||||||
|             path = p; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|     return path; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void FormatLogMessage(const Entry& entry, char* out_text, size_t text_len) { |  | ||||||
|     unsigned int time_seconds = static_cast<unsigned int>(entry.timestamp.count() / 1000000); |     unsigned int time_seconds = static_cast<unsigned int>(entry.timestamp.count() / 1000000); | ||||||
|     unsigned int time_fractional = static_cast<unsigned int>(entry.timestamp.count() % 1000000); |     unsigned int time_fractional = static_cast<unsigned int>(entry.timestamp.count() % 1000000); | ||||||
| 
 | 
 | ||||||
|     const char* class_name = GetLogClassName(entry.log_class); |     const char* class_name = GetLogClassName(entry.log_class); | ||||||
|     const char* level_name = GetLevelName(entry.log_level); |     const char* level_name = GetLevelName(entry.log_level); | ||||||
| 
 | 
 | ||||||
|     snprintf(out_text, text_len, "[%4u.%06u] %s <%s> %s: %s", time_seconds, time_fractional, |     return fmt::format("[{:4d}.{:06d}] {} <{}> {}:{}:{}: {}", time_seconds, time_fractional, | ||||||
|              class_name, level_name, TrimSourcePath(entry.location.c_str()), entry.message.c_str()); |                        class_name, level_name, entry.filename, entry.function, entry.line_num, | ||||||
|  |                        entry.message); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void PrintMessage(const Entry& entry) { | void PrintMessage(const Entry& entry) { | ||||||
|     std::array<char, 4 * 1024> format_buffer; |     auto str = FormatLogMessage(entry) + "\n"; | ||||||
|     FormatLogMessage(entry, format_buffer.data(), format_buffer.size()); |     fputs(str.c_str(), stderr); | ||||||
|     fputs(format_buffer.data(), stderr); |  | ||||||
|     fputc('\n', stderr); |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void PrintColoredMessage(const Entry& entry) { | void PrintColoredMessage(const Entry& entry) { | ||||||
| #ifdef _WIN32 | #ifdef _WIN32 | ||||||
|     static HANDLE console_handle = GetStdHandle(STD_ERROR_HANDLE); |     HANDLE console_handle = GetStdHandle(STD_ERROR_HANDLE); | ||||||
| 
 | 
 | ||||||
|     CONSOLE_SCREEN_BUFFER_INFO original_info = {0}; |     CONSOLE_SCREEN_BUFFER_INFO original_info = {0}; | ||||||
|     GetConsoleScreenBufferInfo(console_handle, &original_info); |     GetConsoleScreenBufferInfo(console_handle, &original_info); | ||||||
|  | @ -129,4 +105,4 @@ void PrintColoredMessage(const Entry& entry) { | ||||||
| #undef ESC | #undef ESC | ||||||
| #endif | #endif | ||||||
| } | } | ||||||
| } | } // namespace Log
 | ||||||
|  |  | ||||||
|  | @ -10,22 +10,10 @@ namespace Log { | ||||||
| 
 | 
 | ||||||
| struct Entry; | struct Entry; | ||||||
| 
 | 
 | ||||||
| /**
 |  | ||||||
|  * Attempts to trim an arbitrary prefix from `path`, leaving only the part starting at `root`. It's |  | ||||||
|  * intended to be used to strip a system-specific build directory from the `__FILE__` macro, |  | ||||||
|  * leaving only the path relative to the sources root. |  | ||||||
|  * |  | ||||||
|  * @param path The input file path as a null-terminated string |  | ||||||
|  * @param root The name of the root source directory as a null-terminated string. Path up to and |  | ||||||
|  *             including the last occurrence of this name will be stripped |  | ||||||
|  * @return A pointer to the same string passed as `path`, but starting at the trimmed portion |  | ||||||
|  */ |  | ||||||
| const char* TrimSourcePath(const char* path, const char* root = "src"); |  | ||||||
| 
 |  | ||||||
| /// Formats a log entry into the provided text buffer.
 | /// Formats a log entry into the provided text buffer.
 | ||||||
| void FormatLogMessage(const Entry& entry, char* out_text, size_t text_len); | std::string FormatLogMessage(const Entry& entry); | ||||||
| /// Formats and prints a log entry to stderr.
 | /// Formats and prints a log entry to stderr.
 | ||||||
| void PrintMessage(const Entry& entry); | void PrintMessage(const Entry& entry); | ||||||
| /// Prints the same message as `PrintMessage`, but colored acoording to the severity level.
 | /// Prints the same message as `PrintMessage`, but colored acoording to the severity level.
 | ||||||
| void PrintColoredMessage(const Entry& entry); | void PrintColoredMessage(const Entry& entry); | ||||||
| } | } // namespace Log
 | ||||||
|  |  | ||||||
|  | @ -202,7 +202,7 @@ bool SplitPath(const std::string& full_path, std::string* _pPath, std::string* _ | ||||||
| #ifdef _WIN32 | #ifdef _WIN32 | ||||||
|                                             ":" |                                             ":" | ||||||
| #endif | #endif | ||||||
|                                             ); |     ); | ||||||
|     if (std::string::npos == dir_end) |     if (std::string::npos == dir_end) | ||||||
|         dir_end = 0; |         dir_end = 0; | ||||||
|     else |     else | ||||||
|  | @ -462,4 +462,26 @@ std::string StringFromFixedZeroTerminatedBuffer(const char* buffer, size_t max_l | ||||||
| 
 | 
 | ||||||
|     return std::string(buffer, len); |     return std::string(buffer, len); | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | const char* TrimSourcePath(const char* path, const char* root) { | ||||||
|  |     const char* p = path; | ||||||
|  | 
 | ||||||
|  |     while (*p != '\0') { | ||||||
|  |         const char* next_slash = p; | ||||||
|  |         while (*next_slash != '\0' && *next_slash != '/' && *next_slash != '\\') { | ||||||
|  |             ++next_slash; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         bool is_src = Common::ComparePartialString(p, next_slash, root); | ||||||
|  |         p = next_slash; | ||||||
|  | 
 | ||||||
|  |         if (*p != '\0') { | ||||||
|  |             ++p; | ||||||
|  |         } | ||||||
|  |         if (is_src) { | ||||||
|  |             path = p; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     return path; | ||||||
| } | } | ||||||
|  | } // namespace Common
 | ||||||
|  |  | ||||||
|  | @ -134,4 +134,17 @@ bool ComparePartialString(InIt begin, InIt end, const char* other) { | ||||||
|  * NUL-terminated then the string ends at max_len characters. |  * NUL-terminated then the string ends at max_len characters. | ||||||
|  */ |  */ | ||||||
| std::string StringFromFixedZeroTerminatedBuffer(const char* buffer, size_t max_len); | std::string StringFromFixedZeroTerminatedBuffer(const char* buffer, size_t max_len); | ||||||
| } | 
 | ||||||
|  | /**
 | ||||||
|  |  * Attempts to trim an arbitrary prefix from `path`, leaving only the part starting at `root`. It's | ||||||
|  |  * intended to be used to strip a system-specific build directory from the `__FILE__` macro, | ||||||
|  |  * leaving only the path relative to the sources root. | ||||||
|  |  * | ||||||
|  |  * @param path The input file path as a null-terminated string | ||||||
|  |  * @param root The name of the root source directory as a null-terminated string. Path up to and | ||||||
|  |  *             including the last occurrence of this name will be stripped | ||||||
|  |  * @return A pointer to the same string passed as `path`, but starting at the trimmed portion | ||||||
|  |  */ | ||||||
|  | const char* TrimSourcePath(const char* path, const char* root = "src"); | ||||||
|  | 
 | ||||||
|  | } // namespace Common
 | ||||||
|  |  | ||||||
|  | @ -4,14 +4,13 @@ | ||||||
| 
 | 
 | ||||||
| #include "audio_core/dsp_interface.h" | #include "audio_core/dsp_interface.h" | ||||||
| #include "core/core.h" | #include "core/core.h" | ||||||
|  | #include "core/frontend/emu_window.h" | ||||||
| #include "core/gdbstub/gdbstub.h" | #include "core/gdbstub/gdbstub.h" | ||||||
| #include "core/hle/service/hid/hid.h" | #include "core/hle/service/hid/hid.h" | ||||||
| #include "core/hle/service/ir/ir.h" | #include "core/hle/service/ir/ir.h" | ||||||
| #include "core/settings.h" | #include "core/settings.h" | ||||||
| #include "video_core/video_core.h" | #include "video_core/video_core.h" | ||||||
| 
 | 
 | ||||||
| #include "core/frontend/emu_window.h" |  | ||||||
| 
 |  | ||||||
| namespace Settings { | namespace Settings { | ||||||
| 
 | 
 | ||||||
| Values values = {}; | Values values = {}; | ||||||
|  |  | ||||||
|  | @ -54,9 +54,21 @@ constexpr int NUM_BUTTONS_IR = BUTTON_IR_END - BUTTON_IR_BEGIN; | ||||||
| constexpr int NUM_BUTTONS_NS = BUTTON_NS_END - BUTTON_NS_BEGIN; | constexpr int NUM_BUTTONS_NS = BUTTON_NS_END - BUTTON_NS_BEGIN; | ||||||
| 
 | 
 | ||||||
| static const std::array<const char*, NumButtons> mapping = {{ | static const std::array<const char*, NumButtons> mapping = {{ | ||||||
|     "button_a", "button_b", "button_x", "button_y", "button_up", "button_down", "button_left", |     "button_a", | ||||||
|     "button_right", "button_l", "button_r", "button_start", "button_select", "button_zl", |     "button_b", | ||||||
|     "button_zr", "button_home", |     "button_x", | ||||||
|  |     "button_y", | ||||||
|  |     "button_up", | ||||||
|  |     "button_down", | ||||||
|  |     "button_left", | ||||||
|  |     "button_right", | ||||||
|  |     "button_l", | ||||||
|  |     "button_r", | ||||||
|  |     "button_start", | ||||||
|  |     "button_select", | ||||||
|  |     "button_zl", | ||||||
|  |     "button_zr", | ||||||
|  |     "button_home", | ||||||
| }}; | }}; | ||||||
| } // namespace NativeButton
 | } // namespace NativeButton
 | ||||||
| 
 | 
 | ||||||
|  | @ -69,7 +81,8 @@ enum Values { | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| static const std::array<const char*, NumAnalogs> mapping = {{ | static const std::array<const char*, NumAnalogs> mapping = {{ | ||||||
|     "circle_pad", "c_stick", |     "circle_pad", | ||||||
|  |     "c_stick", | ||||||
| }}; | }}; | ||||||
| } // namespace NativeAnalog
 | } // namespace NativeAnalog
 | ||||||
| 
 | 
 | ||||||
|  | @ -116,8 +129,6 @@ struct Values { | ||||||
|     float bg_green; |     float bg_green; | ||||||
|     float bg_blue; |     float bg_blue; | ||||||
| 
 | 
 | ||||||
|     std::string log_filter; |  | ||||||
| 
 |  | ||||||
|     // Audio
 |     // Audio
 | ||||||
|     std::string sink_id; |     std::string sink_id; | ||||||
|     bool enable_audio_stretching; |     bool enable_audio_stretching; | ||||||
|  | @ -130,6 +141,7 @@ struct Values { | ||||||
|     // Debugging
 |     // Debugging
 | ||||||
|     bool use_gdbstub; |     bool use_gdbstub; | ||||||
|     u16 gdbstub_port; |     u16 gdbstub_port; | ||||||
|  |     std::string log_filter; | ||||||
| 
 | 
 | ||||||
|     // Movie
 |     // Movie
 | ||||||
|     std::string movie_play; |     std::string movie_play; | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue