288 changed files with 17607 additions and 14163 deletions
-
16.gitignore
-
6.gitmodules
-
1.travis-build.sh
-
13.travis-deps.sh
-
5.travis.yml
-
42CMakeLists.txt
-
1CONTRIBUTING.md
-
2Doxyfile
-
22README.md
-
1externals/boost
-
1externals/nihstro
-
14src/citra/CMakeLists.txt
-
25src/citra/citra.cpp
-
30src/citra/config.cpp
-
7src/citra/config.h
-
7src/citra/default_ini.h
-
26src/citra/emu_window/emu_window_glfw.cpp
-
2src/citra/emu_window/emu_window_glfw.h
-
11src/citra_qt/CMakeLists.txt
-
23src/citra_qt/bootmanager.cpp
-
58src/citra_qt/config.cpp
-
13src/citra_qt/config.h
-
4src/citra_qt/debugger/callstack.cpp
-
4src/citra_qt/debugger/disassembler.cpp
-
2src/citra_qt/debugger/graphics.cpp
-
2src/citra_qt/debugger/graphics.hxx
-
263src/citra_qt/debugger/graphics_breakpoints.cpp
-
53src/citra_qt/debugger/graphics_breakpoints.hxx
-
38src/citra_qt/debugger/graphics_breakpoints_p.hxx
-
275src/citra_qt/debugger/graphics_cmdlists.cpp
-
39src/citra_qt/debugger/graphics_cmdlists.hxx
-
283src/citra_qt/debugger/graphics_framebuffer.cpp
-
92src/citra_qt/debugger/graphics_framebuffer.hxx
-
4src/citra_qt/hotkeys.cpp
-
64src/citra_qt/main.cpp
-
303src/citra_qt/util/spinbox.cpp
-
88src/citra_qt/util/spinbox.hxx
-
14src/common/CMakeLists.txt
-
14src/common/bit_field.h
-
6src/common/break_points.cpp
-
4src/common/break_points.h
-
59src/common/chunk_file.h
-
4src/common/common.h
-
11src/common/common_funcs.h
-
23src/common/common_paths.h
-
2src/common/common_types.h
-
164src/common/concurrent_ring_buffer.h
-
319src/common/console_listener.cpp
-
38src/common/console_listener.h
-
4src/common/cpu_detect.h
-
2src/common/emu_window.cpp
-
2src/common/emu_window.h
-
24src/common/extended_trace.cpp
-
4src/common/fifo_queue.h
-
4src/common/file_search.cpp
-
4src/common/file_search.h
-
130src/common/file_util.cpp
-
11src/common/file_util.h
-
4src/common/hash.cpp
-
4src/common/hash.h
-
2src/common/key_map.cpp
-
2src/common/key_map.h
-
6src/common/linear_disk_cache.h
-
133src/common/log.h
-
199src/common/log_manager.cpp
-
166src/common/log_manager.h
-
151src/common/logging/backend.cpp
-
134src/common/logging/backend.h
-
132src/common/logging/filter.cpp
-
63src/common/logging/filter.h
-
115src/common/logging/log.h
-
136src/common/logging/text_formatter.cpp
-
41src/common/logging/text_formatter.h
-
16src/common/make_unique.h
-
4src/common/math_util.cpp
-
4src/common/math_util.h
-
34src/common/mem_arena.cpp
-
16src/common/memory_util.cpp
-
4src/common/memory_util.h
-
8src/common/misc.cpp
-
6src/common/msg_handler.cpp
-
4src/common/msg_handler.h
-
2src/common/platform.h
-
2src/common/scm_rev.h
-
37src/common/scope_exit.h
-
32src/common/string_util.cpp
-
19src/common/string_util.h
-
2src/common/symbols.cpp
-
2src/common/symbols.h
-
4src/common/thread.cpp
-
5src/common/thread.h
-
14src/common/thread_queue_list.h
-
4src/common/thunk.h
-
10src/common/timer.cpp
-
4src/common/timer.h
-
24src/common/utf8.cpp
-
57src/core/CMakeLists.txt
-
8src/core/arm/arm_interface.h
-
2src/core/arm/disassembler/load_symbol_map.cpp
-
2src/core/arm/disassembler/load_symbol_map.h
@ -1,3 +1,9 @@ |
|||
[submodule "externals/inih/inih"] |
|||
path = externals/inih/inih |
|||
url = https://github.com/svn2github/inih |
|||
[submodule "externals/boost"] |
|||
path = externals/boost |
|||
url = https://github.com/citra-emu/ext-boost.git |
|||
[submodule "externals/nihstro"] |
|||
path = externals/nihstro |
|||
url = https://github.com/neobrain/nihstro.git |
|||
@ -1,17 +1,31 @@ |
|||
citra emulator |
|||
Citra Emulator |
|||
============== |
|||
[](https://travis-ci.org/citra-emu/citra) |
|||
|
|||
An experimental open-source Nintendo 3DS emulator/debugger written in C++. Citra is written with portability in mind, with builds actively maintained for Windows, Linux and OS X. At this time, it only emulates a subset of 3DS hardware, and therefore is generally only useful for booting/debugging very simple homebrew demos. Citra is licensed under the GPLv2. Refer to the license.txt file included. Please read the [FAQ](https://github.com/citra-emu/citra/wiki/FAQ) before getting started with the project. |
|||
Citra is an experimental open-source Nintendo 3DS emulator/debugger written in C++. It is written with portability in mind, with builds actively maintained for Windows, Linux and OS X. Citra only emulates a subset of 3DS hardware, and therefore is generally only useful for running/debugging homebrew applications. At this time, Citra is even able to boot several commercial games! None of these run to a playable state, but we are working every day to advance the project forward. |
|||
|
|||
For development discussion, please join us @ #citra on [freenode](http://webchat.freenode.net/). |
|||
Citra is licensed under the GPLv2. Refer to the license.txt file included. Please read the [FAQ](https://github.com/citra-emu/citra/wiki/FAQ) before getting started with the project. |
|||
|
|||
For development discussion, please join us @ #citra on [freenode](http://webchat.freenode.net/?channels=citra). |
|||
|
|||
### Development |
|||
|
|||
If you want to contribute please take a took at the [Contributor's Guide](CONTRIBUTING.md), [Roadmap](https://github.com/citra-emu/citra/wiki/Roadmap) and [Developer Information](https://github.com/citra-emu/citra/wiki/Developer-Information) pages. You should as well contact any of the developers in the forum in order to know about the current state of the emulator. |
|||
If you want to contribute please take a look at the [Contributor's Guide](CONTRIBUTING.md), [Roadmap](https://github.com/citra-emu/citra/wiki/Roadmap) and [Developer Information](https://github.com/citra-emu/citra/wiki/Developer-Information) pages. You should as well contact any of the developers in the forum in order to know about the current state of the emulator. |
|||
|
|||
### Building |
|||
|
|||
* __Windows__: [Windows Build](https://github.com/citra-emu/citra/wiki/Windows-Build) |
|||
* __Linux__: [Linux Build](https://github.com/citra-emu/citra/wiki/Linux-Build) |
|||
* __OSX__: [OS X Build](https://github.com/citra-emu/citra/wiki/OS-X-Build) |
|||
|
|||
|
|||
### Support |
|||
If you like, you can [donate by PayPal](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=K899FANUJ2ZXW) - any donation received will go towards things like: |
|||
* 3DS consoles for developers to explore the hardware |
|||
* 3DS games for testing |
|||
* Any equipment required for homebrew |
|||
* Infrastructure setup |
|||
* Eventually 3D displays to get proper 3D output working |
|||
* ... etc ... |
|||
|
|||
We also more than gladly accept used 3DS consoles, preferrably ones with firmware 4.5 or lower! If you would like to give yours away, don't hesitate to join our IRC channel #citra on [Freenode](http://webchat.freenode.net/?channels=citra) and talk to neobrain or bunnei. Mind you, IRC is slow-paced, so it might be a while until people reply. If you're in a hurry you can just leave contact details in the channel or via private message and we'll get back to you. |
|||
@ -0,0 +1,263 @@ |
|||
// Copyright 2014 Citra Emulator Project
|
|||
// Licensed under GPLv2 or any later version
|
|||
// Refer to the license.txt file included.
|
|||
|
|||
#include <QMetaType>
|
|||
#include <QPushButton>
|
|||
#include <QTreeWidget>
|
|||
#include <QVBoxLayout>
|
|||
#include <QLabel>
|
|||
|
|||
#include "graphics_breakpoints.hxx"
|
|||
#include "graphics_breakpoints_p.hxx"
|
|||
|
|||
BreakPointModel::BreakPointModel(std::shared_ptr<Pica::DebugContext> debug_context, QObject* parent) |
|||
: QAbstractListModel(parent), context_weak(debug_context), |
|||
at_breakpoint(debug_context->at_breakpoint), |
|||
active_breakpoint(debug_context->active_breakpoint) |
|||
{ |
|||
|
|||
} |
|||
|
|||
int BreakPointModel::columnCount(const QModelIndex& parent) const |
|||
{ |
|||
return 2; |
|||
} |
|||
|
|||
int BreakPointModel::rowCount(const QModelIndex& parent) const |
|||
{ |
|||
return static_cast<int>(Pica::DebugContext::Event::NumEvents); |
|||
} |
|||
|
|||
QVariant BreakPointModel::data(const QModelIndex& index, int role) const |
|||
{ |
|||
const auto event = static_cast<Pica::DebugContext::Event>(index.row()); |
|||
|
|||
switch (role) { |
|||
case Qt::DisplayRole: |
|||
{ |
|||
switch (index.column()) { |
|||
case 0: |
|||
{ |
|||
static const std::map<Pica::DebugContext::Event, QString> map = { |
|||
{ Pica::DebugContext::Event::CommandLoaded, tr("Pica command loaded") }, |
|||
{ Pica::DebugContext::Event::CommandProcessed, tr("Pica command processed") }, |
|||
{ Pica::DebugContext::Event::IncomingPrimitiveBatch, tr("Incoming primitive batch") }, |
|||
{ Pica::DebugContext::Event::FinishedPrimitiveBatch, tr("Finished primitive batch") }, |
|||
{ Pica::DebugContext::Event::VertexLoaded, tr("Vertex Loaded") } |
|||
}; |
|||
|
|||
_dbg_assert_(Debug_GPU, map.size() == static_cast<size_t>(Pica::DebugContext::Event::NumEvents)); |
|||
|
|||
return (map.find(event) != map.end()) ? map.at(event) : QString(); |
|||
} |
|||
|
|||
case 1: |
|||
return data(index, Role_IsEnabled).toBool() ? tr("Enabled") : tr("Disabled"); |
|||
|
|||
default: |
|||
break; |
|||
} |
|||
|
|||
break; |
|||
} |
|||
|
|||
case Qt::BackgroundRole: |
|||
{ |
|||
if (at_breakpoint && index.row() == static_cast<int>(active_breakpoint)) { |
|||
return QBrush(QColor(0xE0, 0xE0, 0x10)); |
|||
} |
|||
break; |
|||
} |
|||
|
|||
case Role_IsEnabled: |
|||
{ |
|||
auto context = context_weak.lock(); |
|||
return context && context->breakpoints[event].enabled; |
|||
} |
|||
|
|||
default: |
|||
break; |
|||
} |
|||
return QVariant(); |
|||
} |
|||
|
|||
QVariant BreakPointModel::headerData(int section, Qt::Orientation orientation, int role) const |
|||
{ |
|||
switch(role) { |
|||
case Qt::DisplayRole: |
|||
{ |
|||
if (section == 0) { |
|||
return tr("Event"); |
|||
} else if (section == 1) { |
|||
return tr("Status"); |
|||
} |
|||
|
|||
break; |
|||
} |
|||
} |
|||
|
|||
return QVariant(); |
|||
} |
|||
|
|||
bool BreakPointModel::setData(const QModelIndex& index, const QVariant& value, int role) |
|||
{ |
|||
const auto event = static_cast<Pica::DebugContext::Event>(index.row()); |
|||
|
|||
switch (role) { |
|||
case Role_IsEnabled: |
|||
{ |
|||
auto context = context_weak.lock(); |
|||
if (!context) |
|||
return false; |
|||
|
|||
context->breakpoints[event].enabled = value.toBool(); |
|||
QModelIndex changed_index = createIndex(index.row(), 1); |
|||
emit dataChanged(changed_index, changed_index); |
|||
return true; |
|||
} |
|||
} |
|||
|
|||
return false; |
|||
} |
|||
|
|||
|
|||
void BreakPointModel::OnBreakPointHit(Pica::DebugContext::Event event) |
|||
{ |
|||
auto context = context_weak.lock(); |
|||
if (!context) |
|||
return; |
|||
|
|||
active_breakpoint = context->active_breakpoint; |
|||
at_breakpoint = context->at_breakpoint; |
|||
emit dataChanged(createIndex(static_cast<int>(event), 0), |
|||
createIndex(static_cast<int>(event), 1)); |
|||
} |
|||
|
|||
void BreakPointModel::OnResumed() |
|||
{ |
|||
auto context = context_weak.lock(); |
|||
if (!context) |
|||
return; |
|||
|
|||
at_breakpoint = context->at_breakpoint; |
|||
emit dataChanged(createIndex(static_cast<int>(active_breakpoint), 0), |
|||
createIndex(static_cast<int>(active_breakpoint), 1)); |
|||
active_breakpoint = context->active_breakpoint; |
|||
} |
|||
|
|||
|
|||
GraphicsBreakPointsWidget::GraphicsBreakPointsWidget(std::shared_ptr<Pica::DebugContext> debug_context, |
|||
QWidget* parent) |
|||
: QDockWidget(tr("Pica Breakpoints"), parent), |
|||
Pica::DebugContext::BreakPointObserver(debug_context) |
|||
{ |
|||
setObjectName("PicaBreakPointsWidget"); |
|||
|
|||
status_text = new QLabel(tr("Emulation running")); |
|||
resume_button = new QPushButton(tr("Resume")); |
|||
resume_button->setEnabled(false); |
|||
|
|||
breakpoint_model = new BreakPointModel(debug_context, this); |
|||
breakpoint_list = new QTreeView; |
|||
breakpoint_list->setModel(breakpoint_model); |
|||
|
|||
toggle_breakpoint_button = new QPushButton(tr("Enable")); |
|||
toggle_breakpoint_button->setEnabled(false); |
|||
|
|||
qRegisterMetaType<Pica::DebugContext::Event>("Pica::DebugContext::Event"); |
|||
|
|||
connect(resume_button, SIGNAL(clicked()), this, SLOT(OnResumeRequested())); |
|||
|
|||
connect(this, SIGNAL(BreakPointHit(Pica::DebugContext::Event,void*)), |
|||
this, SLOT(OnBreakPointHit(Pica::DebugContext::Event,void*)), |
|||
Qt::BlockingQueuedConnection); |
|||
connect(this, SIGNAL(Resumed()), this, SLOT(OnResumed())); |
|||
|
|||
connect(this, SIGNAL(BreakPointHit(Pica::DebugContext::Event,void*)), |
|||
breakpoint_model, SLOT(OnBreakPointHit(Pica::DebugContext::Event)), |
|||
Qt::BlockingQueuedConnection); |
|||
connect(this, SIGNAL(Resumed()), breakpoint_model, SLOT(OnResumed())); |
|||
|
|||
connect(this, SIGNAL(BreakPointsChanged(const QModelIndex&,const QModelIndex&)), |
|||
breakpoint_model, SIGNAL(dataChanged(const QModelIndex&,const QModelIndex&))); |
|||
|
|||
connect(breakpoint_list->selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)), |
|||
this, SLOT(OnBreakpointSelectionChanged(QModelIndex))); |
|||
|
|||
connect(toggle_breakpoint_button, SIGNAL(clicked()), this, SLOT(OnToggleBreakpointEnabled())); |
|||
|
|||
QWidget* main_widget = new QWidget; |
|||
auto main_layout = new QVBoxLayout; |
|||
{ |
|||
auto sub_layout = new QHBoxLayout; |
|||
sub_layout->addWidget(status_text); |
|||
sub_layout->addWidget(resume_button); |
|||
main_layout->addLayout(sub_layout); |
|||
} |
|||
main_layout->addWidget(breakpoint_list); |
|||
main_layout->addWidget(toggle_breakpoint_button); |
|||
main_widget->setLayout(main_layout); |
|||
|
|||
setWidget(main_widget); |
|||
} |
|||
|
|||
void GraphicsBreakPointsWidget::OnPicaBreakPointHit(Event event, void* data) |
|||
{ |
|||
// Process in GUI thread
|
|||
emit BreakPointHit(event, data); |
|||
} |
|||
|
|||
void GraphicsBreakPointsWidget::OnBreakPointHit(Pica::DebugContext::Event event, void* data) |
|||
{ |
|||
status_text->setText(tr("Emulation halted at breakpoint")); |
|||
resume_button->setEnabled(true); |
|||
} |
|||
|
|||
void GraphicsBreakPointsWidget::OnPicaResume() |
|||
{ |
|||
// Process in GUI thread
|
|||
emit Resumed(); |
|||
} |
|||
|
|||
void GraphicsBreakPointsWidget::OnResumed() |
|||
{ |
|||
status_text->setText(tr("Emulation running")); |
|||
resume_button->setEnabled(false); |
|||
} |
|||
|
|||
void GraphicsBreakPointsWidget::OnResumeRequested() |
|||
{ |
|||
if (auto context = context_weak.lock()) |
|||
context->Resume(); |
|||
} |
|||
|
|||
void GraphicsBreakPointsWidget::OnBreakpointSelectionChanged(const QModelIndex& index) |
|||
{ |
|||
if (!index.isValid()) { |
|||
toggle_breakpoint_button->setEnabled(false); |
|||
return; |
|||
} |
|||
|
|||
toggle_breakpoint_button->setEnabled(true); |
|||
UpdateToggleBreakpointButton(index); |
|||
} |
|||
|
|||
void GraphicsBreakPointsWidget::OnToggleBreakpointEnabled() |
|||
{ |
|||
QModelIndex index = breakpoint_list->selectionModel()->currentIndex(); |
|||
bool new_state = !(breakpoint_model->data(index, BreakPointModel::Role_IsEnabled).toBool()); |
|||
|
|||
breakpoint_model->setData(index, new_state, |
|||
BreakPointModel::Role_IsEnabled); |
|||
UpdateToggleBreakpointButton(index); |
|||
} |
|||
|
|||
void GraphicsBreakPointsWidget::UpdateToggleBreakpointButton(const QModelIndex& index) |
|||
{ |
|||
if (true == breakpoint_model->data(index, BreakPointModel::Role_IsEnabled).toBool()) { |
|||
toggle_breakpoint_button->setText(tr("Disable")); |
|||
} else { |
|||
toggle_breakpoint_button->setText(tr("Enable")); |
|||
} |
|||
} |
|||
@ -0,0 +1,53 @@ |
|||
// Copyright 2014 Citra Emulator Project
|
|||
// Licensed under GPLv2 or any later version
|
|||
// Refer to the license.txt file included.
|
|||
|
|||
#pragma once
|
|||
|
|||
#include <memory>
|
|||
|
|||
#include <QAbstractListModel>
|
|||
#include <QDockWidget>
|
|||
|
|||
#include "video_core/debug_utils/debug_utils.h"
|
|||
|
|||
class QLabel; |
|||
class QPushButton; |
|||
class QTreeView; |
|||
|
|||
class BreakPointModel; |
|||
|
|||
class GraphicsBreakPointsWidget : public QDockWidget, Pica::DebugContext::BreakPointObserver { |
|||
Q_OBJECT |
|||
|
|||
using Event = Pica::DebugContext::Event; |
|||
|
|||
public: |
|||
GraphicsBreakPointsWidget(std::shared_ptr<Pica::DebugContext> debug_context, |
|||
QWidget* parent = nullptr); |
|||
|
|||
void OnPicaBreakPointHit(Pica::DebugContext::Event event, void* data) override; |
|||
void OnPicaResume() override; |
|||
|
|||
public slots: |
|||
void OnBreakPointHit(Pica::DebugContext::Event event, void* data); |
|||
void OnResumeRequested(); |
|||
void OnResumed(); |
|||
void OnBreakpointSelectionChanged(const QModelIndex&); |
|||
void OnToggleBreakpointEnabled(); |
|||
|
|||
signals: |
|||
void Resumed(); |
|||
void BreakPointHit(Pica::DebugContext::Event event, void* data); |
|||
void BreakPointsChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight); |
|||
|
|||
private: |
|||
void UpdateToggleBreakpointButton(const QModelIndex& index); |
|||
|
|||
QLabel* status_text; |
|||
QPushButton* resume_button; |
|||
QPushButton* toggle_breakpoint_button; |
|||
|
|||
BreakPointModel* breakpoint_model; |
|||
QTreeView* breakpoint_list; |
|||
}; |
|||
@ -0,0 +1,38 @@ |
|||
// Copyright 2014 Citra Emulator Project
|
|||
// Licensed under GPLv2 or any later version
|
|||
// Refer to the license.txt file included.
|
|||
|
|||
#pragma once
|
|||
|
|||
#include <memory>
|
|||
|
|||
#include <QAbstractListModel>
|
|||
|
|||
#include "video_core/debug_utils/debug_utils.h"
|
|||
|
|||
class BreakPointModel : public QAbstractListModel { |
|||
Q_OBJECT |
|||
|
|||
public: |
|||
enum { |
|||
Role_IsEnabled = Qt::UserRole, |
|||
}; |
|||
|
|||
BreakPointModel(std::shared_ptr<Pica::DebugContext> context, QObject* parent); |
|||
|
|||
int columnCount(const QModelIndex& parent = QModelIndex()) const override; |
|||
int rowCount(const QModelIndex& parent = QModelIndex()) const override; |
|||
QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; |
|||
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; |
|||
|
|||
bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole); |
|||
|
|||
public slots: |
|||
void OnBreakPointHit(Pica::DebugContext::Event event); |
|||
void OnResumed(); |
|||
|
|||
private: |
|||
std::weak_ptr<Pica::DebugContext> context_weak; |
|||
bool at_breakpoint; |
|||
Pica::DebugContext::Event active_breakpoint; |
|||
}; |
|||
@ -0,0 +1,283 @@ |
|||
// Copyright 2014 Citra Emulator Project
|
|||
// Licensed under GPLv2 or any later version
|
|||
// Refer to the license.txt file included.
|
|||
|
|||
#include <QBoxLayout>
|
|||
#include <QComboBox>
|
|||
#include <QDebug>
|
|||
#include <QLabel>
|
|||
#include <QMetaType>
|
|||
#include <QPushButton>
|
|||
#include <QSpinBox>
|
|||
|
|||
#include "video_core/pica.h"
|
|||
|
|||
#include "graphics_framebuffer.hxx"
|
|||
|
|||
#include "util/spinbox.hxx"
|
|||
|
|||
BreakPointObserverDock::BreakPointObserverDock(std::shared_ptr<Pica::DebugContext> debug_context, |
|||
const QString& title, QWidget* parent) |
|||
: QDockWidget(title, parent), BreakPointObserver(debug_context) |
|||
{ |
|||
qRegisterMetaType<Pica::DebugContext::Event>("Pica::DebugContext::Event"); |
|||
|
|||
connect(this, SIGNAL(Resumed()), this, SLOT(OnResumed())); |
|||
|
|||
// NOTE: This signal is emitted from a non-GUI thread, but connect() takes
|
|||
// care of delaying its handling to the GUI thread.
|
|||
connect(this, SIGNAL(BreakPointHit(Pica::DebugContext::Event,void*)), |
|||
this, SLOT(OnBreakPointHit(Pica::DebugContext::Event,void*)), |
|||
Qt::BlockingQueuedConnection); |
|||
} |
|||
|
|||
void BreakPointObserverDock::OnPicaBreakPointHit(Pica::DebugContext::Event event, void* data) |
|||
{ |
|||
emit BreakPointHit(event, data); |
|||
} |
|||
|
|||
void BreakPointObserverDock::OnPicaResume() |
|||
{ |
|||
emit Resumed(); |
|||
} |
|||
|
|||
|
|||
GraphicsFramebufferWidget::GraphicsFramebufferWidget(std::shared_ptr<Pica::DebugContext> debug_context, |
|||
QWidget* parent) |
|||
: BreakPointObserverDock(debug_context, tr("Pica Framebuffer"), parent), |
|||
framebuffer_source(Source::PicaTarget) |
|||
{ |
|||
setObjectName("PicaFramebuffer"); |
|||
|
|||
framebuffer_source_list = new QComboBox; |
|||
framebuffer_source_list->addItem(tr("Active Render Target")); |
|||
framebuffer_source_list->addItem(tr("Custom")); |
|||
framebuffer_source_list->setCurrentIndex(static_cast<int>(framebuffer_source)); |
|||
|
|||
framebuffer_address_control = new CSpinBox; |
|||
framebuffer_address_control->SetBase(16); |
|||
framebuffer_address_control->SetRange(0, 0xFFFFFFFF); |
|||
framebuffer_address_control->SetPrefix("0x"); |
|||
|
|||
framebuffer_width_control = new QSpinBox; |
|||
framebuffer_width_control->setMinimum(1); |
|||
framebuffer_width_control->setMaximum(std::numeric_limits<int>::max()); // TODO: Find actual maximum
|
|||
|
|||
framebuffer_height_control = new QSpinBox; |
|||
framebuffer_height_control->setMinimum(1); |
|||
framebuffer_height_control->setMaximum(std::numeric_limits<int>::max()); // TODO: Find actual maximum
|
|||
|
|||
framebuffer_format_control = new QComboBox; |
|||
framebuffer_format_control->addItem(tr("RGBA8")); |
|||
framebuffer_format_control->addItem(tr("RGB8")); |
|||
framebuffer_format_control->addItem(tr("RGBA5551")); |
|||
framebuffer_format_control->addItem(tr("RGB565")); |
|||
framebuffer_format_control->addItem(tr("RGBA4")); |
|||
|
|||
// TODO: This QLabel should shrink the image to the available space rather than just expanding...
|
|||
framebuffer_picture_label = new QLabel; |
|||
|
|||
auto enlarge_button = new QPushButton(tr("Enlarge")); |
|||
|
|||
// Connections
|
|||
connect(this, SIGNAL(Update()), this, SLOT(OnUpdate())); |
|||
connect(framebuffer_source_list, SIGNAL(currentIndexChanged(int)), this, SLOT(OnFramebufferSourceChanged(int))); |
|||
connect(framebuffer_address_control, SIGNAL(ValueChanged(qint64)), this, SLOT(OnFramebufferAddressChanged(qint64))); |
|||
connect(framebuffer_width_control, SIGNAL(valueChanged(int)), this, SLOT(OnFramebufferWidthChanged(int))); |
|||
connect(framebuffer_height_control, SIGNAL(valueChanged(int)), this, SLOT(OnFramebufferHeightChanged(int))); |
|||
connect(framebuffer_format_control, SIGNAL(currentIndexChanged(int)), this, SLOT(OnFramebufferFormatChanged(int))); |
|||
|
|||
auto main_widget = new QWidget; |
|||
auto main_layout = new QVBoxLayout; |
|||
{ |
|||
auto sub_layout = new QHBoxLayout; |
|||
sub_layout->addWidget(new QLabel(tr("Source:"))); |
|||
sub_layout->addWidget(framebuffer_source_list); |
|||
main_layout->addLayout(sub_layout); |
|||
} |
|||
{ |
|||
auto sub_layout = new QHBoxLayout; |
|||
sub_layout->addWidget(new QLabel(tr("Virtual Address:"))); |
|||
sub_layout->addWidget(framebuffer_address_control); |
|||
main_layout->addLayout(sub_layout); |
|||
} |
|||
{ |
|||
auto sub_layout = new QHBoxLayout; |
|||
sub_layout->addWidget(new QLabel(tr("Width:"))); |
|||
sub_layout->addWidget(framebuffer_width_control); |
|||
main_layout->addLayout(sub_layout); |
|||
} |
|||
{ |
|||
auto sub_layout = new QHBoxLayout; |
|||
sub_layout->addWidget(new QLabel(tr("Height:"))); |
|||
sub_layout->addWidget(framebuffer_height_control); |
|||
main_layout->addLayout(sub_layout); |
|||
} |
|||
{ |
|||
auto sub_layout = new QHBoxLayout; |
|||
sub_layout->addWidget(new QLabel(tr("Format:"))); |
|||
sub_layout->addWidget(framebuffer_format_control); |
|||
main_layout->addLayout(sub_layout); |
|||
} |
|||
main_layout->addWidget(framebuffer_picture_label); |
|||
main_layout->addWidget(enlarge_button); |
|||
main_widget->setLayout(main_layout); |
|||
setWidget(main_widget); |
|||
|
|||
// Load current data - TODO: Make sure this works when emulation is not running
|
|||
if (debug_context && debug_context->at_breakpoint) |
|||
emit Update(); |
|||
widget()->setEnabled(false); // TODO: Only enable if currently at breakpoint
|
|||
} |
|||
|
|||
void GraphicsFramebufferWidget::OnBreakPointHit(Pica::DebugContext::Event event, void* data) |
|||
{ |
|||
emit Update(); |
|||
widget()->setEnabled(true); |
|||
} |
|||
|
|||
void GraphicsFramebufferWidget::OnResumed() |
|||
{ |
|||
widget()->setEnabled(false); |
|||
} |
|||
|
|||
void GraphicsFramebufferWidget::OnFramebufferSourceChanged(int new_value) |
|||
{ |
|||
framebuffer_source = static_cast<Source>(new_value); |
|||
emit Update(); |
|||
} |
|||
|
|||
void GraphicsFramebufferWidget::OnFramebufferAddressChanged(qint64 new_value) |
|||
{ |
|||
if (framebuffer_address != new_value) { |
|||
framebuffer_address = static_cast<unsigned>(new_value); |
|||
|
|||
framebuffer_source_list->setCurrentIndex(static_cast<int>(Source::Custom)); |
|||
emit Update(); |
|||
} |
|||
} |
|||
|
|||
void GraphicsFramebufferWidget::OnFramebufferWidthChanged(int new_value) |
|||
{ |
|||
if (framebuffer_width != new_value) { |
|||
framebuffer_width = new_value; |
|||
|
|||
framebuffer_source_list->setCurrentIndex(static_cast<int>(Source::Custom)); |
|||
emit Update(); |
|||
} |
|||
} |
|||
|
|||
void GraphicsFramebufferWidget::OnFramebufferHeightChanged(int new_value) |
|||
{ |
|||
if (framebuffer_height != new_value) { |
|||
framebuffer_height = new_value; |
|||
|
|||
framebuffer_source_list->setCurrentIndex(static_cast<int>(Source::Custom)); |
|||
emit Update(); |
|||
} |
|||
} |
|||
|
|||
void GraphicsFramebufferWidget::OnFramebufferFormatChanged(int new_value) |
|||
{ |
|||
if (framebuffer_format != static_cast<Format>(new_value)) { |
|||
framebuffer_format = static_cast<Format>(new_value); |
|||
|
|||
framebuffer_source_list->setCurrentIndex(static_cast<int>(Source::Custom)); |
|||
emit Update(); |
|||
} |
|||
} |
|||
|
|||
void GraphicsFramebufferWidget::OnUpdate() |
|||
{ |
|||
QPixmap pixmap; |
|||
|
|||
switch (framebuffer_source) { |
|||
case Source::PicaTarget: |
|||
{ |
|||
// TODO: Store a reference to the registers in the debug context instead of accessing them directly...
|
|||
|
|||
auto framebuffer = Pica::registers.framebuffer; |
|||
using Framebuffer = decltype(framebuffer); |
|||
|
|||
framebuffer_address = framebuffer.GetColorBufferPhysicalAddress(); |
|||
framebuffer_width = framebuffer.GetWidth(); |
|||
framebuffer_height = framebuffer.GetHeight(); |
|||
framebuffer_format = static_cast<Format>(framebuffer.color_format); |
|||
|
|||
break; |
|||
} |
|||
|
|||
case Source::Custom: |
|||
{ |
|||
// Keep user-specified values
|
|||
break; |
|||
} |
|||
|
|||
default: |
|||
qDebug() << "Unknown framebuffer source " << static_cast<int>(framebuffer_source); |
|||
break; |
|||
} |
|||
|
|||
// TODO: Implement a good way to visualize alpha components!
|
|||
// TODO: Unify this decoding code with the texture decoder
|
|||
switch (framebuffer_format) { |
|||
case Format::RGBA8: |
|||
{ |
|||
QImage decoded_image(framebuffer_width, framebuffer_height, QImage::Format_ARGB32); |
|||
u32* color_buffer = (u32*)Memory::GetPointer(Pica::PAddrToVAddr(framebuffer_address)); |
|||
for (unsigned y = 0; y < framebuffer_height; ++y) { |
|||
for (unsigned x = 0; x < framebuffer_width; ++x) { |
|||
u32 value = *(color_buffer + x + y * framebuffer_width); |
|||
|
|||
decoded_image.setPixel(x, y, qRgba((value >> 16) & 0xFF, (value >> 8) & 0xFF, value & 0xFF, 255/*value >> 24*/)); |
|||
} |
|||
} |
|||
pixmap = QPixmap::fromImage(decoded_image); |
|||
break; |
|||
} |
|||
|
|||
case Format::RGB8: |
|||
{ |
|||
QImage decoded_image(framebuffer_width, framebuffer_height, QImage::Format_ARGB32); |
|||
u8* color_buffer = Memory::GetPointer(Pica::PAddrToVAddr(framebuffer_address)); |
|||
for (unsigned y = 0; y < framebuffer_height; ++y) { |
|||
for (unsigned x = 0; x < framebuffer_width; ++x) { |
|||
u8* pixel_pointer = color_buffer + x * 3 + y * 3 * framebuffer_width; |
|||
|
|||
decoded_image.setPixel(x, y, qRgba(pixel_pointer[0], pixel_pointer[1], pixel_pointer[2], 255/*value >> 24*/)); |
|||
} |
|||
} |
|||
pixmap = QPixmap::fromImage(decoded_image); |
|||
break; |
|||
} |
|||
|
|||
case Format::RGBA5551: |
|||
{ |
|||
QImage decoded_image(framebuffer_width, framebuffer_height, QImage::Format_ARGB32); |
|||
u32* color_buffer = (u32*)Memory::GetPointer(Pica::PAddrToVAddr(framebuffer_address)); |
|||
for (unsigned y = 0; y < framebuffer_height; ++y) { |
|||
for (unsigned x = 0; x < framebuffer_width; ++x) { |
|||
u16 value = *(u16*)(((u8*)color_buffer) + x * 2 + y * framebuffer_width * 2); |
|||
u8 r = (value >> 11) & 0x1F; |
|||
u8 g = (value >> 6) & 0x1F; |
|||
u8 b = (value >> 1) & 0x1F; |
|||
u8 a = value & 1; |
|||
|
|||
decoded_image.setPixel(x, y, qRgba(r, g, b, 255/*a*/)); |
|||
} |
|||
} |
|||
pixmap = QPixmap::fromImage(decoded_image); |
|||
break; |
|||
} |
|||
|
|||
default: |
|||
qDebug() << "Unknown fb color format " << static_cast<int>(framebuffer_format); |
|||
break; |
|||
} |
|||
|
|||
framebuffer_address_control->SetValue(framebuffer_address); |
|||
framebuffer_width_control->setValue(framebuffer_width); |
|||
framebuffer_height_control->setValue(framebuffer_height); |
|||
framebuffer_format_control->setCurrentIndex(static_cast<int>(framebuffer_format)); |
|||
framebuffer_picture_label->setPixmap(pixmap); |
|||
} |
|||
@ -0,0 +1,92 @@ |
|||
// Copyright 2014 Citra Emulator Project
|
|||
// Licensed under GPLv2 or any later version
|
|||
// Refer to the license.txt file included.
|
|||
|
|||
#pragma once
|
|||
|
|||
#include <QDockWidget>
|
|||
|
|||
#include "video_core/debug_utils/debug_utils.h"
|
|||
|
|||
class QComboBox; |
|||
class QLabel; |
|||
class QSpinBox; |
|||
|
|||
class CSpinBox; |
|||
|
|||
// Utility class which forwards calls to OnPicaBreakPointHit and OnPicaResume to public slots.
|
|||
// This is because the Pica breakpoint callbacks are called from a non-GUI thread, while
|
|||
// the widget usually wants to perform reactions in the GUI thread.
|
|||
class BreakPointObserverDock : public QDockWidget, Pica::DebugContext::BreakPointObserver { |
|||
Q_OBJECT |
|||
|
|||
public: |
|||
BreakPointObserverDock(std::shared_ptr<Pica::DebugContext> debug_context, const QString& title, |
|||
QWidget* parent = nullptr); |
|||
|
|||
void OnPicaBreakPointHit(Pica::DebugContext::Event event, void* data) override; |
|||
void OnPicaResume() override; |
|||
|
|||
private slots: |
|||
virtual void OnBreakPointHit(Pica::DebugContext::Event event, void* data) = 0; |
|||
virtual void OnResumed() = 0; |
|||
|
|||
signals: |
|||
void Resumed(); |
|||
void BreakPointHit(Pica::DebugContext::Event event, void* data); |
|||
}; |
|||
|
|||
class GraphicsFramebufferWidget : public BreakPointObserverDock { |
|||
Q_OBJECT |
|||
|
|||
using Event = Pica::DebugContext::Event; |
|||
|
|||
enum class Source { |
|||
PicaTarget = 0, |
|||
Custom = 1, |
|||
|
|||
// TODO: Add GPU framebuffer sources!
|
|||
}; |
|||
|
|||
enum class Format { |
|||
RGBA8 = 0, |
|||
RGB8 = 1, |
|||
RGBA5551 = 2, |
|||
RGB565 = 3, |
|||
RGBA4 = 4, |
|||
}; |
|||
|
|||
public: |
|||
GraphicsFramebufferWidget(std::shared_ptr<Pica::DebugContext> debug_context, QWidget* parent = nullptr); |
|||
|
|||
public slots: |
|||
void OnFramebufferSourceChanged(int new_value); |
|||
void OnFramebufferAddressChanged(qint64 new_value); |
|||
void OnFramebufferWidthChanged(int new_value); |
|||
void OnFramebufferHeightChanged(int new_value); |
|||
void OnFramebufferFormatChanged(int new_value); |
|||
void OnUpdate(); |
|||
|
|||
private slots: |
|||
void OnBreakPointHit(Pica::DebugContext::Event event, void* data) override; |
|||
void OnResumed() override; |
|||
|
|||
signals: |
|||
void Update(); |
|||
|
|||
private: |
|||
|
|||
QComboBox* framebuffer_source_list; |
|||
CSpinBox* framebuffer_address_control; |
|||
QSpinBox* framebuffer_width_control; |
|||
QSpinBox* framebuffer_height_control; |
|||
QComboBox* framebuffer_format_control; |
|||
|
|||
QLabel* framebuffer_picture_label; |
|||
|
|||
Source framebuffer_source; |
|||
unsigned framebuffer_address; |
|||
unsigned framebuffer_width; |
|||
unsigned framebuffer_height; |
|||
Format framebuffer_format; |
|||
}; |
|||
@ -0,0 +1,303 @@ |
|||
// Licensed under GPLv2 or any later version
|
|||
// Refer to the license.txt file included.
|
|||
|
|||
|
|||
// Copyright 2014 Tony Wasserka
|
|||
// All rights reserved.
|
|||
//
|
|||
// Redistribution and use in source and binary forms, with or without
|
|||
// modification, are permitted provided that the following conditions are met:
|
|||
//
|
|||
// * Redistributions of source code must retain the above copyright
|
|||
// notice, this list of conditions and the following disclaimer.
|
|||
// * Redistributions in binary form must reproduce the above copyright
|
|||
// notice, this list of conditions and the following disclaimer in the
|
|||
// documentation and/or other materials provided with the distribution.
|
|||
// * Neither the name of the owner nor the names of its contributors may
|
|||
// be used to endorse or promote products derived from this software
|
|||
// without specific prior written permission.
|
|||
//
|
|||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
|
|||
#include <QLineEdit>
|
|||
#include <QRegExpValidator>
|
|||
|
|||
#include "common/log.h"
|
|||
|
|||
#include "spinbox.hxx"
|
|||
|
|||
CSpinBox::CSpinBox(QWidget* parent) : QAbstractSpinBox(parent), base(10), min_value(-100), max_value(100), value(0), num_digits(0) |
|||
{ |
|||
// TODO: Might be nice to not immediately call the slot.
|
|||
// Think of an address that is being replaced by a different one, in which case a lot
|
|||
// invalid intermediate addresses would be read from during editing.
|
|||
connect(lineEdit(), SIGNAL(textEdited(QString)), this, SLOT(OnEditingFinished())); |
|||
|
|||
UpdateText(); |
|||
} |
|||
|
|||
void CSpinBox::SetValue(qint64 val) |
|||
{ |
|||
auto old_value = value; |
|||
value = std::max(std::min(val, max_value), min_value); |
|||
|
|||
if (old_value != value) { |
|||
UpdateText(); |
|||
emit ValueChanged(value); |
|||
} |
|||
} |
|||
|
|||
void CSpinBox::SetRange(qint64 min, qint64 max) |
|||
{ |
|||
min_value = min; |
|||
max_value = max; |
|||
|
|||
SetValue(value); |
|||
UpdateText(); |
|||
} |
|||
|
|||
void CSpinBox::stepBy(int steps) |
|||
{ |
|||
auto new_value = value; |
|||
// Scale number of steps by the currently selected digit
|
|||
// TODO: Move this code elsewhere and enable it.
|
|||
// TODO: Support for num_digits==0, too
|
|||
// TODO: Support base!=16, too
|
|||
// TODO: Make the cursor not jump back to the end of the line...
|
|||
/*if (base == 16 && num_digits > 0) {
|
|||
int digit = num_digits - (lineEdit()->cursorPosition() - prefix.length()) - 1; |
|||
digit = std::max(0, std::min(digit, num_digits - 1)); |
|||
steps <<= digit * 4; |
|||
}*/ |
|||
|
|||
// Increment "new_value" by "steps", and perform annoying overflow checks, too.
|
|||
if (steps < 0 && new_value + steps > new_value) { |
|||
new_value = std::numeric_limits<qint64>::min(); |
|||
} else if (steps > 0 && new_value + steps < new_value) { |
|||
new_value = std::numeric_limits<qint64>::max(); |
|||
} else { |
|||
new_value += steps; |
|||
} |
|||
|
|||
SetValue(new_value); |
|||
UpdateText(); |
|||
} |
|||
|
|||
QAbstractSpinBox::StepEnabled CSpinBox::stepEnabled() const |
|||
{ |
|||
StepEnabled ret = StepNone; |
|||
|
|||
if (value > min_value) |
|||
ret |= StepDownEnabled; |
|||
|
|||
if (value < max_value) |
|||
ret |= StepUpEnabled; |
|||
|
|||
return ret; |
|||
} |
|||
|
|||
void CSpinBox::SetBase(int base) |
|||
{ |
|||
this->base = base; |
|||
|
|||
UpdateText(); |
|||
} |
|||
|
|||
void CSpinBox::SetNumDigits(int num_digits) |
|||
{ |
|||
this->num_digits = num_digits; |
|||
|
|||
UpdateText(); |
|||
} |
|||
|
|||
void CSpinBox::SetPrefix(const QString& prefix) |
|||
{ |
|||
this->prefix = prefix; |
|||
|
|||
UpdateText(); |
|||
} |
|||
|
|||
void CSpinBox::SetSuffix(const QString& suffix) |
|||
{ |
|||
this->suffix = suffix; |
|||
|
|||
UpdateText(); |
|||
} |
|||
|
|||
static QString StringToInputMask(const QString& input) { |
|||
QString mask = input; |
|||
|
|||
// ... replace any special characters by their escaped counterparts ...
|
|||
mask.replace("\\", "\\\\"); |
|||
mask.replace("A", "\\A"); |
|||
mask.replace("a", "\\a"); |
|||
mask.replace("N", "\\N"); |
|||
mask.replace("n", "\\n"); |
|||
mask.replace("X", "\\X"); |
|||
mask.replace("x", "\\x"); |
|||
mask.replace("9", "\\9"); |
|||
mask.replace("0", "\\0"); |
|||
mask.replace("D", "\\D"); |
|||
mask.replace("d", "\\d"); |
|||
mask.replace("#", "\\#"); |
|||
mask.replace("H", "\\H"); |
|||
mask.replace("h", "\\h"); |
|||
mask.replace("B", "\\B"); |
|||
mask.replace("b", "\\b"); |
|||
mask.replace(">", "\\>"); |
|||
mask.replace("<", "\\<"); |
|||
mask.replace("!", "\\!"); |
|||
|
|||
return mask; |
|||
} |
|||
|
|||
void CSpinBox::UpdateText() |
|||
{ |
|||
// If a fixed number of digits is used, we put the line edit in insertion mode by setting an
|
|||
// input mask.
|
|||
QString mask; |
|||
if (num_digits != 0) { |
|||
mask += StringToInputMask(prefix); |
|||
|
|||
// For base 10 and negative range, demand a single sign character
|
|||
if (HasSign()) |
|||
mask += "X"; // identified as "-" or "+" in the validator
|
|||
|
|||
// Uppercase digits greater than 9.
|
|||
mask += ">"; |
|||
|
|||
// The greatest signed 64-bit number has 19 decimal digits.
|
|||
// TODO: Could probably make this more generic with some logarithms.
|
|||
// For reference, unsigned 64-bit can have up to 20 decimal digits.
|
|||
int digits = (num_digits != 0) ? num_digits |
|||
: (base == 16) ? 16 |
|||
: (base == 10) ? 19 |
|||
: 0xFF; // fallback case...
|
|||
|
|||
// Match num_digits digits
|
|||
// Digits irrelevant to the chosen number base are filtered in the validator
|
|||
mask += QString("H").repeated(std::max(num_digits, 1)); |
|||
|
|||
// Switch off case conversion
|
|||
mask += "!"; |
|||
|
|||
mask += StringToInputMask(suffix); |
|||
} |
|||
lineEdit()->setInputMask(mask); |
|||
|
|||
// Set new text without changing the cursor position. This will cause the cursor to briefly
|
|||
// appear at the end of the line and then to jump back to its original position. That's
|
|||
// a bit ugly, but better than having setText() move the cursor permanently all the time.
|
|||
int cursor_position = lineEdit()->cursorPosition(); |
|||
lineEdit()->setText(TextFromValue()); |
|||
lineEdit()->setCursorPosition(cursor_position); |
|||
} |
|||
|
|||
QString CSpinBox::TextFromValue() |
|||
{ |
|||
return prefix |
|||
+ QString(HasSign() ? ((value < 0) ? "-" : "+") : "") |
|||
+ QString("%1").arg(abs(value), num_digits, base, QLatin1Char('0')).toUpper() |
|||
+ suffix; |
|||
} |
|||
|
|||
qint64 CSpinBox::ValueFromText() |
|||
{ |
|||
unsigned strpos = prefix.length(); |
|||
|
|||
QString num_string = text().mid(strpos, text().length() - strpos - suffix.length()); |
|||
return num_string.toLongLong(nullptr, base); |
|||
} |
|||
|
|||
bool CSpinBox::HasSign() const |
|||
{ |
|||
return base == 10 && min_value < 0; |
|||
} |
|||
|
|||
void CSpinBox::OnEditingFinished() |
|||
{ |
|||
// Only update for valid input
|
|||
QString input = lineEdit()->text(); |
|||
int pos = 0; |
|||
if (QValidator::Acceptable == validate(input, pos)) |
|||
SetValue(ValueFromText()); |
|||
} |
|||
|
|||
QValidator::State CSpinBox::validate(QString& input, int& pos) const |
|||
{ |
|||
if (!prefix.isEmpty() && input.left(prefix.length()) != prefix) |
|||
return QValidator::Invalid; |
|||
|
|||
int strpos = prefix.length(); |
|||
|
|||
// Empty "numbers" allowed as intermediate values
|
|||
if (strpos >= input.length() - HasSign() - suffix.length()) |
|||
return QValidator::Intermediate; |
|||
|
|||
_dbg_assert_(Frontend, base <= 10 || base == 16); |
|||
QString regexp; |
|||
|
|||
// Demand sign character for negative ranges
|
|||
if (HasSign()) |
|||
regexp += "[+\\-]"; |
|||
|
|||
// Match digits corresponding to the chosen number base.
|
|||
regexp += QString("[0-%1").arg(std::min(base, 9)); |
|||
if (base == 16) { |
|||
regexp += "a-fA-F"; |
|||
} |
|||
regexp += "]"; |
|||
|
|||
// Specify number of digits
|
|||
if (num_digits > 0) { |
|||
regexp += QString("{%1}").arg(num_digits); |
|||
} else { |
|||
regexp += "+"; |
|||
} |
|||
|
|||
// Match string
|
|||
QRegExp num_regexp(regexp); |
|||
int num_pos = strpos; |
|||
QString sub_input = input.mid(strpos, input.length() - strpos - suffix.length()); |
|||
|
|||
if (!num_regexp.exactMatch(sub_input) && num_regexp.matchedLength() == 0) |
|||
return QValidator::Invalid; |
|||
|
|||
sub_input = sub_input.left(num_regexp.matchedLength()); |
|||
bool ok; |
|||
qint64 val = sub_input.toLongLong(&ok, base); |
|||
|
|||
if (!ok) |
|||
return QValidator::Invalid; |
|||
|
|||
// Outside boundaries => don't accept
|
|||
if (val < min_value || val > max_value) |
|||
return QValidator::Invalid; |
|||
|
|||
// Make sure we are actually at the end of this string...
|
|||
strpos += num_regexp.matchedLength(); |
|||
|
|||
if (!suffix.isEmpty() && input.mid(strpos) != suffix) { |
|||
return QValidator::Invalid; |
|||
} else { |
|||
strpos += suffix.length(); |
|||
} |
|||
|
|||
if (strpos != input.length()) |
|||
return QValidator::Invalid; |
|||
|
|||
// At this point we can say for sure that the input is fine. Let's fix it up a bit though
|
|||
input.replace(num_pos, sub_input.length(), sub_input.toUpper()); |
|||
|
|||
return QValidator::Acceptable; |
|||
} |
|||
@ -0,0 +1,88 @@ |
|||
// Licensed under GPLv2 or any later version
|
|||
// Refer to the license.txt file included.
|
|||
|
|||
|
|||
// Copyright 2014 Tony Wasserka
|
|||
// All rights reserved.
|
|||
//
|
|||
// Redistribution and use in source and binary forms, with or without
|
|||
// modification, are permitted provided that the following conditions are met:
|
|||
//
|
|||
// * Redistributions of source code must retain the above copyright
|
|||
// notice, this list of conditions and the following disclaimer.
|
|||
// * Redistributions in binary form must reproduce the above copyright
|
|||
// notice, this list of conditions and the following disclaimer in the
|
|||
// documentation and/or other materials provided with the distribution.
|
|||
// * Neither the name of the owner nor the names of its contributors may
|
|||
// be used to endorse or promote products derived from this software
|
|||
// without specific prior written permission.
|
|||
//
|
|||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
|
|||
|
|||
#pragma once
|
|||
|
|||
#include <QAbstractSpinBox>
|
|||
#include <QtGlobal>
|
|||
|
|||
class QVariant; |
|||
|
|||
/**
|
|||
* A custom spin box widget with enhanced functionality over Qt's QSpinBox |
|||
*/ |
|||
class CSpinBox : public QAbstractSpinBox { |
|||
Q_OBJECT |
|||
|
|||
public: |
|||
CSpinBox(QWidget* parent = nullptr); |
|||
|
|||
void stepBy(int steps) override; |
|||
StepEnabled stepEnabled() const override; |
|||
|
|||
void SetValue(qint64 val); |
|||
|
|||
void SetRange(qint64 min, qint64 max); |
|||
|
|||
void SetBase(int base); |
|||
|
|||
void SetPrefix(const QString& prefix); |
|||
void SetSuffix(const QString& suffix); |
|||
|
|||
void SetNumDigits(int num_digits); |
|||
|
|||
QValidator::State validate(QString& input, int& pos) const override; |
|||
|
|||
signals: |
|||
void ValueChanged(qint64 val); |
|||
|
|||
private slots: |
|||
void OnEditingFinished(); |
|||
|
|||
private: |
|||
void UpdateText(); |
|||
|
|||
bool HasSign() const; |
|||
|
|||
QString TextFromValue(); |
|||
qint64 ValueFromText(); |
|||
|
|||
qint64 min_value, max_value; |
|||
|
|||
qint64 value; |
|||
|
|||
QString prefix, suffix; |
|||
|
|||
int base; |
|||
|
|||
int num_digits; |
|||
}; |
|||
@ -0,0 +1,164 @@ |
|||
// Copyright 2014 Citra Emulator Project |
|||
// Licensed under GPLv2 or any later version |
|||
// Refer to the license.txt file included. |
|||
|
|||
#pragma once |
|||
|
|||
#include <array> |
|||
#include <condition_variable> |
|||
#include <cstdint> |
|||
#include <mutex> |
|||
#include <thread> |
|||
|
|||
#include "common/common.h" // for NonCopyable |
|||
#include "common/log.h" // for _dbg_assert_ |
|||
|
|||
namespace Common { |
|||
|
|||
/** |
|||
* A MPMC (Multiple-Producer Multiple-Consumer) concurrent ring buffer. This data structure permits |
|||
* multiple threads to push and pop from a queue of bounded size. |
|||
*/ |
|||
template <typename T, size_t ArraySize> |
|||
class ConcurrentRingBuffer : private NonCopyable { |
|||
public: |
|||
/// Value returned by the popping functions when the queue has been closed. |
|||
static const size_t QUEUE_CLOSED = -1; |
|||
|
|||
ConcurrentRingBuffer() {} |
|||
|
|||
~ConcurrentRingBuffer() { |
|||
// If for whatever reason the queue wasn't completely drained, destroy the left over items. |
|||
for (size_t i = reader_index, end = writer_index; i != end; i = (i + 1) % ArraySize) { |
|||
Data()[i].~T(); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Pushes a value to the queue. If the queue is full, this method will block. Does nothing if |
|||
* the queue is closed. |
|||
*/ |
|||
void Push(T val) { |
|||
std::unique_lock<std::mutex> lock(mutex); |
|||
if (closed) { |
|||
return; |
|||
} |
|||
|
|||
// If the buffer is full, wait |
|||
writer.wait(lock, [&]{ |
|||
return (writer_index + 1) % ArraySize != reader_index; |
|||
}); |
|||
|
|||
T* item = &Data()[writer_index]; |
|||
new (item) T(std::move(val)); |
|||
|
|||
writer_index = (writer_index + 1) % ArraySize; |
|||
|
|||
// Wake up waiting readers |
|||
lock.unlock(); |
|||
reader.notify_one(); |
|||
} |
|||
|
|||
/** |
|||
* Pops up to `dest_len` items from the queue, storing them in `dest`. This function will not |
|||
* block, and might return 0 values if there are no elements in the queue when it is called. |
|||
* |
|||
* @return The number of elements stored in `dest`. If the queue has been closed, returns |
|||
* `QUEUE_CLOSED`. |
|||
*/ |
|||
size_t Pop(T* dest, size_t dest_len) { |
|||
std::unique_lock<std::mutex> lock(mutex); |
|||
if (closed && !CanRead()) { |
|||
return QUEUE_CLOSED; |
|||
} |
|||
return PopInternal(dest, dest_len); |
|||
} |
|||
|
|||
/** |
|||
* Pops up to `dest_len` items from the queue, storing them in `dest`. This function will block |
|||
* if there are no elements in the queue when it is called. |
|||
* |
|||
* @return The number of elements stored in `dest`. If the queue has been closed, returns |
|||
* `QUEUE_CLOSED`. |
|||
*/ |
|||
size_t BlockingPop(T* dest, size_t dest_len) { |
|||
std::unique_lock<std::mutex> lock(mutex); |
|||
if (closed && !CanRead()) { |
|||
return QUEUE_CLOSED; |
|||
} |
|||
|
|||
while (!CanRead()) { |
|||
reader.wait(lock); |
|||
if (closed && !CanRead()) { |
|||
return QUEUE_CLOSED; |
|||
} |
|||
} |
|||
_dbg_assert_(Common, CanRead()); |
|||
return PopInternal(dest, dest_len); |
|||
} |
|||
|
|||
/** |
|||
* Closes the queue. After calling this method, `Push` operations won't have any effect, and |
|||
* `PopMany` and `PopManyBlock` will start returning `QUEUE_CLOSED`. This is intended to allow |
|||
* a graceful shutdown of all consumers. |
|||
*/ |
|||
void Close() { |
|||
std::unique_lock<std::mutex> lock(mutex); |
|||
closed = true; |
|||
// We need to wake up any reader that are waiting for an item that will never come. |
|||
lock.unlock(); |
|||
reader.notify_all(); |
|||
} |
|||
|
|||
/// Returns true if `Close()` has been called. |
|||
bool IsClosed() const { |
|||
return closed; |
|||
} |
|||
|
|||
private: |
|||
size_t PopInternal(T* dest, size_t dest_len) { |
|||
size_t output_count = 0; |
|||
while (output_count < dest_len && CanRead()) { |
|||
_dbg_assert_(Common, CanRead()); |
|||
|
|||
T* item = &Data()[reader_index]; |
|||
T out_val = std::move(*item); |
|||
item->~T(); |
|||
|
|||
size_t prev_index = (reader_index + ArraySize - 1) % ArraySize; |
|||
reader_index = (reader_index + 1) % ArraySize; |
|||
if (writer_index == prev_index) { |
|||
writer.notify_one(); |
|||
} |
|||
dest[output_count++] = std::move(out_val); |
|||
} |
|||
return output_count; |
|||
} |
|||
|
|||
bool CanRead() const { |
|||
return reader_index != writer_index; |
|||
} |
|||
|
|||
T* Data() { |
|||
return static_cast<T*>(static_cast<void*>(&storage)); |
|||
} |
|||
|
|||
/// Storage for entries |
|||
typename std::aligned_storage<ArraySize * sizeof(T), |
|||
std::alignment_of<T>::value>::type storage; |
|||
|
|||
/// Data is valid in the half-open interval [reader, writer). If they are `QUEUE_CLOSED` then the |
|||
/// queue has been closed. |
|||
size_t writer_index = 0, reader_index = 0; |
|||
// True if the queue has been closed. |
|||
bool closed = false; |
|||
|
|||
/// Mutex that protects the entire data structure. |
|||
std::mutex mutex; |
|||
/// Signaling wakes up reader which is waiting for storage to be non-empty. |
|||
std::condition_variable reader; |
|||
/// Signaling wakes up writer which is waiting for storage to be non-full. |
|||
std::condition_variable writer; |
|||
}; |
|||
|
|||
} // namespace |
|||
@ -1,319 +0,0 @@ |
|||
// Copyright 2013 Dolphin Emulator Project
|
|||
// Licensed under GPLv2
|
|||
// Refer to the license.txt file included.
|
|||
|
|||
#include <algorithm>
|
|||
|
|||
#ifdef _WIN32
|
|||
#include <windows.h>
|
|||
#include <array>
|
|||
#endif
|
|||
|
|||
#include "common/common.h"
|
|||
#include "common/log_manager.h" // Common
|
|||
#include "common/console_listener.h" // Common
|
|||
|
|||
ConsoleListener::ConsoleListener() |
|||
{ |
|||
#ifdef _WIN32
|
|||
hConsole = NULL; |
|||
bUseColor = true; |
|||
#else
|
|||
bUseColor = isatty(fileno(stdout)); |
|||
#endif
|
|||
} |
|||
|
|||
ConsoleListener::~ConsoleListener() |
|||
{ |
|||
Close(); |
|||
} |
|||
|
|||
// 100, 100, "Dolphin Log Console"
|
|||
// Open console window - width and height is the size of console window
|
|||
// Name is the window title
|
|||
void ConsoleListener::Open(bool Hidden, int Width, int Height, const char *Title) |
|||
{ |
|||
#ifdef _WIN32
|
|||
if (!GetConsoleWindow()) |
|||
{ |
|||
// Open the console window and create the window handle for GetStdHandle()
|
|||
AllocConsole(); |
|||
// Hide
|
|||
if (Hidden) ShowWindow(GetConsoleWindow(), SW_HIDE); |
|||
// Save the window handle that AllocConsole() created
|
|||
hConsole = GetStdHandle(STD_OUTPUT_HANDLE); |
|||
// Set the console window title
|
|||
SetConsoleTitle(Common::UTF8ToTStr(Title).c_str()); |
|||
// Set letter space
|
|||
LetterSpace(80, 4000); |
|||
//MoveWindow(GetConsoleWindow(), 200,200, 800,800, true);
|
|||
} |
|||
else |
|||
{ |
|||
hConsole = GetStdHandle(STD_OUTPUT_HANDLE); |
|||
} |
|||
#endif
|
|||
} |
|||
|
|||
void ConsoleListener::UpdateHandle() |
|||
{ |
|||
#ifdef _WIN32
|
|||
hConsole = GetStdHandle(STD_OUTPUT_HANDLE); |
|||
#endif
|
|||
} |
|||
|
|||
// Close the console window and close the eventual file handle
|
|||
void ConsoleListener::Close() |
|||
{ |
|||
#ifdef _WIN32
|
|||
if (hConsole == NULL) |
|||
return; |
|||
FreeConsole(); |
|||
hConsole = NULL; |
|||
#else
|
|||
fflush(NULL); |
|||
#endif
|
|||
} |
|||
|
|||
bool ConsoleListener::IsOpen() |
|||
{ |
|||
#ifdef _WIN32
|
|||
return (hConsole != NULL); |
|||
#else
|
|||
return true; |
|||
#endif
|
|||
} |
|||
|
|||
/*
|
|||
LetterSpace: SetConsoleScreenBufferSize and SetConsoleWindowInfo are |
|||
dependent on each other, that's the reason for the additional checks. |
|||
*/ |
|||
void ConsoleListener::BufferWidthHeight(int BufferWidth, int BufferHeight, int ScreenWidth, int ScreenHeight, bool BufferFirst) |
|||
{ |
|||
#ifdef _WIN32
|
|||
BOOL SB, SW; |
|||
if (BufferFirst) |
|||
{ |
|||
// Change screen buffer size
|
|||
COORD Co = {BufferWidth, BufferHeight}; |
|||
SB = SetConsoleScreenBufferSize(hConsole, Co); |
|||
// Change the screen buffer window size
|
|||
SMALL_RECT coo = {0,0,ScreenWidth, ScreenHeight}; // top, left, right, bottom
|
|||
SW = SetConsoleWindowInfo(hConsole, TRUE, &coo); |
|||
} |
|||
else |
|||
{ |
|||
// Change the screen buffer window size
|
|||
SMALL_RECT coo = {0,0, ScreenWidth, ScreenHeight}; // top, left, right, bottom
|
|||
SW = SetConsoleWindowInfo(hConsole, TRUE, &coo); |
|||
// Change screen buffer size
|
|||
COORD Co = {BufferWidth, BufferHeight}; |
|||
SB = SetConsoleScreenBufferSize(hConsole, Co); |
|||
} |
|||
#endif
|
|||
} |
|||
void ConsoleListener::LetterSpace(int Width, int Height) |
|||
{ |
|||
#ifdef _WIN32
|
|||
// Get console info
|
|||
CONSOLE_SCREEN_BUFFER_INFO ConInfo; |
|||
GetConsoleScreenBufferInfo(hConsole, &ConInfo); |
|||
|
|||
//
|
|||
int OldBufferWidth = ConInfo.dwSize.X; |
|||
int OldBufferHeight = ConInfo.dwSize.Y; |
|||
int OldScreenWidth = (ConInfo.srWindow.Right - ConInfo.srWindow.Left); |
|||
int OldScreenHeight = (ConInfo.srWindow.Bottom - ConInfo.srWindow.Top); |
|||
//
|
|||
int NewBufferWidth = Width; |
|||
int NewBufferHeight = Height; |
|||
int NewScreenWidth = NewBufferWidth - 1; |
|||
int NewScreenHeight = OldScreenHeight; |
|||
|
|||
// Width
|
|||
BufferWidthHeight(NewBufferWidth, OldBufferHeight, NewScreenWidth, OldScreenHeight, (NewBufferWidth > OldScreenWidth-1)); |
|||
// Height
|
|||
BufferWidthHeight(NewBufferWidth, NewBufferHeight, NewScreenWidth, NewScreenHeight, (NewBufferHeight > OldScreenHeight-1)); |
|||
|
|||
// Resize the window too
|
|||
//MoveWindow(GetConsoleWindow(), 200,200, (Width*8 + 50),(NewScreenHeight*12 + 200), true);
|
|||
#endif
|
|||
} |
|||
#ifdef _WIN32
|
|||
COORD ConsoleListener::GetCoordinates(int BytesRead, int BufferWidth) |
|||
{ |
|||
COORD Ret = {0, 0}; |
|||
// Full rows
|
|||
int Step = (int)floor((float)BytesRead / (float)BufferWidth); |
|||
Ret.Y += Step; |
|||
// Partial row
|
|||
Ret.X = BytesRead - (BufferWidth * Step); |
|||
return Ret; |
|||
} |
|||
#endif
|
|||
void ConsoleListener::PixelSpace(int Left, int Top, int Width, int Height, bool Resize) |
|||
{ |
|||
#ifdef _WIN32
|
|||
// Check size
|
|||
if (Width < 8 || Height < 12) return; |
|||
|
|||
bool DBef = true; |
|||
bool DAft = true; |
|||
std::string SLog = ""; |
|||
|
|||
const HWND hWnd = GetConsoleWindow(); |
|||
const HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE); |
|||
|
|||
// Get console info
|
|||
CONSOLE_SCREEN_BUFFER_INFO ConInfo; |
|||
GetConsoleScreenBufferInfo(hConsole, &ConInfo); |
|||
DWORD BufferSize = ConInfo.dwSize.X * ConInfo.dwSize.Y; |
|||
|
|||
// ---------------------------------------------------------------------
|
|||
// Save the current text
|
|||
// ------------------------
|
|||
DWORD cCharsRead = 0; |
|||
COORD coordScreen = { 0, 0 }; |
|||
|
|||
static const int MAX_BYTES = 1024 * 16; |
|||
|
|||
std::vector<std::array<TCHAR, MAX_BYTES>> Str; |
|||
std::vector<std::array<WORD, MAX_BYTES>> Attr; |
|||
|
|||
// ReadConsoleOutputAttribute seems to have a limit at this level
|
|||
static const int ReadBufferSize = MAX_BYTES - 32; |
|||
|
|||
DWORD cAttrRead = ReadBufferSize; |
|||
DWORD BytesRead = 0; |
|||
while (BytesRead < BufferSize) |
|||
{ |
|||
Str.resize(Str.size() + 1); |
|||
if (!ReadConsoleOutputCharacter(hConsole, Str.back().data(), ReadBufferSize, coordScreen, &cCharsRead)) |
|||
SLog += Common::StringFromFormat("WriteConsoleOutputCharacter error"); |
|||
|
|||
Attr.resize(Attr.size() + 1); |
|||
if (!ReadConsoleOutputAttribute(hConsole, Attr.back().data(), ReadBufferSize, coordScreen, &cAttrRead)) |
|||
SLog += Common::StringFromFormat("WriteConsoleOutputAttribute error"); |
|||
|
|||
// Break on error
|
|||
if (cAttrRead == 0) break; |
|||
BytesRead += cAttrRead; |
|||
coordScreen = GetCoordinates(BytesRead, ConInfo.dwSize.X); |
|||
} |
|||
// Letter space
|
|||
int LWidth = (int)(floor((float)Width / 8.0f) - 1.0f); |
|||
int LHeight = (int)(floor((float)Height / 12.0f) - 1.0f); |
|||
int LBufWidth = LWidth + 1; |
|||
int LBufHeight = (int)floor((float)BufferSize / (float)LBufWidth); |
|||
// Change screen buffer size
|
|||
LetterSpace(LBufWidth, LBufHeight); |
|||
|
|||
|
|||
ClearScreen(true); |
|||
coordScreen.Y = 0; |
|||
coordScreen.X = 0; |
|||
DWORD cCharsWritten = 0; |
|||
|
|||
int BytesWritten = 0; |
|||
DWORD cAttrWritten = 0; |
|||
for (size_t i = 0; i < Attr.size(); i++) |
|||
{ |
|||
if (!WriteConsoleOutputCharacter(hConsole, Str[i].data(), ReadBufferSize, coordScreen, &cCharsWritten)) |
|||
SLog += Common::StringFromFormat("WriteConsoleOutputCharacter error"); |
|||
if (!WriteConsoleOutputAttribute(hConsole, Attr[i].data(), ReadBufferSize, coordScreen, &cAttrWritten)) |
|||
SLog += Common::StringFromFormat("WriteConsoleOutputAttribute error"); |
|||
|
|||
BytesWritten += cAttrWritten; |
|||
coordScreen = GetCoordinates(BytesWritten, LBufWidth); |
|||
} |
|||
|
|||
const int OldCursor = ConInfo.dwCursorPosition.Y * ConInfo.dwSize.X + ConInfo.dwCursorPosition.X; |
|||
COORD Coo = GetCoordinates(OldCursor, LBufWidth); |
|||
SetConsoleCursorPosition(hConsole, Coo); |
|||
|
|||
if (SLog.length() > 0) Log(LogTypes::LNOTICE, SLog.c_str()); |
|||
|
|||
// Resize the window too
|
|||
if (Resize) MoveWindow(GetConsoleWindow(), Left,Top, (Width + 100),Height, true); |
|||
#endif
|
|||
} |
|||
|
|||
void ConsoleListener::Log(LogTypes::LOG_LEVELS Level, const char *Text) |
|||
{ |
|||
#if defined(_WIN32)
|
|||
WORD Color; |
|||
|
|||
switch (Level) |
|||
{ |
|||
case OS_LEVEL: // light yellow
|
|||
Color = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY; |
|||
break; |
|||
case NOTICE_LEVEL: // light green
|
|||
Color = FOREGROUND_GREEN | FOREGROUND_INTENSITY; |
|||
break; |
|||
case ERROR_LEVEL: // light red
|
|||
Color = FOREGROUND_RED | FOREGROUND_INTENSITY; |
|||
break; |
|||
case WARNING_LEVEL: // light purple
|
|||
Color = FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_INTENSITY; |
|||
break; |
|||
case INFO_LEVEL: // cyan
|
|||
Color = FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_INTENSITY; |
|||
break; |
|||
case DEBUG_LEVEL: // gray
|
|||
Color = FOREGROUND_INTENSITY; |
|||
break; |
|||
default: // off-white
|
|||
Color = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE; |
|||
break; |
|||
} |
|||
SetConsoleTextAttribute(hConsole, Color); |
|||
printf(Text); |
|||
#else
|
|||
char ColorAttr[16] = ""; |
|||
char ResetAttr[16] = ""; |
|||
|
|||
if (bUseColor) |
|||
{ |
|||
strcpy(ResetAttr, "\033[0m"); |
|||
switch (Level) |
|||
{ |
|||
case NOTICE_LEVEL: // light green
|
|||
strcpy(ColorAttr, "\033[92m"); |
|||
break; |
|||
case ERROR_LEVEL: // light red
|
|||
strcpy(ColorAttr, "\033[91m"); |
|||
break; |
|||
case WARNING_LEVEL: // light yellow
|
|||
strcpy(ColorAttr, "\033[93m"); |
|||
break; |
|||
default: |
|||
break; |
|||
} |
|||
} |
|||
fprintf(stderr, "%s%s%s", ColorAttr, Text, ResetAttr); |
|||
#endif
|
|||
} |
|||
// Clear console screen
|
|||
void ConsoleListener::ClearScreen(bool Cursor) |
|||
{ |
|||
#if defined(_WIN32)
|
|||
COORD coordScreen = { 0, 0 }; |
|||
DWORD cCharsWritten; |
|||
CONSOLE_SCREEN_BUFFER_INFO csbi; |
|||
DWORD dwConSize; |
|||
|
|||
HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE); |
|||
|
|||
GetConsoleScreenBufferInfo(hConsole, &csbi); |
|||
dwConSize = csbi.dwSize.X * csbi.dwSize.Y; |
|||
// Write space to the entire console
|
|||
FillConsoleOutputCharacter(hConsole, TEXT(' '), dwConSize, coordScreen, &cCharsWritten); |
|||
GetConsoleScreenBufferInfo(hConsole, &csbi); |
|||
FillConsoleOutputAttribute(hConsole, csbi.wAttributes, dwConSize, coordScreen, &cCharsWritten); |
|||
// Reset cursor
|
|||
if (Cursor) SetConsoleCursorPosition(hConsole, coordScreen); |
|||
#endif
|
|||
} |
|||
|
|||
|
|||
@ -1,38 +0,0 @@ |
|||
// Copyright 2013 Dolphin Emulator Project |
|||
// Licensed under GPLv2 |
|||
// Refer to the license.txt file included. |
|||
|
|||
#pragma once |
|||
|
|||
#include "common/log_manager.h" |
|||
|
|||
#ifdef _WIN32 |
|||
#include <windows.h> |
|||
#endif |
|||
|
|||
class ConsoleListener : public LogListener |
|||
{ |
|||
public: |
|||
ConsoleListener(); |
|||
~ConsoleListener(); |
|||
|
|||
void Open(bool Hidden = false, int Width = 100, int Height = 100, const char * Name = "Console"); |
|||
void UpdateHandle(); |
|||
void Close(); |
|||
bool IsOpen(); |
|||
void LetterSpace(int Width, int Height); |
|||
void BufferWidthHeight(int BufferWidth, int BufferHeight, int ScreenWidth, int ScreenHeight, bool BufferFirst); |
|||
void PixelSpace(int Left, int Top, int Width, int Height, bool); |
|||
#ifdef _WIN32 |
|||
COORD GetCoordinates(int BytesRead, int BufferWidth); |
|||
#endif |
|||
void Log(LogTypes::LOG_LEVELS, const char *Text) override; |
|||
void ClearScreen(bool Cursor = true); |
|||
|
|||
private: |
|||
#ifdef _WIN32 |
|||
HWND GetHwnd(void); |
|||
HANDLE hConsole; |
|||
#endif |
|||
bool bUseColor; |
|||
}; |
|||
@ -1,199 +0,0 @@ |
|||
// Copyright 2013 Dolphin Emulator Project
|
|||
// Licensed under GPLv2
|
|||
// Refer to the license.txt file included.
|
|||
|
|||
#include <algorithm>
|
|||
|
|||
#include "common/log_manager.h"
|
|||
#include "common/console_listener.h"
|
|||
#include "common/timer.h"
|
|||
|
|||
void GenericLog(LogTypes::LOG_LEVELS level, LogTypes::LOG_TYPE type, const char* file, int line, |
|||
const char* function, const char* fmt, ...) |
|||
{ |
|||
va_list args; |
|||
va_start(args, fmt); |
|||
|
|||
if (LogManager::GetInstance()) { |
|||
LogManager::GetInstance()->Log(level, type, |
|||
file, line, function, fmt, args); |
|||
} |
|||
va_end(args); |
|||
} |
|||
|
|||
LogManager *LogManager::m_logManager = NULL; |
|||
|
|||
LogManager::LogManager() |
|||
{ |
|||
// create log files
|
|||
m_Log[LogTypes::MASTER_LOG] = new LogContainer("*", "Master Log"); |
|||
m_Log[LogTypes::BOOT] = new LogContainer("BOOT", "Boot"); |
|||
m_Log[LogTypes::COMMON] = new LogContainer("COMMON", "Common"); |
|||
m_Log[LogTypes::CONFIG] = new LogContainer("CONFIG", "Configuration"); |
|||
m_Log[LogTypes::DISCIO] = new LogContainer("DIO", "Disc IO"); |
|||
m_Log[LogTypes::FILEMON] = new LogContainer("FileMon", "File Monitor"); |
|||
m_Log[LogTypes::PAD] = new LogContainer("PAD", "Pad"); |
|||
m_Log[LogTypes::PIXELENGINE] = new LogContainer("PE", "PixelEngine"); |
|||
m_Log[LogTypes::COMMANDPROCESSOR] = new LogContainer("CP", "CommandProc"); |
|||
m_Log[LogTypes::VIDEOINTERFACE] = new LogContainer("VI", "VideoInt"); |
|||
m_Log[LogTypes::SERIALINTERFACE] = new LogContainer("SI", "SerialInt"); |
|||
m_Log[LogTypes::PROCESSORINTERFACE] = new LogContainer("PI", "ProcessorInt"); |
|||
m_Log[LogTypes::MEMMAP] = new LogContainer("MI", "MI & memmap"); |
|||
m_Log[LogTypes::SP1] = new LogContainer("SP1", "Serial Port 1"); |
|||
m_Log[LogTypes::STREAMINGINTERFACE] = new LogContainer("Stream", "StreamingInt"); |
|||
m_Log[LogTypes::DSPINTERFACE] = new LogContainer("DSP", "DSPInterface"); |
|||
m_Log[LogTypes::DVDINTERFACE] = new LogContainer("DVD", "DVDInterface"); |
|||
m_Log[LogTypes::GSP] = new LogContainer("GSP", "GSP"); |
|||
m_Log[LogTypes::EXPANSIONINTERFACE] = new LogContainer("EXI", "ExpansionInt"); |
|||
m_Log[LogTypes::GDB_STUB] = new LogContainer("GDB_STUB", "GDB Stub"); |
|||
m_Log[LogTypes::AUDIO_INTERFACE] = new LogContainer("AI", "AudioInt"); |
|||
m_Log[LogTypes::ARM11] = new LogContainer("ARM11", "ARM11"); |
|||
m_Log[LogTypes::OSHLE] = new LogContainer("HLE", "HLE"); |
|||
m_Log[LogTypes::DSPHLE] = new LogContainer("DSPHLE", "DSP HLE"); |
|||
m_Log[LogTypes::DSPLLE] = new LogContainer("DSPLLE", "DSP LLE"); |
|||
m_Log[LogTypes::DSP_MAIL] = new LogContainer("DSPMails", "DSP Mails"); |
|||
m_Log[LogTypes::VIDEO] = new LogContainer("Video", "Video Backend"); |
|||
m_Log[LogTypes::AUDIO] = new LogContainer("Audio", "Audio Emulator"); |
|||
m_Log[LogTypes::DYNA_REC] = new LogContainer("JIT", "JIT"); |
|||
m_Log[LogTypes::CONSOLE] = new LogContainer("CONSOLE", "Dolphin Console"); |
|||
m_Log[LogTypes::OSREPORT] = new LogContainer("OSREPORT", "OSReport"); |
|||
m_Log[LogTypes::TIME] = new LogContainer("Time", "Core Timing"); |
|||
m_Log[LogTypes::LOADER] = new LogContainer("Loader", "Loader"); |
|||
m_Log[LogTypes::FILESYS] = new LogContainer("FileSys", "File System"); |
|||
m_Log[LogTypes::WII_IPC_HID] = new LogContainer("WII_IPC_HID", "WII IPC HID"); |
|||
m_Log[LogTypes::KERNEL] = new LogContainer("KERNEL", "KERNEL HLE"); |
|||
m_Log[LogTypes::WII_IPC_DVD] = new LogContainer("WII_IPC_DVD", "WII IPC DVD"); |
|||
m_Log[LogTypes::WII_IPC_ES] = new LogContainer("WII_IPC_ES", "WII IPC ES"); |
|||
m_Log[LogTypes::WII_IPC_FILEIO] = new LogContainer("WII_IPC_FILEIO", "WII IPC FILEIO"); |
|||
m_Log[LogTypes::RENDER] = new LogContainer("RENDER", "RENDER"); |
|||
m_Log[LogTypes::GPU] = new LogContainer("GPU", "GPU"); |
|||
m_Log[LogTypes::SVC] = new LogContainer("SVC", "Supervisor Call HLE"); |
|||
m_Log[LogTypes::NDMA] = new LogContainer("NDMA", "NDMA"); |
|||
m_Log[LogTypes::HLE] = new LogContainer("HLE", "High Level Emulation"); |
|||
m_Log[LogTypes::HW] = new LogContainer("HW", "Hardware"); |
|||
m_Log[LogTypes::ACTIONREPLAY] = new LogContainer("ActionReplay", "ActionReplay"); |
|||
m_Log[LogTypes::MEMCARD_MANAGER] = new LogContainer("MemCard Manager", "MemCard Manager"); |
|||
m_Log[LogTypes::NETPLAY] = new LogContainer("NETPLAY", "Netplay"); |
|||
m_Log[LogTypes::GUI] = new LogContainer("GUI", "GUI"); |
|||
|
|||
m_fileLog = new FileLogListener(FileUtil::GetUserPath(F_MAINLOG_IDX).c_str()); |
|||
m_consoleLog = new ConsoleListener(); |
|||
m_debuggerLog = new DebuggerLogListener(); |
|||
|
|||
for (int i = 0; i < LogTypes::NUMBER_OF_LOGS; ++i) |
|||
{ |
|||
m_Log[i]->SetEnable(true); |
|||
m_Log[i]->AddListener(m_fileLog); |
|||
m_Log[i]->AddListener(m_consoleLog); |
|||
#ifdef _MSC_VER
|
|||
if (IsDebuggerPresent()) |
|||
m_Log[i]->AddListener(m_debuggerLog); |
|||
#endif
|
|||
} |
|||
|
|||
m_consoleLog->Open(); |
|||
} |
|||
|
|||
LogManager::~LogManager() |
|||
{ |
|||
for (int i = 0; i < LogTypes::NUMBER_OF_LOGS; ++i) |
|||
{ |
|||
m_logManager->RemoveListener((LogTypes::LOG_TYPE)i, m_fileLog); |
|||
m_logManager->RemoveListener((LogTypes::LOG_TYPE)i, m_consoleLog); |
|||
m_logManager->RemoveListener((LogTypes::LOG_TYPE)i, m_debuggerLog); |
|||
} |
|||
|
|||
for (int i = 0; i < LogTypes::NUMBER_OF_LOGS; ++i) |
|||
delete m_Log[i]; |
|||
|
|||
delete m_fileLog; |
|||
delete m_consoleLog; |
|||
delete m_debuggerLog; |
|||
} |
|||
|
|||
void LogManager::Log(LogTypes::LOG_LEVELS level, LogTypes::LOG_TYPE type, const char* file, |
|||
int line, const char* function, const char *fmt, va_list args) |
|||
{ |
|||
char temp[MAX_MSGLEN]; |
|||
char msg[MAX_MSGLEN * 2]; |
|||
LogContainer *log = m_Log[type]; |
|||
|
|||
if (!log->IsEnabled() || level > log->GetLevel() || ! log->HasListeners()) |
|||
return; |
|||
|
|||
Common::CharArrayFromFormatV(temp, MAX_MSGLEN, fmt, args); |
|||
|
|||
static const char level_to_char[7] = "ONEWID"; |
|||
sprintf(msg, "%s %s:%u %c[%s] %s: %s\n", Common::Timer::GetTimeFormatted().c_str(), file, line, |
|||
level_to_char[(int)level], log->GetShortName(), function, temp); |
|||
|
|||
#ifdef ANDROID
|
|||
Host_SysMessage(msg); |
|||
#endif
|
|||
log->Trigger(level, msg); |
|||
} |
|||
|
|||
void LogManager::Init() |
|||
{ |
|||
m_logManager = new LogManager(); |
|||
} |
|||
|
|||
void LogManager::Shutdown() |
|||
{ |
|||
delete m_logManager; |
|||
m_logManager = NULL; |
|||
} |
|||
|
|||
LogContainer::LogContainer(const char* shortName, const char* fullName, bool enable) |
|||
: m_enable(enable) |
|||
{ |
|||
strncpy(m_fullName, fullName, 128); |
|||
strncpy(m_shortName, shortName, 32); |
|||
m_level = LogTypes::MAX_LOGLEVEL; |
|||
} |
|||
|
|||
// LogContainer
|
|||
void LogContainer::AddListener(LogListener *listener) |
|||
{ |
|||
std::lock_guard<std::mutex> lk(m_listeners_lock); |
|||
m_listeners.insert(listener); |
|||
} |
|||
|
|||
void LogContainer::RemoveListener(LogListener *listener) |
|||
{ |
|||
std::lock_guard<std::mutex> lk(m_listeners_lock); |
|||
m_listeners.erase(listener); |
|||
} |
|||
|
|||
void LogContainer::Trigger(LogTypes::LOG_LEVELS level, const char *msg) |
|||
{ |
|||
std::lock_guard<std::mutex> lk(m_listeners_lock); |
|||
|
|||
std::set<LogListener*>::const_iterator i; |
|||
for (i = m_listeners.begin(); i != m_listeners.end(); ++i) |
|||
{ |
|||
(*i)->Log(level, msg); |
|||
} |
|||
} |
|||
|
|||
FileLogListener::FileLogListener(const char *filename) |
|||
{ |
|||
OpenFStream(m_logfile, filename, std::ios::app); |
|||
SetEnable(true); |
|||
} |
|||
|
|||
void FileLogListener::Log(LogTypes::LOG_LEVELS, const char *msg) |
|||
{ |
|||
if (!IsEnabled() || !IsValid()) |
|||
return; |
|||
|
|||
std::lock_guard<std::mutex> lk(m_log_lock); |
|||
m_logfile << msg << std::flush; |
|||
} |
|||
|
|||
void DebuggerLogListener::Log(LogTypes::LOG_LEVELS, const char *msg) |
|||
{ |
|||
#if _MSC_VER
|
|||
::OutputDebugStringA(msg); |
|||
#endif
|
|||
} |
|||
@ -1,166 +0,0 @@ |
|||
// Copyright 2013 Dolphin Emulator Project |
|||
// Licensed under GPLv2 |
|||
// Refer to the license.txt file included. |
|||
|
|||
#pragma once |
|||
|
|||
#include "common/log.h" |
|||
#include "common/string_util.h" |
|||
#include "common/file_util.h" |
|||
|
|||
#include <cstring> |
|||
#include <set> |
|||
#include <mutex> |
|||
|
|||
#define MAX_MESSAGES 8000 |
|||
#define MAX_MSGLEN 1024 |
|||
|
|||
|
|||
// pure virtual interface |
|||
class LogListener |
|||
{ |
|||
public: |
|||
virtual ~LogListener() {} |
|||
|
|||
virtual void Log(LogTypes::LOG_LEVELS, const char *msg) = 0; |
|||
}; |
|||
|
|||
class FileLogListener : public LogListener |
|||
{ |
|||
public: |
|||
FileLogListener(const char *filename); |
|||
|
|||
void Log(LogTypes::LOG_LEVELS, const char *msg) override; |
|||
|
|||
bool IsValid() { return !m_logfile.fail(); } |
|||
bool IsEnabled() const { return m_enable; } |
|||
void SetEnable(bool enable) { m_enable = enable; } |
|||
|
|||
const char* GetName() const { return "file"; } |
|||
|
|||
private: |
|||
std::mutex m_log_lock; |
|||
std::ofstream m_logfile; |
|||
bool m_enable; |
|||
}; |
|||
|
|||
class DebuggerLogListener : public LogListener |
|||
{ |
|||
public: |
|||
void Log(LogTypes::LOG_LEVELS, const char *msg) override; |
|||
}; |
|||
|
|||
class LogContainer |
|||
{ |
|||
public: |
|||
LogContainer(const char* shortName, const char* fullName, bool enable = false); |
|||
|
|||
const char* GetShortName() const { return m_shortName; } |
|||
const char* GetFullName() const { return m_fullName; } |
|||
|
|||
void AddListener(LogListener* listener); |
|||
void RemoveListener(LogListener* listener); |
|||
|
|||
void Trigger(LogTypes::LOG_LEVELS, const char *msg); |
|||
|
|||
bool IsEnabled() const { return m_enable; } |
|||
void SetEnable(bool enable) { m_enable = enable; } |
|||
|
|||
LogTypes::LOG_LEVELS GetLevel() const { return m_level; } |
|||
|
|||
void SetLevel(LogTypes::LOG_LEVELS level) { m_level = level; } |
|||
|
|||
bool HasListeners() const { return !m_listeners.empty(); } |
|||
|
|||
private: |
|||
char m_fullName[128]; |
|||
char m_shortName[32]; |
|||
bool m_enable; |
|||
LogTypes::LOG_LEVELS m_level; |
|||
std::mutex m_listeners_lock; |
|||
std::set<LogListener*> m_listeners; |
|||
}; |
|||
|
|||
class ConsoleListener; |
|||
|
|||
class LogManager : NonCopyable |
|||
{ |
|||
private: |
|||
LogContainer* m_Log[LogTypes::NUMBER_OF_LOGS]; |
|||
FileLogListener *m_fileLog; |
|||
ConsoleListener *m_consoleLog; |
|||
DebuggerLogListener *m_debuggerLog; |
|||
static LogManager *m_logManager; // Singleton. Ugh. |
|||
|
|||
LogManager(); |
|||
~LogManager(); |
|||
public: |
|||
|
|||
static u32 GetMaxLevel() { return LogTypes::MAX_LOGLEVEL; } |
|||
|
|||
void Log(LogTypes::LOG_LEVELS level, LogTypes::LOG_TYPE type, const char* file, int line, |
|||
const char* function, const char *fmt, va_list args); |
|||
|
|||
void SetLogLevel(LogTypes::LOG_TYPE type, LogTypes::LOG_LEVELS level) |
|||
{ |
|||
m_Log[type]->SetLevel(level); |
|||
} |
|||
|
|||
void SetEnable(LogTypes::LOG_TYPE type, bool enable) |
|||
{ |
|||
m_Log[type]->SetEnable(enable); |
|||
} |
|||
|
|||
bool IsEnabled(LogTypes::LOG_TYPE type) const |
|||
{ |
|||
return m_Log[type]->IsEnabled(); |
|||
} |
|||
|
|||
const char* GetShortName(LogTypes::LOG_TYPE type) const |
|||
{ |
|||
return m_Log[type]->GetShortName(); |
|||
} |
|||
|
|||
const char* GetFullName(LogTypes::LOG_TYPE type) const |
|||
{ |
|||
return m_Log[type]->GetFullName(); |
|||
} |
|||
|
|||
void AddListener(LogTypes::LOG_TYPE type, LogListener *listener) |
|||
{ |
|||
m_Log[type]->AddListener(listener); |
|||
} |
|||
|
|||
void RemoveListener(LogTypes::LOG_TYPE type, LogListener *listener) |
|||
{ |
|||
m_Log[type]->RemoveListener(listener); |
|||
} |
|||
|
|||
FileLogListener *GetFileListener() const |
|||
{ |
|||
return m_fileLog; |
|||
} |
|||
|
|||
ConsoleListener *GetConsoleListener() const |
|||
{ |
|||
return m_consoleLog; |
|||
} |
|||
|
|||
DebuggerLogListener *GetDebuggerListener() const |
|||
{ |
|||
return m_debuggerLog; |
|||
} |
|||
|
|||
static LogManager* GetInstance() |
|||
{ |
|||
return m_logManager; |
|||
} |
|||
|
|||
static void SetInstance(LogManager *logManager) |
|||
{ |
|||
m_logManager = logManager; |
|||
} |
|||
|
|||
static void Init(); |
|||
static void Shutdown(); |
|||
}; |
|||
@ -0,0 +1,151 @@ |
|||
// Copyright 2014 Citra Emulator Project
|
|||
// Licensed under GPLv2 or any later version
|
|||
// Refer to the license.txt file included.
|
|||
|
|||
#include <algorithm>
|
|||
|
|||
#include "common/log.h" // For _dbg_assert_
|
|||
|
|||
#include "common/logging/backend.h"
|
|||
#include "common/logging/log.h"
|
|||
#include "common/logging/text_formatter.h"
|
|||
|
|||
namespace Log { |
|||
|
|||
static std::shared_ptr<Logger> global_logger; |
|||
|
|||
/// Macro listing all log classes. Code should define CLS and SUB as desired before invoking this.
|
|||
#define ALL_LOG_CLASSES() \
|
|||
CLS(Log) \ |
|||
CLS(Common) \ |
|||
SUB(Common, Filesystem) \ |
|||
SUB(Common, Memory) \ |
|||
CLS(Core) \ |
|||
SUB(Core, ARM11) \ |
|||
CLS(Config) \ |
|||
CLS(Debug) \ |
|||
SUB(Debug, Emulated) \ |
|||
SUB(Debug, GPU) \ |
|||
SUB(Debug, Breakpoint) \ |
|||
CLS(Kernel) \ |
|||
SUB(Kernel, SVC) \ |
|||
CLS(Service) \ |
|||
SUB(Service, SRV) \ |
|||
SUB(Service, FS) \ |
|||
SUB(Service, APT) \ |
|||
SUB(Service, GSP) \ |
|||
SUB(Service, AC) \ |
|||
SUB(Service, PTM) \ |
|||
SUB(Service, CFG) \ |
|||
SUB(Service, DSP) \ |
|||
SUB(Service, HID) \ |
|||
CLS(HW) \ |
|||
SUB(HW, Memory) \ |
|||
SUB(HW, GPU) \ |
|||
CLS(Frontend) \ |
|||
CLS(Render) \ |
|||
SUB(Render, Software) \ |
|||
SUB(Render, OpenGL) \ |
|||
CLS(Loader) |
|||
|
|||
Logger::Logger() { |
|||
// Register logging classes so that they can be queried at runtime
|
|||
size_t parent_class; |
|||
all_classes.reserve((size_t)Class::Count); |
|||
|
|||
#define CLS(x) \
|
|||
all_classes.push_back(Class::x); \ |
|||
parent_class = all_classes.size() - 1; |
|||
#define SUB(x, y) \
|
|||
all_classes.push_back(Class::x##_##y); \ |
|||
all_classes[parent_class].num_children += 1; |
|||
|
|||
ALL_LOG_CLASSES() |
|||
#undef CLS
|
|||
#undef SUB
|
|||
|
|||
// Ensures that ALL_LOG_CLASSES isn't missing any entries.
|
|||
_dbg_assert_(Log, all_classes.size() == (size_t)Class::Count); |
|||
} |
|||
|
|||
// GetClassName is a macro defined by Windows.h, grrr...
|
|||
const char* Logger::GetLogClassName(Class log_class) { |
|||
switch (log_class) { |
|||
#define CLS(x) case Class::x: return #x;
|
|||
#define SUB(x, y) case Class::x##_##y: return #x "." #y;
|
|||
ALL_LOG_CLASSES() |
|||
#undef CLS
|
|||
#undef SUB
|
|||
} |
|||
return "Unknown"; |
|||
} |
|||
|
|||
const char* Logger::GetLevelName(Level log_level) { |
|||
#define LVL(x) case Level::x: return #x
|
|||
switch (log_level) { |
|||
LVL(Trace); |
|||
LVL(Debug); |
|||
LVL(Info); |
|||
LVL(Warning); |
|||
LVL(Error); |
|||
LVL(Critical); |
|||
} |
|||
return "Unknown"; |
|||
#undef LVL
|
|||
} |
|||
|
|||
void Logger::LogMessage(Entry entry) { |
|||
ring_buffer.Push(std::move(entry)); |
|||
} |
|||
|
|||
size_t Logger::GetEntries(Entry* out_buffer, size_t buffer_len) { |
|||
return ring_buffer.BlockingPop(out_buffer, buffer_len); |
|||
} |
|||
|
|||
std::shared_ptr<Logger> InitGlobalLogger() { |
|||
global_logger = std::make_shared<Logger>(); |
|||
return global_logger; |
|||
} |
|||
|
|||
Entry CreateEntry(Class log_class, Level log_level, |
|||
const char* filename, unsigned int line_nr, const char* function, |
|||
const char* format, va_list args) { |
|||
using std::chrono::steady_clock; |
|||
using std::chrono::duration_cast; |
|||
|
|||
static steady_clock::time_point time_origin = steady_clock::now(); |
|||
|
|||
std::array<char, 4 * 1024> formatting_buffer; |
|||
|
|||
Entry entry; |
|||
entry.timestamp = duration_cast<std::chrono::microseconds>(steady_clock::now() - time_origin); |
|||
entry.log_class = log_class; |
|||
entry.log_level = log_level; |
|||
|
|||
snprintf(formatting_buffer.data(), formatting_buffer.size(), "%s:%s:%u", filename, function, line_nr); |
|||
entry.location = std::string(formatting_buffer.data()); |
|||
|
|||
vsnprintf(formatting_buffer.data(), formatting_buffer.size(), format, args); |
|||
entry.message = std::string(formatting_buffer.data()); |
|||
|
|||
return std::move(entry); |
|||
} |
|||
|
|||
void LogMessage(Class log_class, Level log_level, |
|||
const char* filename, unsigned int line_nr, const char* function, |
|||
const char* format, ...) { |
|||
va_list args; |
|||
va_start(args, format); |
|||
Entry entry = CreateEntry(log_class, log_level, |
|||
filename, line_nr, function, format, args); |
|||
va_end(args); |
|||
|
|||
if (global_logger != nullptr && !global_logger->IsClosed()) { |
|||
global_logger->LogMessage(std::move(entry)); |
|||
} else { |
|||
// Fall back to directly printing to stderr
|
|||
PrintMessage(entry); |
|||
} |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,134 @@ |
|||
// Copyright 2014 Citra Emulator Project |
|||
// Licensed under GPLv2 or any later version |
|||
// Refer to the license.txt file included. |
|||
|
|||
#pragma once |
|||
|
|||
#include <cstdarg> |
|||
#include <memory> |
|||
#include <vector> |
|||
|
|||
#include "common/concurrent_ring_buffer.h" |
|||
|
|||
#include "common/logging/log.h" |
|||
|
|||
namespace Log { |
|||
|
|||
/** |
|||
* A log entry. Log entries are store in a structured format to permit more varied output |
|||
* formatting on different frontends, as well as facilitating filtering and aggregation. |
|||
*/ |
|||
struct Entry { |
|||
std::chrono::microseconds timestamp; |
|||
Class log_class; |
|||
Level log_level; |
|||
std::string location; |
|||
std::string message; |
|||
|
|||
Entry() = default; |
|||
|
|||
// TODO(yuriks) Use defaulted move constructors once MSVC supports them |
|||
#define MOVE(member) member(std::move(o.member)) |
|||
Entry(Entry&& o) |
|||
: MOVE(timestamp), MOVE(log_class), MOVE(log_level), |
|||
MOVE(location), MOVE(message) |
|||
{} |
|||
#undef MOVE |
|||
|
|||
Entry& operator=(const Entry&& o) { |
|||
#define MOVE(member) member = std::move(o.member) |
|||
MOVE(timestamp); |
|||
MOVE(log_class); |
|||
MOVE(log_level); |
|||
MOVE(location); |
|||
MOVE(message); |
|||
#undef MOVE |
|||
return *this; |
|||
} |
|||
}; |
|||
|
|||
struct ClassInfo { |
|||
Class log_class; |
|||
|
|||
/** |
|||
* Total number of (direct or indirect) sub classes this class has. If any, they follow in |
|||
* sequence after this class in the class list. |
|||
*/ |
|||
unsigned int num_children = 0; |
|||
|
|||
ClassInfo(Class log_class) : log_class(log_class) {} |
|||
}; |
|||
|
|||
/** |
|||
* Logging management class. This class has the dual purpose of acting as an exchange point between |
|||
* the logging clients and the log outputter, as well as containing reflection info about available |
|||
* log classes. |
|||
*/ |
|||
class Logger { |
|||
private: |
|||
using Buffer = Common::ConcurrentRingBuffer<Entry, 16 * 1024 / sizeof(Entry)>; |
|||
|
|||
public: |
|||
static const size_t QUEUE_CLOSED = Buffer::QUEUE_CLOSED; |
|||
|
|||
Logger(); |
|||
|
|||
/** |
|||
* Returns a list of all vector classes and subclasses. The sequence returned is a pre-order of |
|||
* classes and subclasses, which together with the `num_children` field in ClassInfo, allows |
|||
* you to recover the hierarchy. |
|||
*/ |
|||
const std::vector<ClassInfo>& GetClasses() const { return all_classes; } |
|||
|
|||
/** |
|||
* Returns the name of the passed log class as a C-string. Subclasses are separated by periods |
|||
* instead of underscores as in the enumeration. |
|||
*/ |
|||
static const char* GetLogClassName(Class log_class); |
|||
|
|||
/** |
|||
* Returns the name of the passed log level as a C-string. |
|||
*/ |
|||
static const char* GetLevelName(Level log_level); |
|||
|
|||
/** |
|||
* Appends a messages to the log buffer. |
|||
* @note This function is thread safe. |
|||
*/ |
|||
void LogMessage(Entry entry); |
|||
|
|||
/** |
|||
* Retrieves a batch of messages from the log buffer, blocking until they are available. |
|||
* @note This function is thread safe. |
|||
* |
|||
* @param out_buffer Destination buffer that will receive the log entries. |
|||
* @param buffer_len The maximum size of `out_buffer`. |
|||
* @return The number of entries stored. In case the logger is shutting down, `QUEUE_CLOSED` is |
|||
* returned, no entries are stored and the logger should shutdown. |
|||
*/ |
|||
size_t GetEntries(Entry* out_buffer, size_t buffer_len); |
|||
|
|||
/** |
|||
* Initiates a shutdown of the logger. This will indicate to log output clients that they |
|||
* should shutdown. |
|||
*/ |
|||
void Close() { ring_buffer.Close(); } |
|||
|
|||
/** |
|||
* Returns true if Close() has already been called on the Logger. |
|||
*/ |
|||
bool IsClosed() const { return ring_buffer.IsClosed(); } |
|||
|
|||
private: |
|||
Buffer ring_buffer; |
|||
std::vector<ClassInfo> all_classes; |
|||
}; |
|||
|
|||
/// 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, const char* function, |
|||
const char* format, va_list args); |
|||
/// Initializes the default Logger. |
|||
std::shared_ptr<Logger> InitGlobalLogger(); |
|||
|
|||
} |
|||
@ -0,0 +1,132 @@ |
|||
// Copyright 2014 Citra Emulator Project
|
|||
// Licensed under GPLv2 or any later version
|
|||
// Refer to the license.txt file included.
|
|||
|
|||
#include <algorithm>
|
|||
|
|||
#include "common/logging/filter.h"
|
|||
#include "common/logging/backend.h"
|
|||
#include "common/string_util.h"
|
|||
|
|||
namespace Log { |
|||
|
|||
Filter::Filter(Level default_level) { |
|||
ResetAll(default_level); |
|||
} |
|||
|
|||
void Filter::ResetAll(Level level) { |
|||
class_levels.fill(level); |
|||
} |
|||
|
|||
void Filter::SetClassLevel(Class log_class, Level level) { |
|||
class_levels[static_cast<size_t>(log_class)] = level; |
|||
} |
|||
|
|||
void Filter::SetSubclassesLevel(const ClassInfo& log_class, Level level) { |
|||
const size_t log_class_i = static_cast<size_t>(log_class.log_class); |
|||
|
|||
const size_t begin = log_class_i + 1; |
|||
const size_t end = begin + log_class.num_children; |
|||
for (size_t i = begin; begin < end; ++i) { |
|||
class_levels[i] = level; |
|||
} |
|||
} |
|||
|
|||
void Filter::ParseFilterString(const std::string& filter_str) { |
|||
auto clause_begin = filter_str.cbegin(); |
|||
while (clause_begin != filter_str.cend()) { |
|||
auto clause_end = std::find(clause_begin, filter_str.cend(), ' '); |
|||
|
|||
// If clause isn't empty
|
|||
if (clause_end != clause_begin) { |
|||
ParseFilterRule(clause_begin, clause_end); |
|||
} |
|||
|
|||
if (clause_end != filter_str.cend()) { |
|||
// Skip over the whitespace
|
|||
++clause_end; |
|||
} |
|||
clause_begin = clause_end; |
|||
} |
|||
} |
|||
|
|||
template <typename It> |
|||
static Level GetLevelByName(const It begin, const It end) { |
|||
for (u8 i = 0; i < static_cast<u8>(Level::Count); ++i) { |
|||
const char* level_name = Logger::GetLevelName(static_cast<Level>(i)); |
|||
if (Common::ComparePartialString(begin, end, level_name)) { |
|||
return static_cast<Level>(i); |
|||
} |
|||
} |
|||
return Level::Count; |
|||
} |
|||
|
|||
template <typename It> |
|||
static Class GetClassByName(const It begin, const It end) { |
|||
for (ClassType i = 0; i < static_cast<ClassType>(Class::Count); ++i) { |
|||
const char* level_name = Logger::GetLogClassName(static_cast<Class>(i)); |
|||
if (Common::ComparePartialString(begin, end, level_name)) { |
|||
return static_cast<Class>(i); |
|||
} |
|||
} |
|||
return Class::Count; |
|||
} |
|||
|
|||
template <typename InputIt, typename T> |
|||
static InputIt find_last(InputIt begin, const InputIt end, const T& value) { |
|||
auto match = end; |
|||
while (begin != end) { |
|||
auto new_match = std::find(begin, end, value); |
|||
if (new_match != end) { |
|||
match = new_match; |
|||
++new_match; |
|||
} |
|||
begin = new_match; |
|||
} |
|||
return match; |
|||
} |
|||
|
|||
bool Filter::ParseFilterRule(const std::string::const_iterator begin, |
|||
const std::string::const_iterator end) { |
|||
auto level_separator = std::find(begin, end, ':'); |
|||
if (level_separator == end) { |
|||
LOG_ERROR(Log, "Invalid log filter. Must specify a log level after `:`: %s", |
|||
std::string(begin, end).c_str()); |
|||
return false; |
|||
} |
|||
|
|||
const Level level = GetLevelByName(level_separator + 1, end); |
|||
if (level == Level::Count) { |
|||
LOG_ERROR(Log, "Unknown log level in filter: %s", std::string(begin, end).c_str()); |
|||
return false; |
|||
} |
|||
|
|||
if (Common::ComparePartialString(begin, level_separator, "*")) { |
|||
ResetAll(level); |
|||
return true; |
|||
} |
|||
|
|||
auto class_name_end = find_last(begin, level_separator, '.'); |
|||
if (class_name_end != level_separator && |
|||
!Common::ComparePartialString(class_name_end + 1, level_separator, "*")) { |
|||
class_name_end = level_separator; |
|||
} |
|||
|
|||
const Class log_class = GetClassByName(begin, class_name_end); |
|||
if (log_class == Class::Count) { |
|||
LOG_ERROR(Log, "Unknown log class in filter: %s", std::string(begin, end).c_str()); |
|||
return false; |
|||
} |
|||
|
|||
if (class_name_end == level_separator) { |
|||
SetClassLevel(log_class, level); |
|||
} |
|||
SetSubclassesLevel(log_class, level); |
|||
return true; |
|||
} |
|||
|
|||
bool Filter::CheckMessage(Class log_class, Level level) const { |
|||
return static_cast<u8>(level) >= static_cast<u8>(class_levels[static_cast<size_t>(log_class)]); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,63 @@ |
|||
// Copyright 2014 Citra Emulator Project |
|||
// Licensed under GPLv2 or any later version |
|||
// Refer to the license.txt file included. |
|||
|
|||
#include <array> |
|||
#include <string> |
|||
|
|||
#include "common/logging/log.h" |
|||
|
|||
namespace Log { |
|||
|
|||
struct ClassInfo; |
|||
|
|||
/** |
|||
* Implements a log message filter which allows different log classes to have different minimum |
|||
* severity levels. The filter can be changed at runtime and can be parsed from a string to allow |
|||
* editing via the interface or loading from a configuration file. |
|||
*/ |
|||
class Filter { |
|||
public: |
|||
/// Initializes the filter with all classes having `default_level` as the minimum level. |
|||
Filter(Level default_level); |
|||
|
|||
/// Resets the filter so that all classes have `level` as the minimum displayed level. |
|||
void ResetAll(Level level); |
|||
/// Sets the minimum level of `log_class` (and not of its subclasses) to `level`. |
|||
void SetClassLevel(Class log_class, Level level); |
|||
/** |
|||
* Sets the minimum level of all of `log_class` subclasses to `level`. The level of `log_class` |
|||
* itself is not changed. |
|||
*/ |
|||
void SetSubclassesLevel(const ClassInfo& log_class, Level level); |
|||
|
|||
/** |
|||
* Parses a filter string and applies it to this filter. |
|||
* |
|||
* A filter string consists of a space-separated list of filter rules, each of the format |
|||
* `<class>:<level>`. `<class>` is a log class name, with subclasses separated using periods. |
|||
* A rule for a given class also affects all of its subclasses. `*` wildcards are allowed and |
|||
* can be used to apply a rule to all classes or to all subclasses of a class without affecting |
|||
* the parent class. `<level>` a severity level name which will be set as the minimum logging |
|||
* level of the matched classes. Rules are applied left to right, with each rule overriding |
|||
* previous ones in the sequence. |
|||
* |
|||
* A few examples of filter rules: |
|||
* - `*:Info` -- Resets the level of all classes to Info. |
|||
* - `Service:Info` -- Sets the level of Service and all subclasses (Service.FS, Service.APT, |
|||
* etc.) to Info. |
|||
* - `Service.*:Debug` -- Sets the level of all Service subclasses to Debug, while leaving the |
|||
* level of Service unchanged. |
|||
* - `Service.FS:Trace` -- Sets the level of the Service.FS class to Trace. |
|||
*/ |
|||
void ParseFilterString(const std::string& filter_str); |
|||
bool ParseFilterRule(const std::string::const_iterator start, const std::string::const_iterator end); |
|||
|
|||
/// Matches class/level combination against the filter, returning true if it passed. |
|||
bool CheckMessage(Class log_class, Level level) const; |
|||
|
|||
private: |
|||
std::array<Level, (size_t)Class::Count> class_levels; |
|||
}; |
|||
|
|||
} |
|||
@ -0,0 +1,115 @@ |
|||
// Copyright 2014 Citra Emulator Project |
|||
// Licensed under GPLv2 or any later version |
|||
// Refer to the license.txt file included. |
|||
|
|||
#pragma once |
|||
|
|||
#include <cassert> |
|||
#include <chrono> |
|||
#include <string> |
|||
|
|||
#include "common/common_types.h" |
|||
|
|||
namespace Log { |
|||
|
|||
/// Specifies the severity or level of detail of the log message. |
|||
enum class Level : u8 { |
|||
Trace, ///< Extremely detailed and repetitive debugging information that is likely to |
|||
/// pollute logs. |
|||
Debug, ///< Less detailed debugging information. |
|||
Info, ///< Status information from important points during execution. |
|||
Warning, ///< Minor or potential problems found during execution of a task. |
|||
Error, ///< Major problems found during execution of a task that prevent it from being |
|||
/// completed. |
|||
Critical, ///< Major problems during execution that threathen the stability of the entire |
|||
/// application. |
|||
|
|||
Count ///< Total number of logging levels |
|||
}; |
|||
|
|||
typedef u8 ClassType; |
|||
|
|||
/** |
|||
* Specifies the sub-system that generated the log message. |
|||
* |
|||
* @note If you add a new entry here, also add a corresponding one to `ALL_LOG_CLASSES` in log.cpp. |
|||
*/ |
|||
enum class Class : ClassType { |
|||
Log, ///< Messages about the log system itself |
|||
Common, ///< Library routines |
|||
Common_Filesystem, ///< Filesystem interface library |
|||
Common_Memory, ///< Memory mapping and management functions |
|||
Core, ///< LLE emulation core |
|||
Core_ARM11, ///< ARM11 CPU core |
|||
Config, ///< Emulator configuration (including commandline) |
|||
Debug, ///< Debugging tools |
|||
Debug_Emulated, ///< Debug messages from the emulated programs |
|||
Debug_GPU, ///< GPU debugging tools |
|||
Debug_Breakpoint, ///< Logging breakpoints and watchpoints |
|||
Kernel, ///< The HLE implementation of the CTR kernel |
|||
Kernel_SVC, ///< Kernel system calls |
|||
Service, ///< HLE implementation of system services. Each major service |
|||
/// should have its own subclass. |
|||
Service_SRV, ///< The SRV (Service Directory) implementation |
|||
Service_FS, ///< The FS (Filesystem) service implementation |
|||
Service_APT, ///< The APT (Applets) service |
|||
Service_GSP, ///< The GSP (GPU control) service |
|||
Service_AC, ///< The AC (WiFi status) service |
|||
Service_PTM, ///< The PTM (Power status & misc.) service |
|||
Service_CFG, ///< The CFG (Configuration) service |
|||
Service_DSP, ///< The DSP (DSP control) service |
|||
Service_HID, ///< The HID (User input) service |
|||
HW, ///< Low-level hardware emulation |
|||
HW_Memory, ///< Memory-map and address translation |
|||
HW_GPU, ///< GPU control emulation |
|||
Frontend, ///< Emulator UI |
|||
Render, ///< Emulator video output and hardware acceleration |
|||
Render_Software, ///< Software renderer backend |
|||
Render_OpenGL, ///< OpenGL backend |
|||
Loader, ///< ROM loader |
|||
|
|||
Count ///< Total number of logging classes |
|||
}; |
|||
|
|||
/** |
|||
* Level below which messages are simply discarded without buffering regardless of the display |
|||
* settings. |
|||
*/ |
|||
const Level MINIMUM_LEVEL = |
|||
#ifdef _DEBUG |
|||
Level::Trace; |
|||
#else |
|||
Level::Debug; |
|||
#endif |
|||
|
|||
/** |
|||
* Logs a message to the global logger. This proxy exists to avoid exposing the details of the |
|||
* Logger class, including the ConcurrentRingBuffer template, to all files that desire to log |
|||
* messages, reducing unecessary recompilations. |
|||
*/ |
|||
void LogMessage(Class log_class, Level log_level, |
|||
const char* filename, unsigned int line_nr, const char* function, |
|||
#ifdef _MSC_VER |
|||
_Printf_format_string_ |
|||
#endif |
|||
const char* format, ...) |
|||
#ifdef __GNUC__ |
|||
__attribute__((format(printf, 6, 7))) |
|||
#endif |
|||
; |
|||
|
|||
} // namespace Log |
|||
|
|||
#define LOG_GENERIC(log_class, log_level, ...) \ |
|||
do { \ |
|||
if (::Log::Level::log_level >= ::Log::MINIMUM_LEVEL) \ |
|||
::Log::LogMessage(::Log::Class::log_class, ::Log::Level::log_level, \ |
|||
__FILE__, __LINE__, __func__, __VA_ARGS__); \ |
|||
} while (0) |
|||
|
|||
#define LOG_TRACE( log_class, ...) LOG_GENERIC(log_class, Trace, __VA_ARGS__) |
|||
#define LOG_DEBUG( log_class, ...) LOG_GENERIC(log_class, Debug, __VA_ARGS__) |
|||
#define LOG_INFO( log_class, ...) LOG_GENERIC(log_class, Info, __VA_ARGS__) |
|||
#define LOG_WARNING( log_class, ...) LOG_GENERIC(log_class, Warning, __VA_ARGS__) |
|||
#define LOG_ERROR( log_class, ...) LOG_GENERIC(log_class, Error, __VA_ARGS__) |
|||
#define LOG_CRITICAL(log_class, ...) LOG_GENERIC(log_class, Critical, __VA_ARGS__) |
|||
@ -0,0 +1,136 @@ |
|||
// Copyright 2014 Citra Emulator Project
|
|||
// Licensed under GPLv2 or any later version
|
|||
// Refer to the license.txt file included.
|
|||
|
|||
#include <array>
|
|||
#include <cstdio>
|
|||
|
|||
#ifdef _WIN32
|
|||
# define WIN32_LEAN_AND_MEAN
|
|||
# include <Windows.h>
|
|||
#endif
|
|||
|
|||
#include "common/logging/backend.h"
|
|||
#include "common/logging/filter.h"
|
|||
#include "common/logging/log.h"
|
|||
#include "common/logging/text_formatter.h"
|
|||
|
|||
#include "common/string_util.h"
|
|||
|
|||
namespace Log { |
|||
|
|||
// TODO(bunnei): This should be moved to a generic path manipulation library
|
|||
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_fractional = static_cast<unsigned int>(entry.timestamp.count() % 1000000); |
|||
|
|||
const char* class_name = Logger::GetLogClassName(entry.log_class); |
|||
const char* level_name = Logger::GetLevelName(entry.log_level); |
|||
|
|||
snprintf(out_text, text_len, "[%4u.%06u] %s <%s> %s: %s", |
|||
time_seconds, time_fractional, class_name, level_name, |
|||
TrimSourcePath(entry.location.c_str()), entry.message.c_str()); |
|||
} |
|||
|
|||
void PrintMessage(const Entry& entry) { |
|||
std::array<char, 4 * 1024> format_buffer; |
|||
FormatLogMessage(entry, format_buffer.data(), format_buffer.size()); |
|||
fputs(format_buffer.data(), stderr); |
|||
fputc('\n', stderr); |
|||
} |
|||
|
|||
void PrintColoredMessage(const Entry& entry) { |
|||
#ifdef _WIN32
|
|||
static HANDLE console_handle = GetStdHandle(STD_ERROR_HANDLE); |
|||
|
|||
CONSOLE_SCREEN_BUFFER_INFO original_info = {0}; |
|||
GetConsoleScreenBufferInfo(console_handle, &original_info); |
|||
|
|||
WORD color = 0; |
|||
switch (entry.log_level) { |
|||
case Level::Trace: // Grey
|
|||
color = FOREGROUND_INTENSITY; break; |
|||
case Level::Debug: // Cyan
|
|||
color = FOREGROUND_GREEN | FOREGROUND_BLUE; break; |
|||
case Level::Info: // Bright gray
|
|||
color = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE; break; |
|||
case Level::Warning: // Bright yellow
|
|||
color = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY; break; |
|||
case Level::Error: // Bright red
|
|||
color = FOREGROUND_RED | FOREGROUND_INTENSITY; break; |
|||
case Level::Critical: // Bright magenta
|
|||
color = FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_INTENSITY; break; |
|||
} |
|||
|
|||
SetConsoleTextAttribute(console_handle, color); |
|||
#else
|
|||
# define ESC "\x1b"
|
|||
const char* color = ""; |
|||
switch (entry.log_level) { |
|||
case Level::Trace: // Grey
|
|||
color = ESC "[1;30m"; break; |
|||
case Level::Debug: // Cyan
|
|||
color = ESC "[0;36m"; break; |
|||
case Level::Info: // Bright gray
|
|||
color = ESC "[0;37m"; break; |
|||
case Level::Warning: // Bright yellow
|
|||
color = ESC "[1;33m"; break; |
|||
case Level::Error: // Bright red
|
|||
color = ESC "[1;31m"; break; |
|||
case Level::Critical: // Bright magenta
|
|||
color = ESC "[1;35m"; break; |
|||
} |
|||
|
|||
fputs(color, stderr); |
|||
#endif
|
|||
|
|||
PrintMessage(entry); |
|||
|
|||
#ifdef _WIN32
|
|||
SetConsoleTextAttribute(console_handle, original_info.wAttributes); |
|||
#else
|
|||
fputs(ESC "[0m", stderr); |
|||
# undef ESC
|
|||
#endif
|
|||
} |
|||
|
|||
void TextLoggingLoop(std::shared_ptr<Logger> logger, const Filter* filter) { |
|||
std::array<Entry, 256> entry_buffer; |
|||
|
|||
while (true) { |
|||
size_t num_entries = logger->GetEntries(entry_buffer.data(), entry_buffer.size()); |
|||
if (num_entries == Logger::QUEUE_CLOSED) { |
|||
break; |
|||
} |
|||
for (size_t i = 0; i < num_entries; ++i) { |
|||
const Entry& entry = entry_buffer[i]; |
|||
if (filter->CheckMessage(entry.log_class, entry.log_level)) { |
|||
PrintColoredMessage(entry); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,41 @@ |
|||
// Copyright 2014 Citra Emulator Project |
|||
// Licensed under GPLv2 or any later version |
|||
// Refer to the license.txt file included. |
|||
|
|||
#pragma once |
|||
|
|||
#include <cstddef> |
|||
#include <memory> |
|||
|
|||
namespace Log { |
|||
|
|||
class Logger; |
|||
struct Entry; |
|||
class Filter; |
|||
|
|||
/** |
|||
* 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 occurence 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. |
|||
void FormatLogMessage(const Entry& entry, char* out_text, size_t text_len); |
|||
/// Formats and prints a log entry to stderr. |
|||
void PrintMessage(const Entry& entry); |
|||
/// Prints the same message as `PrintMessage`, but colored acoording to the severity level. |
|||
void PrintColoredMessage(const Entry& entry); |
|||
|
|||
/** |
|||
* Logging loop that repeatedly reads messages from the provided logger and prints them to the |
|||
* console. It is the baseline barebones log outputter. |
|||
*/ |
|||
void TextLoggingLoop(std::shared_ptr<Logger> logger, const Filter* filter); |
|||
|
|||
} |
|||
@ -0,0 +1,16 @@ |
|||
// Copyright 2014 Citra Emulator Project |
|||
// Licensed under GPLv2 or any later version |
|||
// Refer to the license.txt file included. |
|||
|
|||
#pragma once |
|||
|
|||
#include <memory> |
|||
|
|||
namespace Common { |
|||
|
|||
template <typename T, typename... Args> |
|||
std::unique_ptr<T> make_unique(Args&&... args) { |
|||
return std::unique_ptr<T>(new T(std::forward<Args>(args)...)); |
|||
} |
|||
|
|||
} // namespace |
|||
@ -0,0 +1,37 @@ |
|||
// Copyright 2014 Citra Emulator Project |
|||
// Licensed under GPLv2 or any later version |
|||
// Refer to the license.txt file included. |
|||
|
|||
#pragma once |
|||
|
|||
namespace detail { |
|||
template <typename Func> |
|||
struct ScopeExitHelper { |
|||
explicit ScopeExitHelper(Func&& func) : func(std::move(func)) {} |
|||
~ScopeExitHelper() { func(); } |
|||
|
|||
Func func; |
|||
}; |
|||
|
|||
template <typename Func> |
|||
ScopeExitHelper<Func> ScopeExit(Func&& func) { return ScopeExitHelper<Func>(std::move(func)); } |
|||
} |
|||
|
|||
/** |
|||
* This macro allows you to conveniently specify a block of code that will run on scope exit. Handy |
|||
* for doing ad-hoc clean-up tasks in a function with multiple returns. |
|||
* |
|||
* Example usage: |
|||
* \code |
|||
* const int saved_val = g_foo; |
|||
* g_foo = 55; |
|||
* SCOPE_EXIT({ g_foo = saved_val; }); |
|||
* |
|||
* if (Bar()) { |
|||
* return 0; |
|||
* } else { |
|||
* return 20; |
|||
* } |
|||
* \endcode |
|||
*/ |
|||
#define SCOPE_EXIT(body) auto scope_exit_helper_##__LINE__ = detail::ScopeExit([&]() body) |
|||
Some files were not shown because too many files changed in this diff
Write
Preview
Loading…
Cancel
Save
Reference in new issue