Merge pull request #4847 from zhaowenlan1779/ipc-debugger
core, citra_qt: IPC Recorder
This commit is contained in:
@@ -90,6 +90,12 @@ add_executable(citra-qt
|
||||
debugger/graphics/graphics_tracing.h
|
||||
debugger/graphics/graphics_vertex_shader.cpp
|
||||
debugger/graphics/graphics_vertex_shader.h
|
||||
debugger/ipc/record_dialog.cpp
|
||||
debugger/ipc/record_dialog.h
|
||||
debugger/ipc/record_dialog.ui
|
||||
debugger/ipc/recorder.cpp
|
||||
debugger/ipc/recorder.h
|
||||
debugger/ipc/recorder.ui
|
||||
debugger/lle_service_modules.cpp
|
||||
debugger/lle_service_modules.h
|
||||
debugger/profiler.cpp
|
||||
|
64
src/citra_qt/debugger/ipc/record_dialog.cpp
Normal file
64
src/citra_qt/debugger/ipc/record_dialog.cpp
Normal file
@@ -0,0 +1,64 @@
|
||||
// Copyright 2019 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <fmt/format.h>
|
||||
#include "citra_qt/debugger/ipc/record_dialog.h"
|
||||
#include "common/assert.h"
|
||||
#include "core/hle/kernel/ipc_debugger/recorder.h"
|
||||
#include "ui_record_dialog.h"
|
||||
|
||||
QString RecordDialog::FormatObject(const IPCDebugger::ObjectInfo& object) const {
|
||||
if (object.id == -1) {
|
||||
return tr("null");
|
||||
}
|
||||
|
||||
return QStringLiteral("%1 (0x%2)")
|
||||
.arg(QString::fromStdString(object.name))
|
||||
.arg(object.id, 8, 16, QLatin1Char('0'));
|
||||
}
|
||||
|
||||
QString RecordDialog::FormatCmdbuf(const std::vector<u32>& cmdbuf) const {
|
||||
QString result;
|
||||
for (std::size_t i = 0; i < cmdbuf.size(); ++i) {
|
||||
result.append(
|
||||
QStringLiteral("[%1]: 0x%2\n").arg(i).arg(cmdbuf[i], 8, 16, QLatin1Char('0')));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
RecordDialog::RecordDialog(QWidget* parent, const IPCDebugger::RequestRecord& record,
|
||||
const QString& service, const QString& function)
|
||||
: QDialog(parent), ui(std::make_unique<Ui::RecordDialog>()) {
|
||||
|
||||
ui->setupUi(this);
|
||||
|
||||
ui->clientProcess->setText(FormatObject(record.client_process));
|
||||
ui->clientThread->setText(FormatObject(record.client_thread));
|
||||
ui->clientSession->setText(FormatObject(record.client_session));
|
||||
|
||||
ui->serverProcess->setText(FormatObject(record.server_process));
|
||||
ui->serverThread->setText(FormatObject(record.server_thread));
|
||||
ui->serverSession->setText(FormatObject(record.server_session));
|
||||
|
||||
ui->clientPort->setText(FormatObject(record.client_port));
|
||||
ui->service->setText(std::move(service));
|
||||
ui->function->setText(std::move(function));
|
||||
|
||||
cmdbufs = {record.untranslated_request_cmdbuf, record.translated_request_cmdbuf,
|
||||
record.untranslated_reply_cmdbuf, record.translated_reply_cmdbuf};
|
||||
|
||||
UpdateCmdbufDisplay();
|
||||
|
||||
connect(ui->cmdbufSelection, qOverload<int>(&QComboBox::currentIndexChanged), this,
|
||||
&RecordDialog::UpdateCmdbufDisplay);
|
||||
}
|
||||
|
||||
RecordDialog::~RecordDialog() = default;
|
||||
|
||||
void RecordDialog::UpdateCmdbufDisplay() {
|
||||
int index = ui->cmdbufSelection->currentIndex();
|
||||
|
||||
ASSERT_MSG(0 <= index && index <= 3, "Index out of bound");
|
||||
ui->cmdbuf->setPlainText(FormatCmdbuf(cmdbufs[index]));
|
||||
}
|
36
src/citra_qt/debugger/ipc/record_dialog.h
Normal file
36
src/citra_qt/debugger/ipc/record_dialog.h
Normal file
@@ -0,0 +1,36 @@
|
||||
// Copyright 2019 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <QDialog>
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace IPCDebugger {
|
||||
struct ObjectInfo;
|
||||
struct RequestRecord;
|
||||
} // namespace IPCDebugger
|
||||
|
||||
namespace Ui {
|
||||
class RecordDialog;
|
||||
}
|
||||
|
||||
class RecordDialog : public QDialog {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit RecordDialog(QWidget* parent, const IPCDebugger::RequestRecord& record,
|
||||
const QString& service, const QString& function);
|
||||
~RecordDialog() override;
|
||||
|
||||
private:
|
||||
QString FormatObject(const IPCDebugger::ObjectInfo& object) const;
|
||||
QString FormatCmdbuf(const std::vector<u32>& cmdbuf) const;
|
||||
void UpdateCmdbufDisplay();
|
||||
|
||||
std::unique_ptr<Ui::RecordDialog> ui;
|
||||
std::array<std::vector<u32>, 4> cmdbufs;
|
||||
};
|
245
src/citra_qt/debugger/ipc/record_dialog.ui
Normal file
245
src/citra_qt/debugger/ipc/record_dialog.ui
Normal file
@@ -0,0 +1,245 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>RecordDialog</class>
|
||||
<widget class="QDialog" name="RecordDialog">
|
||||
<property name="windowTitle">
|
||||
<string>View Record</string>
|
||||
</property>
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>800</width>
|
||||
<height>500</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QVBoxLayout">
|
||||
<item>
|
||||
<layout class="QHBoxLayout">
|
||||
<item>
|
||||
<widget class="QGroupBox">
|
||||
<property name="title">
|
||||
<string>Client</string>
|
||||
</property>
|
||||
<layout class="QFormLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel">
|
||||
<property name="text">
|
||||
<string>Process:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLineEdit" name="clientProcess">
|
||||
<property name="readOnly">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel">
|
||||
<property name="text">
|
||||
<string>Thread:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLineEdit" name="clientThread">
|
||||
<property name="readOnly">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel">
|
||||
<property name="text">
|
||||
<string>Session:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QLineEdit" name="clientSession">
|
||||
<property name="readOnly">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox">
|
||||
<property name="title">
|
||||
<string>Server</string>
|
||||
</property>
|
||||
<layout class="QFormLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel">
|
||||
<property name="text">
|
||||
<string>Process:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLineEdit" name="serverProcess">
|
||||
<property name="readOnly">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel">
|
||||
<property name="text">
|
||||
<string>Thread:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLineEdit" name="serverThread">
|
||||
<property name="readOnly">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel">
|
||||
<property name="text">
|
||||
<string>Session:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QLineEdit" name="serverSession">
|
||||
<property name="readOnly">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox">
|
||||
<property name="title">
|
||||
<string>General</string>
|
||||
</property>
|
||||
<layout class="QFormLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel">
|
||||
<property name="text">
|
||||
<string>Client Port:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLineEdit" name="clientPort">
|
||||
<property name="readOnly">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel">
|
||||
<property name="text">
|
||||
<string>Service:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLineEdit" name="service">
|
||||
<property name="readOnly">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel">
|
||||
<property name="text">
|
||||
<string>Function:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QLineEdit" name="function">
|
||||
<property name="readOnly">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox">
|
||||
<property name="title">
|
||||
<string>Command Buffer</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout">
|
||||
<item>
|
||||
<layout class="QHBoxLayout">
|
||||
<item>
|
||||
<widget class="QLabel">
|
||||
<property name="text">
|
||||
<string>Select:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QComboBox" name="cmdbufSelection">
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Request Untranslated</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Request Translated</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Reply Untranslated</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Reply Translated</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPlainTextEdit" name="cmdbuf">
|
||||
<property name="readOnly">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout">
|
||||
<item>
|
||||
<spacer>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="okButton">
|
||||
<property name="text">
|
||||
<string>OK</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</ui>
|
183
src/citra_qt/debugger/ipc/recorder.cpp
Normal file
183
src/citra_qt/debugger/ipc/recorder.cpp
Normal file
@@ -0,0 +1,183 @@
|
||||
// Copyright 2019 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <QString>
|
||||
#include <QTreeWidgetItem>
|
||||
#include <fmt/format.h>
|
||||
#include "citra_qt/debugger/ipc/record_dialog.h"
|
||||
#include "citra_qt/debugger/ipc/recorder.h"
|
||||
#include "common/assert.h"
|
||||
#include "common/string_util.h"
|
||||
#include "core/core.h"
|
||||
#include "core/hle/kernel/ipc_debugger/recorder.h"
|
||||
#include "core/hle/kernel/kernel.h"
|
||||
#include "core/hle/service/sm/sm.h"
|
||||
#include "ui_recorder.h"
|
||||
|
||||
IPCRecorderWidget::IPCRecorderWidget(QWidget* parent)
|
||||
: QDockWidget(parent), ui(std::make_unique<Ui::IPCRecorder>()) {
|
||||
|
||||
ui->setupUi(this);
|
||||
qRegisterMetaType<IPCDebugger::RequestRecord>();
|
||||
|
||||
connect(ui->enabled, &QCheckBox::stateChanged,
|
||||
[this](int new_state) { SetEnabled(new_state == Qt::Checked); });
|
||||
connect(ui->clearButton, &QPushButton::clicked, this, &IPCRecorderWidget::Clear);
|
||||
connect(ui->filter, &QLineEdit::textChanged, this, &IPCRecorderWidget::ApplyFilterToAll);
|
||||
connect(ui->main, &QTreeWidget::itemDoubleClicked, this, &IPCRecorderWidget::OpenRecordDialog);
|
||||
connect(this, &IPCRecorderWidget::EntryUpdated, this, &IPCRecorderWidget::OnEntryUpdated);
|
||||
}
|
||||
|
||||
IPCRecorderWidget::~IPCRecorderWidget() = default;
|
||||
|
||||
void IPCRecorderWidget::OnEmulationStarting() {
|
||||
Clear();
|
||||
id_offset = 1;
|
||||
|
||||
// Update the enabled status when the system is powered on.
|
||||
SetEnabled(ui->enabled->isChecked());
|
||||
}
|
||||
|
||||
QString IPCRecorderWidget::GetStatusStr(const IPCDebugger::RequestRecord& record) const {
|
||||
switch (record.status) {
|
||||
case IPCDebugger::RequestStatus::Invalid:
|
||||
return tr("Invalid");
|
||||
case IPCDebugger::RequestStatus::Sent:
|
||||
return tr("Sent");
|
||||
case IPCDebugger::RequestStatus::Handling:
|
||||
return tr("Handling");
|
||||
case IPCDebugger::RequestStatus::Handled:
|
||||
if (record.translated_reply_cmdbuf[1] == RESULT_SUCCESS.raw) {
|
||||
return tr("Success");
|
||||
}
|
||||
return tr("Error");
|
||||
case IPCDebugger::RequestStatus::HLEUnimplemented:
|
||||
return tr("HLE Unimplemented");
|
||||
default:
|
||||
UNREACHABLE();
|
||||
}
|
||||
}
|
||||
|
||||
void IPCRecorderWidget::OnEntryUpdated(IPCDebugger::RequestRecord record) {
|
||||
if (record.id < id_offset) { // The record has already been deleted by 'Clear'
|
||||
return;
|
||||
}
|
||||
|
||||
QString service = GetServiceName(record);
|
||||
if (record.status == IPCDebugger::RequestStatus::Handling ||
|
||||
record.status == IPCDebugger::RequestStatus::Handled ||
|
||||
record.status == IPCDebugger::RequestStatus::HLEUnimplemented) {
|
||||
|
||||
service = QStringLiteral("%1 (%2)").arg(service, record.is_hle ? tr("HLE") : tr("LLE"));
|
||||
}
|
||||
|
||||
QTreeWidgetItem item{
|
||||
{QString::number(record.id), GetStatusStr(record), service, GetFunctionName(record)}};
|
||||
|
||||
const int row_id = record.id - id_offset;
|
||||
if (ui->main->invisibleRootItem()->childCount() > row_id) {
|
||||
records[row_id] = record;
|
||||
(*ui->main->invisibleRootItem()->child(row_id)) = item;
|
||||
} else {
|
||||
records.emplace_back(record);
|
||||
ui->main->invisibleRootItem()->addChild(new QTreeWidgetItem(item));
|
||||
}
|
||||
|
||||
if (record.status == IPCDebugger::RequestStatus::HLEUnimplemented ||
|
||||
(record.status == IPCDebugger::RequestStatus::Handled &&
|
||||
record.translated_reply_cmdbuf[1] != RESULT_SUCCESS.raw)) { // Unimplemented / Error
|
||||
|
||||
auto* item = ui->main->invisibleRootItem()->child(row_id);
|
||||
for (int column = 0; column < item->columnCount(); ++column) {
|
||||
item->setBackgroundColor(column, QColor::fromRgb(255, 0, 0));
|
||||
}
|
||||
}
|
||||
|
||||
ApplyFilter(row_id);
|
||||
}
|
||||
|
||||
void IPCRecorderWidget::SetEnabled(bool enabled) {
|
||||
if (!Core::System::GetInstance().IsPoweredOn()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto& ipc_recorder = Core::System::GetInstance().Kernel().GetIPCRecorder();
|
||||
ipc_recorder.SetEnabled(enabled);
|
||||
|
||||
if (enabled) {
|
||||
handle = ipc_recorder.BindCallback(
|
||||
[this](const IPCDebugger::RequestRecord& record) { emit EntryUpdated(record); });
|
||||
} else if (handle) {
|
||||
ipc_recorder.UnbindCallback(handle);
|
||||
}
|
||||
}
|
||||
|
||||
void IPCRecorderWidget::Clear() {
|
||||
id_offset = records.size() + 1;
|
||||
|
||||
records.clear();
|
||||
ui->main->invisibleRootItem()->takeChildren();
|
||||
}
|
||||
|
||||
QString IPCRecorderWidget::GetServiceName(const IPCDebugger::RequestRecord& record) const {
|
||||
if (Core::System::GetInstance().IsPoweredOn() && record.client_port.id != -1) {
|
||||
const auto service_name =
|
||||
Core::System::GetInstance().ServiceManager().GetServiceNameByPortId(
|
||||
static_cast<u32>(record.client_port.id));
|
||||
|
||||
if (!service_name.empty()) {
|
||||
return QString::fromStdString(service_name);
|
||||
}
|
||||
}
|
||||
|
||||
// Get a similar result from the server session name
|
||||
std::string session_name = record.server_session.name;
|
||||
session_name = Common::ReplaceAll(session_name, "_Server", "");
|
||||
session_name = Common::ReplaceAll(session_name, "_Client", "");
|
||||
return QString::fromStdString(session_name);
|
||||
}
|
||||
|
||||
QString IPCRecorderWidget::GetFunctionName(const IPCDebugger::RequestRecord& record) const {
|
||||
if (record.untranslated_request_cmdbuf.empty()) { // Cmdbuf is not yet available
|
||||
return tr("Unknown");
|
||||
}
|
||||
const QString header_code =
|
||||
QStringLiteral("0x%1").arg(record.untranslated_request_cmdbuf[0], 8, 16, QLatin1Char('0'));
|
||||
if (record.function_name.empty()) {
|
||||
return header_code;
|
||||
}
|
||||
return QStringLiteral("%1 (%2)").arg(QString::fromStdString(record.function_name), header_code);
|
||||
}
|
||||
|
||||
void IPCRecorderWidget::ApplyFilter(int index) {
|
||||
auto* item = ui->main->invisibleRootItem()->child(index);
|
||||
const QString filter = ui->filter->text();
|
||||
if (filter.isEmpty()) {
|
||||
item->setHidden(false);
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < item->columnCount(); ++i) {
|
||||
if (item->text(i).contains(filter)) {
|
||||
item->setHidden(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
item->setHidden(true);
|
||||
}
|
||||
|
||||
void IPCRecorderWidget::ApplyFilterToAll() {
|
||||
for (int i = 0; i < ui->main->invisibleRootItem()->childCount(); ++i) {
|
||||
ApplyFilter(i);
|
||||
}
|
||||
}
|
||||
|
||||
void IPCRecorderWidget::OpenRecordDialog(QTreeWidgetItem* item, [[maybe_unused]] int column) {
|
||||
int index = ui->main->invisibleRootItem()->indexOfChild(item);
|
||||
|
||||
RecordDialog dialog(this, records[static_cast<std::size_t>(index)], item->text(2),
|
||||
item->text(3));
|
||||
dialog.exec();
|
||||
}
|
52
src/citra_qt/debugger/ipc/recorder.h
Normal file
52
src/citra_qt/debugger/ipc/recorder.h
Normal file
@@ -0,0 +1,52 @@
|
||||
// Copyright 2019 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
#include <QDockWidget>
|
||||
#include "core/hle/kernel/ipc_debugger/recorder.h"
|
||||
|
||||
class QTreeWidgetItem;
|
||||
|
||||
namespace Ui {
|
||||
class IPCRecorder;
|
||||
}
|
||||
|
||||
class IPCRecorderWidget : public QDockWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit IPCRecorderWidget(QWidget* parent = nullptr);
|
||||
~IPCRecorderWidget();
|
||||
|
||||
void OnEmulationStarting();
|
||||
|
||||
signals:
|
||||
void EntryUpdated(IPCDebugger::RequestRecord record);
|
||||
|
||||
private:
|
||||
QString GetStatusStr(const IPCDebugger::RequestRecord& record) const;
|
||||
void OnEntryUpdated(IPCDebugger::RequestRecord record);
|
||||
void SetEnabled(bool enabled);
|
||||
void Clear();
|
||||
void ApplyFilter(int index);
|
||||
void ApplyFilterToAll();
|
||||
QString GetServiceName(const IPCDebugger::RequestRecord& record) const;
|
||||
QString GetFunctionName(const IPCDebugger::RequestRecord& record) const;
|
||||
void OpenRecordDialog(QTreeWidgetItem* item, int column);
|
||||
|
||||
std::unique_ptr<Ui::IPCRecorder> ui;
|
||||
IPCDebugger::CallbackHandle handle;
|
||||
|
||||
// The offset between record id and row id, assuming record ids are assigned
|
||||
// continuously and only the 'Clear' action can be performed, this is enough.
|
||||
// The initial value is 1, which means record 1 = row 0.
|
||||
int id_offset = 1;
|
||||
std::vector<IPCDebugger::RequestRecord> records;
|
||||
};
|
||||
|
||||
Q_DECLARE_METATYPE(IPCDebugger::RequestRecord);
|
93
src/citra_qt/debugger/ipc/recorder.ui
Normal file
93
src/citra_qt/debugger/ipc/recorder.ui
Normal file
@@ -0,0 +1,93 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>IPCRecorder</class>
|
||||
<widget class="QDockWidget" name="IPCRecorder">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>600</width>
|
||||
<height>300</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>IPC Recorder</string>
|
||||
</property>
|
||||
<widget class="QWidget">
|
||||
<layout class="QVBoxLayout">
|
||||
<item>
|
||||
<widget class="QCheckBox" name="enabled">
|
||||
<property name="text">
|
||||
<string>Enable Recording</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout">
|
||||
<item>
|
||||
<widget class="QLabel">
|
||||
<property name="text">
|
||||
<string>Filter:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="filter">
|
||||
<property name="placeholderText">
|
||||
<string>Leave empty to disable filtering</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QTreeWidget" name="main">
|
||||
<property name="alternatingRowColors">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>#</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Status</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Service</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Function</string>
|
||||
</property>
|
||||
</column>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout">
|
||||
<item>
|
||||
<spacer>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="clearButton">
|
||||
<property name="text">
|
||||
<string>Clear</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
@@ -40,6 +40,7 @@
|
||||
#include "citra_qt/debugger/graphics/graphics_surface.h"
|
||||
#include "citra_qt/debugger/graphics/graphics_tracing.h"
|
||||
#include "citra_qt/debugger/graphics/graphics_vertex_shader.h"
|
||||
#include "citra_qt/debugger/ipc/recorder.h"
|
||||
#include "citra_qt/debugger/lle_service_modules.h"
|
||||
#include "citra_qt/debugger/profiler.h"
|
||||
#include "citra_qt/debugger/registers.h"
|
||||
@@ -339,6 +340,13 @@ void GMainWindow::InitializeDebugWidgets() {
|
||||
[this] { lleServiceModulesWidget->setDisabled(true); });
|
||||
connect(this, &GMainWindow::EmulationStopping, waitTreeWidget,
|
||||
[this] { lleServiceModulesWidget->setDisabled(false); });
|
||||
|
||||
ipcRecorderWidget = new IPCRecorderWidget(this);
|
||||
addDockWidget(Qt::RightDockWidgetArea, ipcRecorderWidget);
|
||||
ipcRecorderWidget->hide();
|
||||
debug_menu->addAction(ipcRecorderWidget->toggleViewAction());
|
||||
connect(this, &GMainWindow::EmulationStarting, ipcRecorderWidget,
|
||||
&IPCRecorderWidget::OnEmulationStarting);
|
||||
}
|
||||
|
||||
void GMainWindow::InitializeRecentFileMenuActions() {
|
||||
|
@@ -30,6 +30,7 @@ class GraphicsBreakPointsWidget;
|
||||
class GraphicsTracingWidget;
|
||||
class GraphicsVertexShaderWidget;
|
||||
class GRenderWindow;
|
||||
class IPCRecorderWidget;
|
||||
class LLEServiceModulesWidget;
|
||||
class MicroProfileDialog;
|
||||
class MultiplayerState;
|
||||
@@ -254,6 +255,7 @@ private:
|
||||
GraphicsBreakPointsWidget* graphicsBreakpointsWidget;
|
||||
GraphicsVertexShaderWidget* graphicsVertexShaderWidget;
|
||||
GraphicsTracingWidget* graphicsTracingWidget;
|
||||
IPCRecorderWidget* ipcRecorderWidget;
|
||||
LLEServiceModulesWidget* lleServiceModulesWidget;
|
||||
WaitTreeWidget* waitTreeWidget;
|
||||
Updater* updater;
|
||||
|
Reference in New Issue
Block a user