summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/common/common_paths.h3
-rw-r--r--src/common/file_util.cpp17
-rw-r--r--src/common/file_util.h11
-rw-r--r--src/common/logging/backend.cpp157
-rw-r--r--src/common/logging/backend.h87
-rw-r--r--src/yuzu/CMakeLists.txt10
-rw-r--r--src/yuzu/configuration/config.cpp3
-rw-r--r--src/yuzu/configuration/configure_debug.cpp22
-rw-r--r--src/yuzu/configuration/configure_debug.ui41
-rw-r--r--src/yuzu/debugger/console.cpp45
-rw-r--r--src/yuzu/debugger/console.h14
-rw-r--r--src/yuzu/main.cpp10
-rw-r--r--src/yuzu/ui_settings.h3
-rw-r--r--src/yuzu_cmd/yuzu.cpp7
14 files changed, 408 insertions, 22 deletions
diff --git a/src/common/common_paths.h b/src/common/common_paths.h
index 0a6132dab..d03fca314 100644
--- a/src/common/common_paths.h
+++ b/src/common/common_paths.h
@@ -32,12 +32,15 @@
32#define SDMC_DIR "sdmc" 32#define SDMC_DIR "sdmc"
33#define NAND_DIR "nand" 33#define NAND_DIR "nand"
34#define SYSDATA_DIR "sysdata" 34#define SYSDATA_DIR "sysdata"
35#define LOG_DIR "log"
35 36
36// Filenames 37// Filenames
37// Files in the directory returned by GetUserPath(D_CONFIG_IDX) 38// Files in the directory returned by GetUserPath(D_CONFIG_IDX)
38#define EMU_CONFIG "emu.ini" 39#define EMU_CONFIG "emu.ini"
39#define DEBUGGER_CONFIG "debugger.ini" 40#define DEBUGGER_CONFIG "debugger.ini"
40#define LOGGER_CONFIG "logger.ini" 41#define LOGGER_CONFIG "logger.ini"
42// Files in the directory returned by GetUserPath(D_LOGS_IDX)
43#define LOG_FILE "citra_log.txt"
41 44
42// Sys files 45// Sys files
43#define SHARED_FONT "shared_font.bin" 46#define SHARED_FONT "shared_font.bin"
diff --git a/src/common/file_util.cpp b/src/common/file_util.cpp
index 2152e3fea..b9e1fd1f6 100644
--- a/src/common/file_util.cpp
+++ b/src/common/file_util.cpp
@@ -713,6 +713,8 @@ const std::string& GetUserPath(const unsigned int DirIDX, const std::string& new
713 paths[D_SDMC_IDX] = paths[D_USER_IDX] + SDMC_DIR DIR_SEP; 713 paths[D_SDMC_IDX] = paths[D_USER_IDX] + SDMC_DIR DIR_SEP;
714 paths[D_NAND_IDX] = paths[D_USER_IDX] + NAND_DIR DIR_SEP; 714 paths[D_NAND_IDX] = paths[D_USER_IDX] + NAND_DIR DIR_SEP;
715 paths[D_SYSDATA_IDX] = paths[D_USER_IDX] + SYSDATA_DIR DIR_SEP; 715 paths[D_SYSDATA_IDX] = paths[D_USER_IDX] + SYSDATA_DIR DIR_SEP;
716 // TODO: Put the logs in a better location for each OS
717 paths[D_LOGS_IDX] = paths[D_USER_IDX] + LOG_DIR DIR_SEP;
716 } 718 }
717 719
718 if (!newPath.empty()) { 720 if (!newPath.empty()) {
@@ -799,8 +801,8 @@ void SplitFilename83(const std::string& filename, std::array<char, 9>& short_nam
799 801
800IOFile::IOFile() {} 802IOFile::IOFile() {}
801 803
802IOFile::IOFile(const std::string& filename, const char openmode[]) { 804IOFile::IOFile(const std::string& filename, const char openmode[], int flags) {
803 Open(filename, openmode); 805 Open(filename, openmode, flags);
804} 806}
805 807
806IOFile::~IOFile() { 808IOFile::~IOFile() {
@@ -821,11 +823,16 @@ void IOFile::Swap(IOFile& other) noexcept {
821 std::swap(m_good, other.m_good); 823 std::swap(m_good, other.m_good);
822} 824}
823 825
824bool IOFile::Open(const std::string& filename, const char openmode[]) { 826bool IOFile::Open(const std::string& filename, const char openmode[], int flags) {
825 Close(); 827 Close();
826#ifdef _WIN32 828#ifdef _WIN32
827 _wfopen_s(&m_file, Common::UTF8ToUTF16W(filename).c_str(), 829 if (flags != 0) {
828 Common::UTF8ToUTF16W(openmode).c_str()); 830 m_file = _wfsopen(Common::UTF8ToUTF16W(filename).c_str(),
831 Common::UTF8ToUTF16W(openmode).c_str(), flags);
832 } else {
833 _wfopen_s(&m_file, Common::UTF8ToUTF16W(filename).c_str(),
834 Common::UTF8ToUTF16W(openmode).c_str());
835 }
829#else 836#else
830 m_file = fopen(filename.c_str(), openmode); 837 m_file = fopen(filename.c_str(), openmode);
831#endif 838#endif
diff --git a/src/common/file_util.h b/src/common/file_util.h
index fc6b3ea46..5bc7fbf7c 100644
--- a/src/common/file_util.h
+++ b/src/common/file_util.h
@@ -156,7 +156,10 @@ void SplitFilename83(const std::string& filename, std::array<char, 9>& short_nam
156class IOFile : public NonCopyable { 156class IOFile : public NonCopyable {
157public: 157public:
158 IOFile(); 158 IOFile();
159 IOFile(const std::string& filename, const char openmode[]); 159 // flags is used for windows specific file open mode flags, which
160 // allows yuzu to open the logs in shared write mode, so that the file
161 // isn't considered "locked" while yuzu is open and people can open the log file and view it
162 IOFile(const std::string& filename, const char openmode[], int flags = 0);
160 163
161 ~IOFile(); 164 ~IOFile();
162 165
@@ -165,7 +168,7 @@ public:
165 168
166 void Swap(IOFile& other) noexcept; 169 void Swap(IOFile& other) noexcept;
167 170
168 bool Open(const std::string& filename, const char openmode[]); 171 bool Open(const std::string& filename, const char openmode[], int flags = 0);
169 bool Close(); 172 bool Close();
170 173
171 template <typename T> 174 template <typename T>
@@ -220,6 +223,10 @@ public:
220 return WriteArray(&object, 1); 223 return WriteArray(&object, 1);
221 } 224 }
222 225
226 size_t WriteString(const std::string& str) {
227 return WriteArray(str.c_str(), str.length());
228 }
229
223 bool IsOpen() const { 230 bool IsOpen() const {
224 return nullptr != m_file; 231 return nullptr != m_file;
225 } 232 }
diff --git a/src/common/logging/backend.cpp b/src/common/logging/backend.cpp
index c26b20062..242914c6a 100644
--- a/src/common/logging/backend.cpp
+++ b/src/common/logging/backend.cpp
@@ -2,16 +2,145 @@
2// Licensed under GPLv2 or any later version 2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included. 3// Refer to the license.txt file included.
4 4
5#include <utility> 5#include <algorithm>
6#include <array>
7#include <chrono>
8#include <condition_variable>
9#include <memory>
10#include <thread>
11#ifdef _WIN32
12#include <share.h> // For _SH_DENYWR
13#else
14#define _SH_DENYWR 0
15#endif
6#include "common/assert.h" 16#include "common/assert.h"
17#include "common/common_funcs.h" // snprintf compatibility define
7#include "common/logging/backend.h" 18#include "common/logging/backend.h"
8#include "common/logging/filter.h"
9#include "common/logging/log.h" 19#include "common/logging/log.h"
10#include "common/logging/text_formatter.h" 20#include "common/logging/text_formatter.h"
11#include "common/string_util.h" 21#include "common/string_util.h"
22#include "common/threadsafe_queue.h"
12 23
13namespace Log { 24namespace Log {
14 25
26/**
27 * Static state as a singleton.
28 */
29class Impl {
30public:
31 static Impl& Instance() {
32 static Impl backend;
33 return backend;
34 }
35
36 Impl(Impl const&) = delete;
37 const Impl& operator=(Impl const&) = delete;
38
39 void PushEntry(Entry e) {
40 std::lock_guard<std::mutex> lock(message_mutex);
41 message_queue.Push(std::move(e));
42 message_cv.notify_one();
43 }
44
45 void AddBackend(std::unique_ptr<Backend> backend) {
46 std::lock_guard<std::mutex> lock(writing_mutex);
47 backends.push_back(std::move(backend));
48 }
49
50 void RemoveBackend(const std::string& backend_name) {
51 std::lock_guard<std::mutex> lock(writing_mutex);
52 auto it = std::remove_if(backends.begin(), backends.end(), [&backend_name](const auto& i) {
53 return !strcmp(i->GetName(), backend_name.c_str());
54 });
55 backends.erase(it, backends.end());
56 }
57
58 const Filter& GetGlobalFilter() const {
59 return filter;
60 }
61
62 void SetGlobalFilter(const Filter& f) {
63 filter = f;
64 }
65
66 Backend* GetBackend(const std::string& backend_name) {
67 auto it = std::find_if(backends.begin(), backends.end(), [&backend_name](const auto& i) {
68 return !strcmp(i->GetName(), backend_name.c_str());
69 });
70 if (it == backends.end())
71 return nullptr;
72 return it->get();
73 }
74
75private:
76 Impl() {
77 backend_thread = std::thread([&] {
78 Entry entry;
79 auto write_logs = [&](Entry& e) {
80 std::lock_guard<std::mutex> lock(writing_mutex);
81 for (const auto& backend : backends) {
82 backend->Write(e);
83 }
84 };
85 while (true) {
86 std::unique_lock<std::mutex> lock(message_mutex);
87 message_cv.wait(lock, [&] { return !running || message_queue.Pop(entry); });
88 if (!running) {
89 break;
90 }
91 write_logs(entry);
92 }
93 // Drain the logging queue. Only writes out up to MAX_LOGS_TO_WRITE to prevent a case
94 // where a system is repeatedly spamming logs even on close.
95 constexpr int MAX_LOGS_TO_WRITE = 100;
96 int logs_written = 0;
97 while (logs_written++ < MAX_LOGS_TO_WRITE && message_queue.Pop(entry)) {
98 write_logs(entry);
99 }
100 });
101 }
102
103 ~Impl() {
104 running = false;
105 message_cv.notify_one();
106 backend_thread.join();
107 }
108
109 std::atomic_bool running{true};
110 std::mutex message_mutex, writing_mutex;
111 std::condition_variable message_cv;
112 std::thread backend_thread;
113 std::vector<std::unique_ptr<Backend>> backends;
114 Common::MPSCQueue<Log::Entry> message_queue;
115 Filter filter;
116};
117
118void ConsoleBackend::Write(const Entry& entry) {
119 PrintMessage(entry);
120}
121
122void ColorConsoleBackend::Write(const Entry& entry) {
123 PrintColoredMessage(entry);
124}
125
126// _SH_DENYWR allows read only access to the file for other programs.
127// It is #defined to 0 on other platforms
128FileBackend::FileBackend(const std::string& filename)
129 : file(filename, "w", _SH_DENYWR), bytes_written(0) {}
130
131void FileBackend::Write(const Entry& entry) {
132 // prevent logs from going over the maximum size (in case its spamming and the user doesn't
133 // know)
134 constexpr size_t MAX_BYTES_WRITTEN = 50 * 1024L * 1024L;
135 if (!file.IsOpen() || bytes_written > MAX_BYTES_WRITTEN) {
136 return;
137 }
138 bytes_written += file.WriteString(FormatLogMessage(entry) + '\n');
139 if (entry.log_level >= Level::Error) {
140 file.Flush();
141 }
142}
143
15/// Macro listing all log classes. Code should define CLS and SUB as desired before invoking this. 144/// Macro listing all log classes. Code should define CLS and SUB as desired before invoking this.
16#define ALL_LOG_CLASSES() \ 145#define ALL_LOG_CLASSES() \
17 CLS(Log) \ 146 CLS(Log) \
@@ -125,20 +254,32 @@ Entry CreateEntry(Class log_class, Level log_level, const char* filename, unsign
125 return entry; 254 return entry;
126} 255}
127 256
128static Filter* filter = nullptr; 257void SetGlobalFilter(const Filter& filter) {
258 Impl::Instance().SetGlobalFilter(filter);
259}
260
261void AddBackend(std::unique_ptr<Backend> backend) {
262 Impl::Instance().AddBackend(std::move(backend));
263}
129 264
130void SetFilter(Filter* new_filter) { 265void RemoveBackend(const std::string& backend_name) {
131 filter = new_filter; 266 Impl::Instance().RemoveBackend(backend_name);
267}
268
269Backend* GetBackend(const std::string& backend_name) {
270 return Impl::Instance().GetBackend(backend_name);
132} 271}
133 272
134void FmtLogMessageImpl(Class log_class, Level log_level, const char* filename, 273void FmtLogMessageImpl(Class log_class, Level log_level, const char* filename,
135 unsigned int line_num, const char* function, const char* format, 274 unsigned int line_num, const char* function, const char* format,
136 const fmt::format_args& args) { 275 const fmt::format_args& args) {
137 if (filter && !filter->CheckMessage(log_class, log_level)) 276 auto filter = Impl::Instance().GetGlobalFilter();
277 if (!filter.CheckMessage(log_class, log_level))
138 return; 278 return;
279
139 Entry entry = 280 Entry entry =
140 CreateEntry(log_class, log_level, filename, line_num, function, fmt::vformat(format, args)); 281 CreateEntry(log_class, log_level, filename, line_num, function, fmt::vformat(format, args));
141 282
142 PrintColoredMessage(entry); 283 Impl::Instance().PushEntry(std::move(entry));
143} 284}
144} // namespace Log 285} // namespace Log \ No newline at end of file
diff --git a/src/common/logging/backend.h b/src/common/logging/backend.h
index 7e81efb23..57cdf6b2d 100644
--- a/src/common/logging/backend.h
+++ b/src/common/logging/backend.h
@@ -1,13 +1,15 @@
1// Copyright 2014 Citra Emulator Project 1// Copyright 2014 Citra Emulator Project
2// Licensed under GPLv2 or any later version 2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included. 3// Refer to the license.txt file included.
4
5#pragma once 4#pragma once
6 5
7#include <chrono> 6#include <chrono>
8#include <cstdarg> 7#include <cstdarg>
8#include <memory>
9#include <string> 9#include <string>
10#include <utility> 10#include <utility>
11#include "common/file_util.h"
12#include "common/logging/filter.h"
11#include "common/logging/log.h" 13#include "common/logging/log.h"
12 14
13namespace Log { 15namespace Log {
@@ -35,6 +37,80 @@ struct Entry {
35}; 37};
36 38
37/** 39/**
40 * Interface for logging backends. As loggers can be created and removed at runtime, this can be
41 * used by a frontend for adding a custom logging backend as needed
42 */
43class Backend {
44public:
45 virtual ~Backend() = default;
46 virtual void SetFilter(const Filter& new_filter) {
47 filter = new_filter;
48 }
49 virtual const char* GetName() const = 0;
50 virtual void Write(const Entry& entry) = 0;
51
52private:
53 Filter filter;
54};
55
56/**
57 * Backend that writes to stderr without any color commands
58 */
59class ConsoleBackend : public Backend {
60public:
61 static const char* Name() {
62 return "console";
63 }
64 const char* GetName() const override {
65 return Name();
66 }
67 void Write(const Entry& entry) override;
68};
69
70/**
71 * Backend that writes to stderr and with color
72 */
73class ColorConsoleBackend : public Backend {
74public:
75 static const char* Name() {
76 return "color_console";
77 }
78
79 const char* GetName() const override {
80 return Name();
81 }
82 void Write(const Entry& entry) override;
83};
84
85/**
86 * Backend that writes to a file passed into the constructor
87 */
88class FileBackend : public Backend {
89public:
90 explicit FileBackend(const std::string& filename);
91
92 static const char* Name() {
93 return "file";
94 }
95
96 const char* GetName() const override {
97 return Name();
98 }
99
100 void Write(const Entry& entry) override;
101
102private:
103 FileUtil::IOFile file;
104 size_t bytes_written;
105};
106
107void AddBackend(std::unique_ptr<Backend> backend);
108
109void RemoveBackend(const std::string& backend_name);
110
111Backend* GetBackend(const std::string& backend_name);
112
113/**
38 * Returns the name of the passed log class as a C-string. Subclasses are separated by periods 114 * Returns the name of the passed log class as a C-string. Subclasses are separated by periods
39 * instead of underscores as in the enumeration. 115 * instead of underscores as in the enumeration.
40 */ 116 */
@@ -49,5 +125,10 @@ const char* GetLevelName(Level log_level);
49Entry CreateEntry(Class log_class, Level log_level, const char* filename, unsigned int line_nr, 125Entry CreateEntry(Class log_class, Level log_level, const char* filename, unsigned int line_nr,
50 const char* function, std::string message); 126 const char* function, std::string message);
51 127
52void SetFilter(Filter* filter); 128/**
53} // namespace Log 129 * The global filter will prevent any messages from even being processed if they are filtered. Each
130 * backend can have a filter, but if the level is lower than the global filter, the backend will
131 * never get the message
132 */
133void SetGlobalFilter(const Filter& filter);
134} // namespace Log \ No newline at end of file
diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt
index c662570d2..7de919a8e 100644
--- a/src/yuzu/CMakeLists.txt
+++ b/src/yuzu/CMakeLists.txt
@@ -30,6 +30,8 @@ add_executable(yuzu
30 debugger/graphics/graphics_breakpoints_p.h 30 debugger/graphics/graphics_breakpoints_p.h
31 debugger/graphics/graphics_surface.cpp 31 debugger/graphics/graphics_surface.cpp
32 debugger/graphics/graphics_surface.h 32 debugger/graphics/graphics_surface.h
33 debugger/console.cpp
34 debugger/console.h
33 debugger/profiler.cpp 35 debugger/profiler.cpp
34 debugger/profiler.h 36 debugger/profiler.h
35 debugger/wait_tree.cpp 37 debugger/wait_tree.cpp
@@ -81,6 +83,14 @@ if (APPLE)
81 target_sources(yuzu PRIVATE ${MACOSX_ICON}) 83 target_sources(yuzu PRIVATE ${MACOSX_ICON})
82 set_target_properties(yuzu PROPERTIES MACOSX_BUNDLE TRUE) 84 set_target_properties(yuzu PROPERTIES MACOSX_BUNDLE TRUE)
83 set_target_properties(yuzu PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/Info.plist) 85 set_target_properties(yuzu PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/Info.plist)
86elseif(WIN32)
87 # compile as a win32 gui application instead of a console application
88 target_link_libraries(yuzu PRIVATE Qt5::WinMain)
89 if(MSVC)
90 set_target_properties(yuzu PROPERTIES LINK_FLAGS_RELEASE "/SUBSYSTEM:WINDOWS")
91 elseif(MINGW)
92 set_target_properties(yuzu PROPERTIES LINK_FLAGS_RELEASE "-mwindows")
93 endif()
84endif() 94endif()
85 95
86create_target_directory_groups(yuzu) 96create_target_directory_groups(yuzu)
diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp
index cd7986efa..a32134fbe 100644
--- a/src/yuzu/configuration/config.cpp
+++ b/src/yuzu/configuration/config.cpp
@@ -160,6 +160,7 @@ void Config::ReadValues() {
160 UISettings::values.confirm_before_closing = qt_config->value("confirmClose", true).toBool(); 160 UISettings::values.confirm_before_closing = qt_config->value("confirmClose", true).toBool();
161 UISettings::values.first_start = qt_config->value("firstStart", true).toBool(); 161 UISettings::values.first_start = qt_config->value("firstStart", true).toBool();
162 UISettings::values.callout_flags = qt_config->value("calloutFlags", 0).toUInt(); 162 UISettings::values.callout_flags = qt_config->value("calloutFlags", 0).toUInt();
163 UISettings::values.show_console = qt_config->value("showConsole", false).toBool();
163 164
164 qt_config->endGroup(); 165 qt_config->endGroup();
165} 166}
@@ -246,7 +247,7 @@ void Config::SaveValues() {
246 qt_config->setValue("confirmClose", UISettings::values.confirm_before_closing); 247 qt_config->setValue("confirmClose", UISettings::values.confirm_before_closing);
247 qt_config->setValue("firstStart", UISettings::values.first_start); 248 qt_config->setValue("firstStart", UISettings::values.first_start);
248 qt_config->setValue("calloutFlags", UISettings::values.callout_flags); 249 qt_config->setValue("calloutFlags", UISettings::values.callout_flags);
249 250 qt_config->setValue("showConsole", UISettings::values.show_console);
250 qt_config->endGroup(); 251 qt_config->endGroup();
251} 252}
252 253
diff --git a/src/yuzu/configuration/configure_debug.cpp b/src/yuzu/configuration/configure_debug.cpp
index a45edd510..241db4ae3 100644
--- a/src/yuzu/configuration/configure_debug.cpp
+++ b/src/yuzu/configuration/configure_debug.cpp
@@ -2,13 +2,26 @@
2// Licensed under GPLv2 or any later version 2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included. 3// Refer to the license.txt file included.
4 4
5#include <QDesktopServices>
6#include <QUrl>
7#include "common/file_util.h"
8#include "common/logging/backend.h"
9#include "common/logging/filter.h"
10#include "common/logging/log.h"
11#include "core/core.h"
5#include "core/settings.h" 12#include "core/settings.h"
6#include "ui_configure_debug.h" 13#include "ui_configure_debug.h"
7#include "yuzu/configuration/configure_debug.h" 14#include "yuzu/configuration/configure_debug.h"
15#include "yuzu/debugger/console.h"
16#include "yuzu/ui_settings.h"
8 17
9ConfigureDebug::ConfigureDebug(QWidget* parent) : QWidget(parent), ui(new Ui::ConfigureDebug) { 18ConfigureDebug::ConfigureDebug(QWidget* parent) : QWidget(parent), ui(new Ui::ConfigureDebug) {
10 ui->setupUi(this); 19 ui->setupUi(this);
11 this->setConfiguration(); 20 this->setConfiguration();
21 connect(ui->open_log_button, &QPushButton::pressed, []() {
22 QString path = QString::fromStdString(FileUtil::GetUserPath(D_LOGS_IDX));
23 QDesktopServices::openUrl(QUrl::fromLocalFile(path));
24 });
12} 25}
13 26
14ConfigureDebug::~ConfigureDebug() {} 27ConfigureDebug::~ConfigureDebug() {}
@@ -17,10 +30,19 @@ void ConfigureDebug::setConfiguration() {
17 ui->toggle_gdbstub->setChecked(Settings::values.use_gdbstub); 30 ui->toggle_gdbstub->setChecked(Settings::values.use_gdbstub);
18 ui->gdbport_spinbox->setEnabled(Settings::values.use_gdbstub); 31 ui->gdbport_spinbox->setEnabled(Settings::values.use_gdbstub);
19 ui->gdbport_spinbox->setValue(Settings::values.gdbstub_port); 32 ui->gdbport_spinbox->setValue(Settings::values.gdbstub_port);
33 ui->toggle_console->setEnabled(!Core::System::GetInstance().IsPoweredOn());
34 ui->toggle_console->setChecked(UISettings::values.show_console);
35 ui->log_filter_edit->setText(QString::fromStdString(Settings::values.log_filter));
20} 36}
21 37
22void ConfigureDebug::applyConfiguration() { 38void ConfigureDebug::applyConfiguration() {
23 Settings::values.use_gdbstub = ui->toggle_gdbstub->isChecked(); 39 Settings::values.use_gdbstub = ui->toggle_gdbstub->isChecked();
24 Settings::values.gdbstub_port = ui->gdbport_spinbox->value(); 40 Settings::values.gdbstub_port = ui->gdbport_spinbox->value();
41 UISettings::values.show_console = ui->toggle_console->isChecked();
42 Settings::values.log_filter = ui->log_filter_edit->text().toStdString();
43 Debugger::ToggleConsole();
44 Log::Filter filter;
45 filter.ParseFilterString(Settings::values.log_filter);
46 Log::SetGlobalFilter(filter);
25 Settings::Apply(); 47 Settings::Apply();
26} 48}
diff --git a/src/yuzu/configuration/configure_debug.ui b/src/yuzu/configuration/configure_debug.ui
index a10bea2f4..118e91cf1 100644
--- a/src/yuzu/configuration/configure_debug.ui
+++ b/src/yuzu/configuration/configure_debug.ui
@@ -73,6 +73,47 @@
73 </layout> 73 </layout>
74 </item> 74 </item>
75 <item> 75 <item>
76 <widget class="QGroupBox" name="groupBox_2">
77 <property name="title">
78 <string>Logging</string>
79 </property>
80 <layout class="QVBoxLayout" name="verticalLayout">
81 <item>
82 <layout class="QHBoxLayout" name="horizontalLayout">
83 <item>
84 <widget class="QLabel" name="label">
85 <property name="text">
86 <string>Global Log Filter</string>
87 </property>
88 </widget>
89 </item>
90 <item>
91 <widget class="QLineEdit" name="log_filter_edit"/>
92 </item>
93 </layout>
94 </item>
95 <item>
96 <layout class="QHBoxLayout" name="horizontalLayout_2">
97 <item>
98 <widget class="QCheckBox" name="toggle_console">
99 <property name="text">
100 <string>Show Log Console (Windows Only)</string>
101 </property>
102 </widget>
103 </item>
104 <item>
105 <widget class="QPushButton" name="open_log_button">
106 <property name="text">
107 <string>Open Log Location</string>
108 </property>
109 </widget>
110 </item>
111 </layout>
112 </item>
113 </layout>
114 </widget>
115 </item>
116 <item>
76 <spacer name="verticalSpacer"> 117 <spacer name="verticalSpacer">
77 <property name="orientation"> 118 <property name="orientation">
78 <enum>Qt::Vertical</enum> 119 <enum>Qt::Vertical</enum>
diff --git a/src/yuzu/debugger/console.cpp b/src/yuzu/debugger/console.cpp
new file mode 100644
index 000000000..e3d2d975f
--- /dev/null
+++ b/src/yuzu/debugger/console.cpp
@@ -0,0 +1,45 @@
1// Copyright 2018 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#ifdef _WIN32
6#include <windows.h>
7
8#include <wincon.h>
9#endif
10
11#include "common/logging/backend.h"
12#include "yuzu/debugger/console.h"
13#include "yuzu/ui_settings.h"
14
15namespace Debugger {
16void ToggleConsole() {
17#if defined(_WIN32) && !defined(_DEBUG)
18 FILE* temp;
19 if (UISettings::values.show_console) {
20 if (AllocConsole()) {
21 // The first parameter for freopen_s is a out parameter, so we can just ignore it
22 freopen_s(&temp, "CONIN$", "r", stdin);
23 freopen_s(&temp, "CONOUT$", "w", stdout);
24 freopen_s(&temp, "CONOUT$", "w", stderr);
25 Log::AddBackend(std::make_unique<Log::ColorConsoleBackend>());
26 }
27 } else {
28 if (FreeConsole()) {
29 // In order to close the console, we have to also detach the streams on it.
30 // Just redirect them to NUL if there is no console window
31 Log::RemoveBackend(Log::ColorConsoleBackend::Name());
32 freopen_s(&temp, "NUL", "r", stdin);
33 freopen_s(&temp, "NUL", "w", stdout);
34 freopen_s(&temp, "NUL", "w", stderr);
35 }
36 }
37#else
38 if (UISettings::values.show_console) {
39 Log::AddBackend(std::make_unique<Log::ColorConsoleBackend>());
40 } else {
41 Log::RemoveBackend(Log::ColorConsoleBackend::Name());
42 }
43#endif
44}
45} // namespace Debugger
diff --git a/src/yuzu/debugger/console.h b/src/yuzu/debugger/console.h
new file mode 100644
index 000000000..d1990c496
--- /dev/null
+++ b/src/yuzu/debugger/console.h
@@ -0,0 +1,14 @@
1// Copyright 2018 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7namespace Debugger {
8
9/**
10 * Uses the WINAPI to hide or show the stderr console. This function is a placeholder until we can
11 * get a real qt logging window which would work for all platforms.
12 */
13void ToggleConsole();
14} // namespace Debugger \ No newline at end of file
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index 2c52415f9..05a8ae6d2 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -31,6 +31,7 @@
31#include "yuzu/bootmanager.h" 31#include "yuzu/bootmanager.h"
32#include "yuzu/configuration/config.h" 32#include "yuzu/configuration/config.h"
33#include "yuzu/configuration/configure_dialog.h" 33#include "yuzu/configuration/configure_dialog.h"
34#include "yuzu/debugger/console.h"
34#include "yuzu/debugger/graphics/graphics_breakpoints.h" 35#include "yuzu/debugger/graphics/graphics_breakpoints.h"
35#include "yuzu/debugger/graphics/graphics_surface.h" 36#include "yuzu/debugger/graphics/graphics_surface.h"
36#include "yuzu/debugger/profiler.h" 37#include "yuzu/debugger/profiler.h"
@@ -261,6 +262,7 @@ void GMainWindow::RestoreUIState() {
261 262
262 ui.action_Show_Status_Bar->setChecked(UISettings::values.show_status_bar); 263 ui.action_Show_Status_Bar->setChecked(UISettings::values.show_status_bar);
263 statusBar()->setVisible(ui.action_Show_Status_Bar->isChecked()); 264 statusBar()->setVisible(ui.action_Show_Status_Bar->isChecked());
265 Debugger::ToggleConsole();
264} 266}
265 267
266void GMainWindow::ConnectWidgetEvents() { 268void GMainWindow::ConnectWidgetEvents() {
@@ -906,8 +908,7 @@ void GMainWindow::UpdateUITheme() {
906#endif 908#endif
907 909
908int main(int argc, char* argv[]) { 910int main(int argc, char* argv[]) {
909 Log::Filter log_filter(Log::Level::Info); 911 Log::AddBackend(std::make_unique<Log::ColorConsoleBackend>());
910 Log::SetFilter(&log_filter);
911 912
912 MicroProfileOnThreadCreate("Frontend"); 913 MicroProfileOnThreadCreate("Frontend");
913 SCOPE_EXIT({ MicroProfileShutdown(); }); 914 SCOPE_EXIT({ MicroProfileShutdown(); });
@@ -925,7 +926,12 @@ int main(int argc, char* argv[]) {
925 926
926 GMainWindow main_window; 927 GMainWindow main_window;
927 // After settings have been loaded by GMainWindow, apply the filter 928 // After settings have been loaded by GMainWindow, apply the filter
929 Log::Filter log_filter;
928 log_filter.ParseFilterString(Settings::values.log_filter); 930 log_filter.ParseFilterString(Settings::values.log_filter);
931 Log::SetGlobalFilter(log_filter);
932 FileUtil::CreateFullPath(FileUtil::GetUserPath(D_LOGS_IDX));
933 Log::AddBackend(
934 std::make_unique<Log::FileBackend>(FileUtil::GetUserPath(D_LOGS_IDX) + LOG_FILE));
929 935
930 main_window.show(); 936 main_window.show();
931 return app.exec(); 937 return app.exec();
diff --git a/src/yuzu/ui_settings.h b/src/yuzu/ui_settings.h
index 8e215a002..2286c2559 100644
--- a/src/yuzu/ui_settings.h
+++ b/src/yuzu/ui_settings.h
@@ -51,6 +51,9 @@ struct Values {
51 std::vector<Shortcut> shortcuts; 51 std::vector<Shortcut> shortcuts;
52 52
53 uint32_t callout_flags; 53 uint32_t callout_flags;
54
55 // logging
56 bool show_console;
54}; 57};
55 58
56extern Values values; 59extern Values values;
diff --git a/src/yuzu_cmd/yuzu.cpp b/src/yuzu_cmd/yuzu.cpp
index 079f93736..89a3e9f30 100644
--- a/src/yuzu_cmd/yuzu.cpp
+++ b/src/yuzu_cmd/yuzu.cpp
@@ -126,7 +126,12 @@ int main(int argc, char** argv) {
126#endif 126#endif
127 127
128 Log::Filter log_filter(Log::Level::Debug); 128 Log::Filter log_filter(Log::Level::Debug);
129 Log::SetFilter(&log_filter); 129 Log::SetGlobalFilter(log_filter);
130
131 Log::AddBackend(std::make_unique<Log::ColorConsoleBackend>());
132 FileUtil::CreateFullPath(FileUtil::GetUserPath(D_LOGS_IDX));
133 Log::AddBackend(
134 std::make_unique<Log::FileBackend>(FileUtil::GetUserPath(D_LOGS_IDX) + LOG_FILE));
130 135
131 MicroProfileOnThreadCreate("EmuThread"); 136 MicroProfileOnThreadCreate("EmuThread");
132 SCOPE_EXIT({ MicroProfileShutdown(); }); 137 SCOPE_EXIT({ MicroProfileShutdown(); });