chore: update project branding to citron

Signed-off-by: Zephyron <zephyron@citron-emu.org>
This commit is contained in:
Zephyron
2025-05-06 16:06:48 +10:00
parent 0812f75be5
commit bfb82e577c
631 changed files with 57247 additions and 59677 deletions

View File

@@ -0,0 +1,49 @@
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#ifdef _WIN32
#include <windows.h>
#include <wincon.h>
#endif
#include "common/logging/backend.h"
#include "citron/debugger/console.h"
#include "citron/uisettings.h"
namespace Debugger {
void ToggleConsole() {
static bool console_shown = false;
if (console_shown == UISettings::values.show_console.GetValue()) {
return;
} else {
console_shown = UISettings::values.show_console.GetValue();
}
using namespace Common::Log;
#if defined(_WIN32) && !defined(_DEBUG)
FILE* temp;
if (UISettings::values.show_console) {
if (AllocConsole()) {
// The first parameter for freopen_s is a out parameter, so we can just ignore it
freopen_s(&temp, "CONIN$", "r", stdin);
freopen_s(&temp, "CONOUT$", "w", stdout);
freopen_s(&temp, "CONOUT$", "w", stderr);
SetConsoleOutputCP(65001);
SetColorConsoleBackendEnabled(true);
}
} else {
if (FreeConsole()) {
// In order to close the console, we have to also detach the streams on it.
// Just redirect them to NUL if there is no console window
SetColorConsoleBackendEnabled(false);
freopen_s(&temp, "NUL", "r", stdin);
freopen_s(&temp, "NUL", "w", stdout);
freopen_s(&temp, "NUL", "w", stderr);
}
}
#else
SetColorConsoleBackendEnabled(UISettings::values.show_console.GetValue());
#endif
}
} // namespace Debugger

View File

@@ -0,0 +1,13 @@
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
namespace Debugger {
/**
* Uses the WINAPI to hide or show the stderr console. This function is a placeholder until we can
* get a real qt logging window which would work for all platforms.
*/
void ToggleConsole();
} // namespace Debugger

View File

@@ -0,0 +1,116 @@
// SPDX-FileCopyrightText: 2015 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <QAction>
#include <QLayout>
#include <QString>
#include "common/settings.h"
#include "hid_core/frontend/emulated_controller.h"
#include "hid_core/hid_core.h"
#include "input_common/drivers/tas_input.h"
#include "input_common/main.h"
#include "citron/configuration/configure_input_player_widget.h"
#include "citron/debugger/controller.h"
ControllerDialog::ControllerDialog(Core::HID::HIDCore& hid_core_,
std::shared_ptr<InputCommon::InputSubsystem> input_subsystem_,
QWidget* parent)
: QWidget(parent, Qt::Dialog), hid_core{hid_core_}, input_subsystem{input_subsystem_} {
setObjectName(QStringLiteral("Controller"));
setWindowTitle(tr("Controller P1"));
resize(500, 350);
setMinimumSize(500, 350);
// Enable the maximize button
setWindowFlags(windowFlags() | Qt::WindowMaximizeButtonHint);
widget = new PlayerControlPreview(this);
refreshConfiguration();
QLayout* layout = new QVBoxLayout(this);
layout->setContentsMargins(0, 0, 0, 0);
layout->addWidget(widget);
setLayout(layout);
// Configure focus so that widget is focusable and the dialog automatically forwards focus to
// it.
setFocusProxy(widget);
widget->setFocusPolicy(Qt::StrongFocus);
widget->setFocus();
}
void ControllerDialog::refreshConfiguration() {
UnloadController();
auto* player_1 = hid_core.GetEmulatedController(Core::HID::NpadIdType::Player1);
auto* handheld = hid_core.GetEmulatedController(Core::HID::NpadIdType::Handheld);
// Display the correct controller
controller = handheld->IsConnected() ? handheld : player_1;
Core::HID::ControllerUpdateCallback engine_callback{
.on_change = [this](Core::HID::ControllerTriggerType type) { ControllerUpdate(type); },
.is_npad_service = true,
};
callback_key = controller->SetCallback(engine_callback);
widget->SetController(controller);
is_controller_set = true;
}
QAction* ControllerDialog::toggleViewAction() {
if (toggle_view_action == nullptr) {
toggle_view_action = new QAction(tr("&Controller P1"), this);
toggle_view_action->setCheckable(true);
toggle_view_action->setChecked(isVisible());
connect(toggle_view_action, &QAction::toggled, this, &ControllerDialog::setVisible);
}
return toggle_view_action;
}
void ControllerDialog::UnloadController() {
widget->UnloadController();
if (is_controller_set) {
controller->DeleteCallback(callback_key);
is_controller_set = false;
}
}
void ControllerDialog::showEvent(QShowEvent* ev) {
if (toggle_view_action) {
toggle_view_action->setChecked(isVisible());
}
QWidget::showEvent(ev);
}
void ControllerDialog::hideEvent(QHideEvent* ev) {
if (toggle_view_action) {
toggle_view_action->setChecked(isVisible());
}
QWidget::hideEvent(ev);
}
void ControllerDialog::ControllerUpdate(Core::HID::ControllerTriggerType type) {
// TODO(german77): Remove TAS from here
switch (type) {
case Core::HID::ControllerTriggerType::Button:
case Core::HID::ControllerTriggerType::Stick: {
const auto buttons_values = controller->GetButtonsValues();
const auto stick_values = controller->GetSticks();
u64 buttons = 0;
std::size_t index = 0;
for (const auto& button : buttons_values) {
buttons |= button.value ? 1LLU << index : 0;
index++;
}
const InputCommon::TasInput::TasAnalog left_axis = {
.x = stick_values.left.x / 32767.f,
.y = stick_values.left.y / 32767.f,
};
const InputCommon::TasInput::TasAnalog right_axis = {
.x = stick_values.right.x / 32767.f,
.y = stick_values.right.y / 32767.f,
};
input_subsystem->GetTas()->RecordInput(buttons, left_axis, right_axis);
break;
}
default:
break;
}
}

View File

@@ -0,0 +1,56 @@
// SPDX-FileCopyrightText: 2015 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <QWidget>
class QAction;
class QHideEvent;
class QShowEvent;
class PlayerControlPreview;
namespace InputCommon {
class InputSubsystem;
}
namespace Core::HID {
class HIDCore;
class EmulatedController;
enum class ControllerTriggerType;
} // namespace Core::HID
class ControllerDialog : public QWidget {
Q_OBJECT
public:
explicit ControllerDialog(Core::HID::HIDCore& hid_core_,
std::shared_ptr<InputCommon::InputSubsystem> input_subsystem_,
QWidget* parent = nullptr);
/// Returns a QAction that can be used to toggle visibility of this dialog.
QAction* toggleViewAction();
/// Reloads the widget to apply any changes in the configuration
void refreshConfiguration();
/// Disables events from the emulated controller
void UnloadController();
protected:
void showEvent(QShowEvent* ev) override;
void hideEvent(QHideEvent* ev) override;
private:
/// Redirects input from the widget to the TAS driver
void ControllerUpdate(Core::HID::ControllerTriggerType type);
int callback_key;
bool is_controller_set{};
Core::HID::EmulatedController* controller;
QAction* toggle_view_action = nullptr;
PlayerControlPreview* widget;
Core::HID::HIDCore& hid_core;
std::shared_ptr<InputCommon::InputSubsystem> input_subsystem;
};

View File

@@ -0,0 +1,229 @@
// SPDX-FileCopyrightText: 2015 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <QAction>
#include <QLayout>
#include <QMouseEvent>
#include <QPainter>
#include <QString>
#include <QTimer>
#include "common/common_types.h"
#include "common/microprofile.h"
#include "citron/debugger/profiler.h"
#include "citron/util/util.h"
// Include the implementation of the UI in this file. This isn't in microprofile.cpp because the
// non-Qt frontends don't need it (and don't implement the UI drawing hooks either).
#if MICROPROFILE_ENABLED
#define MICROPROFILEUI_IMPL 1
#include "common/microprofileui.h"
class MicroProfileWidget : public QWidget {
public:
MicroProfileWidget(QWidget* parent = nullptr);
protected:
void paintEvent(QPaintEvent* ev) override;
void showEvent(QShowEvent* ev) override;
void hideEvent(QHideEvent* ev) override;
void mouseMoveEvent(QMouseEvent* ev) override;
void mousePressEvent(QMouseEvent* ev) override;
void mouseReleaseEvent(QMouseEvent* ev) override;
void wheelEvent(QWheelEvent* ev) override;
void keyPressEvent(QKeyEvent* ev) override;
void keyReleaseEvent(QKeyEvent* ev) override;
private:
/// This timer is used to redraw the widget's contents continuously. To save resources, it only
/// runs while the widget is visible.
QTimer update_timer;
/// Scale the coordinate system appropriately when dpi != 96.
qreal x_scale = 1.0, y_scale = 1.0;
};
#endif
MicroProfileDialog::MicroProfileDialog(QWidget* parent) : QWidget(parent, Qt::Dialog) {
setObjectName(QStringLiteral("MicroProfile"));
setWindowTitle(tr("&MicroProfile"));
resize(1000, 600);
// Enable the maximize button
setWindowFlags(windowFlags() | Qt::WindowMaximizeButtonHint);
#if MICROPROFILE_ENABLED
MicroProfileWidget* widget = new MicroProfileWidget(this);
QLayout* layout = new QVBoxLayout(this);
layout->setContentsMargins(0, 0, 0, 0);
layout->addWidget(widget);
setLayout(layout);
// Configure focus so that widget is focusable and the dialog automatically forwards focus to
// it.
setFocusProxy(widget);
widget->setFocusPolicy(Qt::StrongFocus);
widget->setFocus();
#endif
}
QAction* MicroProfileDialog::toggleViewAction() {
if (toggle_view_action == nullptr) {
toggle_view_action = new QAction(windowTitle(), this);
toggle_view_action->setCheckable(true);
toggle_view_action->setChecked(isVisible());
connect(toggle_view_action, &QAction::toggled, this, &MicroProfileDialog::setVisible);
}
return toggle_view_action;
}
void MicroProfileDialog::showEvent(QShowEvent* ev) {
if (toggle_view_action) {
toggle_view_action->setChecked(isVisible());
}
QWidget::showEvent(ev);
}
void MicroProfileDialog::hideEvent(QHideEvent* ev) {
if (toggle_view_action) {
toggle_view_action->setChecked(isVisible());
}
QWidget::hideEvent(ev);
}
#if MICROPROFILE_ENABLED
/// There's no way to pass a user pointer to MicroProfile, so this variable is used to make the
/// QPainter available inside the drawing callbacks.
static QPainter* mp_painter = nullptr;
MicroProfileWidget::MicroProfileWidget(QWidget* parent) : QWidget(parent) {
// Send mouse motion events even when not dragging.
setMouseTracking(true);
MicroProfileSetDisplayMode(1); // Timers screen
MicroProfileInitUI();
connect(&update_timer, &QTimer::timeout, this, qOverload<>(&MicroProfileWidget::update));
}
void MicroProfileWidget::paintEvent(QPaintEvent* ev) {
QPainter painter(this);
// The units used by Microprofile for drawing are based in pixels on a 96 dpi display.
x_scale = qreal(painter.device()->logicalDpiX()) / 96.0;
y_scale = qreal(painter.device()->logicalDpiY()) / 96.0;
painter.scale(x_scale, y_scale);
painter.setBackground(Qt::black);
painter.eraseRect(rect());
QFont font = GetMonospaceFont();
font.setPixelSize(MICROPROFILE_TEXT_HEIGHT);
painter.setFont(font);
mp_painter = &painter;
MicroProfileDraw(rect().width() / x_scale, rect().height() / y_scale);
mp_painter = nullptr;
}
void MicroProfileWidget::showEvent(QShowEvent* ev) {
update_timer.start(15); // ~60 Hz
QWidget::showEvent(ev);
}
void MicroProfileWidget::hideEvent(QHideEvent* ev) {
update_timer.stop();
QWidget::hideEvent(ev);
}
void MicroProfileWidget::mouseMoveEvent(QMouseEvent* ev) {
const auto mouse_position = ev->pos();
MicroProfileMousePosition(mouse_position.x() / x_scale, mouse_position.y() / y_scale, 0);
ev->accept();
}
void MicroProfileWidget::mousePressEvent(QMouseEvent* ev) {
const auto mouse_position = ev->pos();
MicroProfileMousePosition(mouse_position.x() / x_scale, mouse_position.y() / y_scale, 0);
MicroProfileMouseButton(ev->buttons() & Qt::LeftButton, ev->buttons() & Qt::RightButton);
ev->accept();
}
void MicroProfileWidget::mouseReleaseEvent(QMouseEvent* ev) {
const auto mouse_position = ev->pos();
MicroProfileMousePosition(mouse_position.x() / x_scale, mouse_position.y() / y_scale, 0);
MicroProfileMouseButton(ev->buttons() & Qt::LeftButton, ev->buttons() & Qt::RightButton);
ev->accept();
}
void MicroProfileWidget::wheelEvent(QWheelEvent* ev) {
const auto wheel_position = ev->position().toPoint();
MicroProfileMousePosition(wheel_position.x() / x_scale, wheel_position.y() / y_scale,
ev->angleDelta().y() / 120);
ev->accept();
}
void MicroProfileWidget::keyPressEvent(QKeyEvent* ev) {
if (ev->key() == Qt::Key_Control) {
// Inform MicroProfile that the user is holding Ctrl.
MicroProfileModKey(1);
}
QWidget::keyPressEvent(ev);
}
void MicroProfileWidget::keyReleaseEvent(QKeyEvent* ev) {
if (ev->key() == Qt::Key_Control) {
MicroProfileModKey(0);
}
QWidget::keyReleaseEvent(ev);
}
// These functions are called by MicroProfileDraw to draw the interface elements on the screen.
void MicroProfileDrawText(int x, int y, u32 hex_color, const char* text, u32 text_length) {
// hex_color does not include an alpha, so it must be assumed to be 255
mp_painter->setPen(QColor::fromRgb(hex_color));
// It's impossible to draw a string using a monospaced font with a fixed width per cell in a
// way that's reliable across different platforms and fonts as far as I (yuriks) can tell, so
// draw each character individually in order to precisely control the text advance.
for (u32 i = 0; i < text_length; ++i) {
// Position the text baseline 1 pixel above the bottom of the text cell, this gives nice
// vertical alignment of text for a wide range of tested fonts.
mp_painter->drawText(x, y + MICROPROFILE_TEXT_HEIGHT - 2, QString{QLatin1Char{text[i]}});
x += MICROPROFILE_TEXT_WIDTH + 1;
}
}
void MicroProfileDrawBox(int left, int top, int right, int bottom, u32 hex_color,
MicroProfileBoxType type) {
QColor color = QColor::fromRgba(hex_color);
QBrush brush = color;
if (type == MicroProfileBoxTypeBar) {
QLinearGradient gradient(left, top, left, bottom);
gradient.setColorAt(0.f, color.lighter(125));
gradient.setColorAt(1.f, color.darker(125));
brush = gradient;
}
mp_painter->fillRect(left, top, right - left, bottom - top, brush);
}
void MicroProfileDrawLine2D(u32 vertices_length, float* vertices, u32 hex_color) {
// Temporary vector used to convert between the float array and QPointF. Marked static to reuse
// the allocation across calls.
static std::vector<QPointF> point_buf;
for (u32 i = 0; i < vertices_length; ++i) {
point_buf.emplace_back(vertices[i * 2 + 0], vertices[i * 2 + 1]);
}
// hex_color does not include an alpha, so it must be assumed to be 255
mp_painter->setPen(QColor::fromRgb(hex_color));
mp_painter->drawPolyline(point_buf.data(), vertices_length);
point_buf.clear();
}
#endif

View File

@@ -0,0 +1,27 @@
// SPDX-FileCopyrightText: 2015 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <QWidget>
class QAction;
class QHideEvent;
class QShowEvent;
class MicroProfileDialog : public QWidget {
Q_OBJECT
public:
explicit MicroProfileDialog(QWidget* parent = nullptr);
/// Returns a QAction that can be used to toggle visibility of this dialog.
QAction* toggleViewAction();
protected:
void showEvent(QShowEvent* ev) override;
void hideEvent(QHideEvent* ev) override;
private:
QAction* toggle_view_action = nullptr;
};

View File

@@ -0,0 +1,431 @@
// SPDX-FileCopyrightText: 2016 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <array>
#include <fmt/format.h>
#include "citron/debugger/wait_tree.h"
#include "citron/uisettings.h"
#include "core/arm/debug.h"
#include "core/core.h"
#include "core/hle/kernel/k_class_token.h"
#include "core/hle/kernel/k_handle_table.h"
#include "core/hle/kernel/k_process.h"
#include "core/hle/kernel/k_readable_event.h"
#include "core/hle/kernel/k_scheduler.h"
#include "core/hle/kernel/k_synchronization_object.h"
#include "core/hle/kernel/k_thread.h"
#include "core/hle/kernel/svc_common.h"
#include "core/hle/kernel/svc_types.h"
#include "core/memory.h"
namespace {
constexpr std::array<std::array<Qt::GlobalColor, 2>, 10> WaitTreeColors{{
{Qt::GlobalColor::darkGreen, Qt::GlobalColor::green},
{Qt::GlobalColor::darkBlue, Qt::GlobalColor::cyan},
{Qt::GlobalColor::lightGray, Qt::GlobalColor::lightGray},
{Qt::GlobalColor::lightGray, Qt::GlobalColor::lightGray},
{Qt::GlobalColor::darkRed, Qt::GlobalColor::red},
{Qt::GlobalColor::darkYellow, Qt::GlobalColor::yellow},
{Qt::GlobalColor::red, Qt::GlobalColor::red},
{Qt::GlobalColor::darkCyan, Qt::GlobalColor::cyan},
{Qt::GlobalColor::gray, Qt::GlobalColor::gray},
}};
bool IsDarkTheme() {
const auto& theme = UISettings::values.theme;
return theme == std::string("qdarkstyle") || theme == std::string("qdarkstyle_midnight_blue") ||
theme == std::string("colorful_dark") || theme == std::string("colorful_midnight_blue");
}
} // namespace
WaitTreeItem::WaitTreeItem() = default;
WaitTreeItem::~WaitTreeItem() = default;
QColor WaitTreeItem::GetColor() const {
if (IsDarkTheme()) {
return QColor(Qt::GlobalColor::white);
} else {
return QColor(Qt::GlobalColor::black);
}
}
std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeItem::GetChildren() const {
return {};
}
void WaitTreeItem::Expand() {
if (IsExpandable() && !expanded) {
children = GetChildren();
for (std::size_t i = 0; i < children.size(); ++i) {
children[i]->parent = this;
children[i]->row = i;
}
expanded = true;
}
}
WaitTreeItem* WaitTreeItem::Parent() const {
return parent;
}
const std::vector<std::unique_ptr<WaitTreeItem>>& WaitTreeItem::Children() const {
return children;
}
bool WaitTreeItem::IsExpandable() const {
return false;
}
std::size_t WaitTreeItem::Row() const {
return row;
}
std::vector<std::unique_ptr<WaitTreeThread>> WaitTreeItem::MakeThreadItemList(
Core::System& system) {
std::vector<std::unique_ptr<WaitTreeThread>> item_list;
std::size_t row = 0;
auto add_threads = [&](const std::vector<Kernel::KThread*>& threads) {
for (std::size_t i = 0; i < threads.size(); ++i) {
if (threads[i]->GetThreadType() == Kernel::ThreadType::User) {
item_list.push_back(std::make_unique<WaitTreeThread>(*threads[i], system));
item_list.back()->row = row;
}
++row;
}
};
add_threads(system.GlobalSchedulerContext().GetThreadList());
return item_list;
}
WaitTreeText::WaitTreeText(QString t) : text(std::move(t)) {}
WaitTreeText::~WaitTreeText() = default;
QString WaitTreeText::GetText() const {
return text;
}
WaitTreeCallstack::WaitTreeCallstack(const Kernel::KThread& thread_, Core::System& system_)
: thread{thread_}, system{system_} {}
WaitTreeCallstack::~WaitTreeCallstack() = default;
QString WaitTreeCallstack::GetText() const {
return tr("Call stack");
}
std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeCallstack::GetChildren() const {
std::vector<std::unique_ptr<WaitTreeItem>> list;
if (thread.GetThreadType() != Kernel::ThreadType::User) {
return list;
}
if (thread.GetOwnerProcess() == nullptr || !thread.GetOwnerProcess()->Is64Bit()) {
return list;
}
auto backtrace = Core::GetBacktraceFromContext(thread.GetOwnerProcess(), thread.GetContext());
for (auto& entry : backtrace) {
std::string s = fmt::format("{:20}{:016X} {:016X} {:016X} {}", entry.module, entry.address,
entry.original_address, entry.offset, entry.name);
list.push_back(std::make_unique<WaitTreeText>(QString::fromStdString(s)));
}
return list;
}
WaitTreeSynchronizationObject::WaitTreeSynchronizationObject(
const Kernel::KSynchronizationObject& object_, Core::System& system_)
: object{object_}, system{system_} {}
WaitTreeSynchronizationObject::~WaitTreeSynchronizationObject() = default;
WaitTreeExpandableItem::WaitTreeExpandableItem() = default;
WaitTreeExpandableItem::~WaitTreeExpandableItem() = default;
bool WaitTreeExpandableItem::IsExpandable() const {
return true;
}
QString WaitTreeSynchronizationObject::GetText() const {
return tr("[%1] %2")
.arg(object.GetId())
.arg(QString::fromStdString(object.GetTypeObj().GetName()));
}
std::unique_ptr<WaitTreeSynchronizationObject> WaitTreeSynchronizationObject::make(
const Kernel::KSynchronizationObject& object, Core::System& system) {
const auto type =
static_cast<Kernel::KClassTokenGenerator::ObjectType>(object.GetTypeObj().GetClassToken());
switch (type) {
case Kernel::KClassTokenGenerator::ObjectType::KReadableEvent:
return std::make_unique<WaitTreeEvent>(static_cast<const Kernel::KReadableEvent&>(object),
system);
case Kernel::KClassTokenGenerator::ObjectType::KThread:
return std::make_unique<WaitTreeThread>(static_cast<const Kernel::KThread&>(object),
system);
default:
return std::make_unique<WaitTreeSynchronizationObject>(object, system);
}
}
std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeSynchronizationObject::GetChildren() const {
std::vector<std::unique_ptr<WaitTreeItem>> list;
auto threads = object.GetWaitingThreadsForDebugging();
if (threads.empty()) {
list.push_back(std::make_unique<WaitTreeText>(tr("waited by no thread")));
} else {
list.push_back(std::make_unique<WaitTreeThreadList>(std::move(threads), system));
}
return list;
}
WaitTreeThread::WaitTreeThread(const Kernel::KThread& thread, Core::System& system_)
: WaitTreeSynchronizationObject(thread, system_), system{system_} {}
WaitTreeThread::~WaitTreeThread() = default;
QString WaitTreeThread::GetText() const {
const auto& thread = static_cast<const Kernel::KThread&>(object);
QString status;
switch (thread.GetState()) {
case Kernel::ThreadState::Runnable:
if (!thread.IsSuspended()) {
status = tr("runnable");
} else {
status = tr("paused");
}
break;
case Kernel::ThreadState::Waiting:
switch (thread.GetWaitReasonForDebugging()) {
case Kernel::ThreadWaitReasonForDebugging::Sleep:
status = tr("sleeping");
break;
case Kernel::ThreadWaitReasonForDebugging::IPC:
status = tr("waiting for IPC reply");
break;
case Kernel::ThreadWaitReasonForDebugging::Synchronization:
status = tr("waiting for objects");
break;
case Kernel::ThreadWaitReasonForDebugging::ConditionVar:
status = tr("waiting for condition variable");
break;
case Kernel::ThreadWaitReasonForDebugging::Arbitration:
status = tr("waiting for address arbiter");
break;
case Kernel::ThreadWaitReasonForDebugging::Suspended:
status = tr("waiting for suspend resume");
break;
default:
status = tr("waiting");
break;
}
break;
case Kernel::ThreadState::Initialized:
status = tr("initialized");
break;
case Kernel::ThreadState::Terminated:
status = tr("terminated");
break;
default:
status = tr("unknown");
break;
}
const auto& context = thread.GetContext();
const QString pc_info = tr(" PC = 0x%1 LR = 0x%2")
.arg(context.pc, 8, 16, QLatin1Char{'0'})
.arg(context.lr, 8, 16, QLatin1Char{'0'});
return QStringLiteral("%1%2 (%3) ")
.arg(WaitTreeSynchronizationObject::GetText(), pc_info, status);
}
QColor WaitTreeThread::GetColor() const {
const std::size_t color_index = IsDarkTheme() ? 1 : 0;
const auto& thread = static_cast<const Kernel::KThread&>(object);
switch (thread.GetState()) {
case Kernel::ThreadState::Runnable:
if (!thread.IsSuspended()) {
return QColor(WaitTreeColors[0][color_index]);
} else {
return QColor(WaitTreeColors[2][color_index]);
}
case Kernel::ThreadState::Waiting:
switch (thread.GetWaitReasonForDebugging()) {
case Kernel::ThreadWaitReasonForDebugging::IPC:
return QColor(WaitTreeColors[4][color_index]);
case Kernel::ThreadWaitReasonForDebugging::Sleep:
return QColor(WaitTreeColors[5][color_index]);
case Kernel::ThreadWaitReasonForDebugging::Synchronization:
case Kernel::ThreadWaitReasonForDebugging::ConditionVar:
case Kernel::ThreadWaitReasonForDebugging::Arbitration:
case Kernel::ThreadWaitReasonForDebugging::Suspended:
return QColor(WaitTreeColors[6][color_index]);
break;
default:
return QColor(WaitTreeColors[3][color_index]);
}
case Kernel::ThreadState::Initialized:
return QColor(WaitTreeColors[7][color_index]);
case Kernel::ThreadState::Terminated:
return QColor(WaitTreeColors[8][color_index]);
default:
return WaitTreeItem::GetColor();
}
}
std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeThread::GetChildren() const {
std::vector<std::unique_ptr<WaitTreeItem>> list(WaitTreeSynchronizationObject::GetChildren());
const auto& thread = static_cast<const Kernel::KThread&>(object);
QString processor;
switch (thread.GetActiveCore()) {
case Kernel::Svc::IdealCoreUseProcessValue:
processor = tr("ideal");
break;
default:
processor = tr("core %1").arg(thread.GetActiveCore());
break;
}
list.push_back(std::make_unique<WaitTreeText>(tr("processor = %1").arg(processor)));
list.push_back(std::make_unique<WaitTreeText>(
tr("affinity mask = %1").arg(thread.GetAffinityMask().GetAffinityMask())));
list.push_back(std::make_unique<WaitTreeText>(tr("thread id = %1").arg(thread.GetThreadId())));
list.push_back(std::make_unique<WaitTreeText>(tr("priority = %1(current) / %2(normal)")
.arg(thread.GetPriority())
.arg(thread.GetBasePriority())));
list.push_back(std::make_unique<WaitTreeText>(
tr("last running ticks = %1").arg(thread.GetLastScheduledTick())));
list.push_back(std::make_unique<WaitTreeCallstack>(thread, system));
return list;
}
WaitTreeEvent::WaitTreeEvent(const Kernel::KReadableEvent& object_, Core::System& system_)
: WaitTreeSynchronizationObject(object_, system_) {}
WaitTreeEvent::~WaitTreeEvent() = default;
WaitTreeThreadList::WaitTreeThreadList(std::vector<Kernel::KThread*>&& list, Core::System& system_)
: thread_list(std::move(list)), system{system_} {}
WaitTreeThreadList::~WaitTreeThreadList() = default;
QString WaitTreeThreadList::GetText() const {
return tr("waited by thread");
}
std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeThreadList::GetChildren() const {
std::vector<std::unique_ptr<WaitTreeItem>> list(thread_list.size());
std::transform(thread_list.begin(), thread_list.end(), list.begin(),
[this](const auto& t) { return std::make_unique<WaitTreeThread>(*t, system); });
return list;
}
WaitTreeModel::WaitTreeModel(Core::System& system_, QObject* parent)
: QAbstractItemModel(parent), system{system_} {}
WaitTreeModel::~WaitTreeModel() = default;
QModelIndex WaitTreeModel::index(int row, int column, const QModelIndex& parent) const {
if (!hasIndex(row, column, parent))
return {};
if (parent.isValid()) {
WaitTreeItem* parent_item = static_cast<WaitTreeItem*>(parent.internalPointer());
parent_item->Expand();
return createIndex(row, column, parent_item->Children()[row].get());
}
return createIndex(row, column, thread_items[row].get());
}
QModelIndex WaitTreeModel::parent(const QModelIndex& index) const {
if (!index.isValid())
return {};
WaitTreeItem* parent_item = static_cast<WaitTreeItem*>(index.internalPointer())->Parent();
if (!parent_item) {
return QModelIndex();
}
return createIndex(static_cast<int>(parent_item->Row()), 0, parent_item);
}
int WaitTreeModel::rowCount(const QModelIndex& parent) const {
if (!parent.isValid())
return static_cast<int>(thread_items.size());
WaitTreeItem* parent_item = static_cast<WaitTreeItem*>(parent.internalPointer());
parent_item->Expand();
return static_cast<int>(parent_item->Children().size());
}
int WaitTreeModel::columnCount(const QModelIndex&) const {
return 1;
}
QVariant WaitTreeModel::data(const QModelIndex& index, int role) const {
if (!index.isValid())
return {};
switch (role) {
case Qt::DisplayRole:
return static_cast<WaitTreeItem*>(index.internalPointer())->GetText();
case Qt::ForegroundRole:
return static_cast<WaitTreeItem*>(index.internalPointer())->GetColor();
default:
return {};
}
}
void WaitTreeModel::ClearItems() {
thread_items.clear();
}
void WaitTreeModel::InitItems() {
thread_items = WaitTreeItem::MakeThreadItemList(system);
}
WaitTreeWidget::WaitTreeWidget(Core::System& system_, QWidget* parent)
: QDockWidget(tr("&Wait Tree"), parent), system{system_} {
setObjectName(QStringLiteral("WaitTreeWidget"));
view = new QTreeView(this);
view->setHeaderHidden(true);
setWidget(view);
setEnabled(false);
}
WaitTreeWidget::~WaitTreeWidget() = default;
void WaitTreeWidget::OnDebugModeEntered() {
if (!system.IsPoweredOn())
return;
model->InitItems();
view->setModel(model);
setEnabled(true);
}
void WaitTreeWidget::OnDebugModeLeft() {
setEnabled(false);
view->setModel(nullptr);
model->ClearItems();
}
void WaitTreeWidget::OnEmulationStarting(EmuThread* emu_thread) {
model = new WaitTreeModel(system, this);
view->setModel(model);
setEnabled(false);
}
void WaitTreeWidget::OnEmulationStopping() {
view->setModel(nullptr);
delete model;
setEnabled(false);
}

View File

@@ -0,0 +1,188 @@
// SPDX-FileCopyrightText: 2016 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <cstddef>
#include <memory>
#include <vector>
#include <QDockWidget>
#include <QTreeView>
#include "common/common_types.h"
#include "core/hle/kernel/k_auto_object.h"
#include "core/hle/kernel/svc_common.h"
class EmuThread;
namespace Core {
class System;
}
namespace Kernel {
class KHandleTable;
class KReadableEvent;
class KSynchronizationObject;
class KThread;
} // namespace Kernel
class WaitTreeThread;
class WaitTreeItem : public QObject {
Q_OBJECT
public:
WaitTreeItem();
~WaitTreeItem() override;
virtual bool IsExpandable() const;
virtual std::vector<std::unique_ptr<WaitTreeItem>> GetChildren() const;
virtual QString GetText() const = 0;
virtual QColor GetColor() const;
void Expand();
WaitTreeItem* Parent() const;
const std::vector<std::unique_ptr<WaitTreeItem>>& Children() const;
std::size_t Row() const;
static std::vector<std::unique_ptr<WaitTreeThread>> MakeThreadItemList(Core::System& system);
private:
std::size_t row;
bool expanded = false;
WaitTreeItem* parent = nullptr;
std::vector<std::unique_ptr<WaitTreeItem>> children;
};
class WaitTreeText : public WaitTreeItem {
Q_OBJECT
public:
explicit WaitTreeText(QString text);
~WaitTreeText() override;
QString GetText() const override;
private:
QString text;
};
class WaitTreeExpandableItem : public WaitTreeItem {
Q_OBJECT
public:
WaitTreeExpandableItem();
~WaitTreeExpandableItem() override;
bool IsExpandable() const override;
};
class WaitTreeCallstack : public WaitTreeExpandableItem {
Q_OBJECT
public:
explicit WaitTreeCallstack(const Kernel::KThread& thread_, Core::System& system_);
~WaitTreeCallstack() override;
QString GetText() const override;
std::vector<std::unique_ptr<WaitTreeItem>> GetChildren() const override;
private:
const Kernel::KThread& thread;
Core::System& system;
};
class WaitTreeSynchronizationObject : public WaitTreeExpandableItem {
Q_OBJECT
public:
explicit WaitTreeSynchronizationObject(const Kernel::KSynchronizationObject& object_,
Core::System& system_);
~WaitTreeSynchronizationObject() override;
static std::unique_ptr<WaitTreeSynchronizationObject> make(
const Kernel::KSynchronizationObject& object, Core::System& system);
QString GetText() const override;
std::vector<std::unique_ptr<WaitTreeItem>> GetChildren() const override;
protected:
const Kernel::KSynchronizationObject& object;
private:
Core::System& system;
};
class WaitTreeThread : public WaitTreeSynchronizationObject {
Q_OBJECT
public:
explicit WaitTreeThread(const Kernel::KThread& thread, Core::System& system_);
~WaitTreeThread() override;
QString GetText() const override;
QColor GetColor() const override;
std::vector<std::unique_ptr<WaitTreeItem>> GetChildren() const override;
private:
Core::System& system;
};
class WaitTreeEvent : public WaitTreeSynchronizationObject {
Q_OBJECT
public:
explicit WaitTreeEvent(const Kernel::KReadableEvent& object_, Core::System& system_);
~WaitTreeEvent() override;
};
class WaitTreeThreadList : public WaitTreeExpandableItem {
Q_OBJECT
public:
explicit WaitTreeThreadList(std::vector<Kernel::KThread*>&& list, Core::System& system_);
~WaitTreeThreadList() override;
QString GetText() const override;
std::vector<std::unique_ptr<WaitTreeItem>> GetChildren() const override;
private:
std::vector<Kernel::KThread*> thread_list;
Core::System& system;
};
class WaitTreeModel : public QAbstractItemModel {
Q_OBJECT
public:
explicit WaitTreeModel(Core::System& system_, QObject* parent = nullptr);
~WaitTreeModel() override;
QVariant data(const QModelIndex& index, int role) const override;
QModelIndex index(int row, int column, const QModelIndex& parent) const override;
QModelIndex parent(const QModelIndex& index) const override;
int rowCount(const QModelIndex& parent) const override;
int columnCount(const QModelIndex& parent) const override;
void ClearItems();
void InitItems();
private:
std::vector<std::unique_ptr<WaitTreeThread>> thread_items;
Core::System& system;
};
class WaitTreeWidget : public QDockWidget {
Q_OBJECT
public:
explicit WaitTreeWidget(Core::System& system_, QWidget* parent = nullptr);
~WaitTreeWidget() override;
public slots:
void OnDebugModeEntered();
void OnDebugModeLeft();
void OnEmulationStarting(EmuThread* emu_thread);
void OnEmulationStopping();
private:
QTreeView* view;
WaitTreeModel* model;
Core::System& system;
};