Initializes GUI for VMM (#890)

This commit is contained in:
Putta Khunchalee 2024-07-15 02:52:33 +07:00 committed by GitHub
parent 98981ef1f9
commit b93fde21d0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 229 additions and 252 deletions

View File

@ -36,6 +36,7 @@ add_executable(obliteration WIN32 MACOSX_BUNDLE
game_settings.cpp
game_settings_dialog.cpp
initialize_wizard.cpp
launch_settings.cpp
log_formatter.cpp
main.cpp
main_window.cpp

View File

@ -9,6 +9,7 @@
struct error;
struct param;
struct pkg;
struct vmm;
typedef void (*pkg_extract_status_t) (const char *status, std::size_t bar, std::uint64_t current, std::uint64_t total, void *ud);
@ -39,6 +40,9 @@ extern "C" {
bool explicit_decryption,
void (*status) (const char *, std::uint64_t, std::uint64_t, void *),
void *ud);
vmm *vmm_new();
void vmm_free(vmm *vmm);
}
class Error final {
@ -209,3 +213,26 @@ public:
private:
pkg *m_obj;
};
class Vmm final {
public:
Vmm() : m_obj(nullptr) {}
Vmm(const Vmm &) = delete;
~Vmm() { kill(); }
public:
Vmm &operator=(const Vmm &) = delete;
operator bool() const { return m_obj != nullptr; }
public:
void kill()
{
if (m_obj) {
vmm_free(m_obj);
m_obj = nullptr;
}
}
private:
vmm *m_obj;
};

View File

@ -4,3 +4,4 @@ mod ffi;
mod fwdl;
mod param;
mod pkg;
mod vmm;

14
src/core/src/vmm/mod.rs Normal file
View File

@ -0,0 +1,14 @@
#[no_mangle]
pub extern "C-unwind" fn vmm_new() -> *mut Vmm {
let vmm = Vmm {};
Box::into_raw(vmm.into())
}
#[no_mangle]
pub unsafe extern "C-unwind" fn vmm_free(vmm: *mut Vmm) {
drop(Box::from_raw(vmm));
}
/// Manage a virtual machine that run the kernel.
pub struct Vmm {}

View File

@ -1,5 +1,6 @@
#include "game_models.hpp"
#include "path.hpp"
#include <QFile>
Game::Game(const QString &id, const QString &name, const QString &directory) :
@ -7,38 +8,21 @@ Game::Game(const QString &id, const QString &name, const QString &directory) :
m_name(name),
m_directory(directory)
{
// Load icon.
auto dir = joinPath(directory, "sce_sys");
auto path = joinPath(dir.c_str(), "icon0.png");
QPixmap icon(QFile::exists(path.c_str()) ? path.c_str() : ":/resources/fallbackicon0.png");
icon.setDevicePixelRatio(2.0);
// Scale down.
m_icon = icon.scaled(64, 64, Qt::KeepAspectRatio, Qt::SmoothTransformation);
}
Game::~Game()
{
}
QPixmap Game::icon() const {
if (!m_cachedIcon.isNull()) {
return m_cachedIcon;
}
// Get icon path.
auto dir = joinPath(m_directory, "sce_sys");
auto path = joinPath(dir.c_str(), "icon0.png");
if (QFile::exists(path.c_str())) {
m_cachedIcon.load(path.c_str());
} else {
// Load fallback icon if icon0 doesn't exist.
m_cachedIcon.load(":/resources/fallbackicon0.png");
}
// For games with large icon sizes.
if (m_cachedIcon.width() != 512 || m_cachedIcon.height() != 512) {
m_cachedIcon = m_cachedIcon.scaled(512, 512, Qt::KeepAspectRatio, Qt::SmoothTransformation);
}
m_cachedIcon.setDevicePixelRatio(2.0);
return m_cachedIcon;
}
GameListModel::GameListModel(QObject *parent) :
QAbstractListModel(parent)
{
@ -72,38 +56,83 @@ void GameListModel::clear()
endResetModel();
}
int GameListModel::columnCount(const QModelIndex &) const
{
return 2;
}
int GameListModel::rowCount(const QModelIndex &) const
{
return m_items.size();
}
QVariant GameListModel::headerData(int section, Qt::Orientation orientation, int role) const
{
if (role != Qt::DisplayRole) {
return {};
} else if (orientation == Qt::Vertical) {
return section + 1;
} else if (orientation != Qt::Horizontal) {
return {};
}
switch (section) {
case 0:
return "Name";
case 1:
return "ID";
default:
return {};
}
}
QVariant GameListModel::data(const QModelIndex &index, int role) const
{
switch (role) {
case Qt::DisplayRole:
return m_items[index.row()]->name();
case Qt::DecorationRole:
return m_items[index.row()]->icon();
default:
return QVariant();
auto game = m_items[index.row()];
switch (index.column()) {
case 0:
switch (role) {
case Qt::DisplayRole:
return game->name();
case Qt::DecorationRole:
return game->icon();
}
break;
case 1:
if (role == Qt::DisplayRole) {
return game->id();
}
break;
}
return {};
}
void GameListModel::sort(int column, Qt::SortOrder order)
{
if (column != 0)
return;
emit layoutAboutToBeChanged();
auto compare = [order](const Game* a, const Game* b) {
if (order == Qt::AscendingOrder)
return a->name().toLower() < b->name().toLower();
else
return a->name().toLower() > b->name().toLower();
};
std::sort(m_items.begin(), m_items.end(), compare);
switch (column) {
case 0:
std::sort(m_items.begin(), m_items.end(), [order](const Game *a, const Game *b) {
if (order == Qt::AscendingOrder) {
return a->name().toUpper() < b->name().toUpper();
} else {
return a->name().toUpper() > b->name().toUpper();
}
});
break;
case 1:
std::sort(m_items.begin(), m_items.end(), [order](const Game *a, const Game *b) {
if (order == Qt::AscendingOrder) {
return a->id() < b->id();
} else {
return a->id() > b->id();
}
});
break;
}
emit layoutChanged();
}

View File

@ -13,19 +13,19 @@ public:
const QString &id() const { return m_id; }
const QString &name() const { return m_name; }
const QString &directory() const { return m_directory; }
QPixmap icon() const;
const QPixmap &icon() const { return m_icon; }
private:
QString m_id;
QString m_name;
QString m_directory;
mutable QPixmap m_cachedIcon;
QPixmap m_icon;
};
class GameListModel final : public QAbstractListModel {
public:
GameListModel(QObject *parent = nullptr);
~GameListModel();
~GameListModel() override;
public:
void add(Game *game);
@ -34,7 +34,9 @@ public:
void sort(int column, Qt::SortOrder order = Qt::AscendingOrder) override;
public:
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
private:

23
src/launch_settings.cpp Normal file
View File

@ -0,0 +1,23 @@
#include "launch_settings.hpp"
#include <QPushButton>
#include <QVBoxLayout>
LaunchSettings::LaunchSettings(QWidget *parent) :
QWidget(parent)
{
auto layout = new QVBoxLayout();
// Start button.
auto start = new QPushButton("Start");
connect(start, &QAbstractButton::clicked, [this]() { emit startClicked(); });
layout->addWidget(start);
setLayout(layout);
}
LaunchSettings::~LaunchSettings()
{
}

13
src/launch_settings.hpp Normal file
View File

@ -0,0 +1,13 @@
#pragma once
#include <QWidget>
class LaunchSettings final : public QWidget {
Q_OBJECT
public:
LaunchSettings(QWidget *parent = nullptr);
~LaunchSettings() override;
signals:
void startClicked();
};

View File

@ -1,14 +1,13 @@
#include "main_window.hpp"
#include "app_data.hpp"
#include "core.hpp"
#include "game_models.hpp"
#include "game_settings.hpp"
#include "game_settings_dialog.hpp"
#include "launch_settings.hpp"
#include "log_formatter.hpp"
#include "path.hpp"
#include "pkg_installer.hpp"
#include "settings.hpp"
#include "string.hpp"
#include <QAction>
#include <QApplication>
@ -18,8 +17,8 @@
#include <QDir>
#include <QFile>
#include <QFileDialog>
#include <QHeaderView>
#include <QIcon>
#include <QListView>
#include <QMenu>
#include <QMenuBar>
#include <QMessageBox>
@ -28,7 +27,9 @@
#include <QResizeEvent>
#include <QScrollBar>
#include <QSettings>
#include <QStackedWidget>
#include <QStyleHints>
#include <QTableView>
#include <QTabWidget>
#include <QToolBar>
#include <QUrl>
@ -37,9 +38,10 @@
MainWindow::MainWindow() :
m_tab(nullptr),
m_screen(nullptr),
m_launch(nullptr),
m_games(nullptr),
m_log(nullptr),
m_kernel(nullptr)
m_log(nullptr)
{
setWindowTitle("Obliteration");
@ -87,41 +89,37 @@ MainWindow::MainWindow() :
helpMenu->addAction(aboutQt);
helpMenu->addAction(about);
#ifndef __APPLE__
// File toolbar.
auto fileBar = addToolBar("&File");
fileBar->setMovable(false);
fileBar->addAction(installPkg);
#endif
// Central widget.
m_tab = new QTabWidget(this);
m_tab->setDocumentMode(true);
#ifdef __APPLE__
m_tab->tabBar()->setExpanding(true);
#endif
setCentralWidget(m_tab);
// Setup screen tab.
m_screen = new QStackedWidget();
m_tab->addTab(m_screen, QIcon(svgPath + "monitor.svg"), "Screen");
// Setup launch settings.
m_launch = new LaunchSettings();
connect(m_launch, &LaunchSettings::startClicked, this, &MainWindow::startKernel);
m_screen->addWidget(m_launch);
// Setup game list.
m_games = new QListView();
m_games = new QTableView();
m_games->setContextMenuPolicy(Qt::CustomContextMenu);
m_games->setLayoutMode(QListView::SinglePass);
m_games->setSortingEnabled(true);
m_games->setWordWrap(false);
m_games->verticalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents);
m_games->setModel(new GameListModel(this));
m_games->setViewMode(QListView::IconMode);
m_games->setWordWrap(true);
m_games->verticalScrollBar()->setSingleStep(20);
connect(m_games, &QAbstractItemView::doubleClicked, this, &MainWindow::startGame);
connect(m_games, &QWidget::customContextMenuRequested, this, &MainWindow::requestGamesContextMenu);
m_tab->addTab(m_games, QIcon(svgPath + "view-comfy.svg"), "Games");
connect(m_tab, &QTabWidget::currentChanged, this, &MainWindow::tabChanged);
// Setup log view.
auto log = new QPlainTextEdit();
@ -169,7 +167,6 @@ bool MainWindow::loadGames()
// Load games
progress.setLabelText("Loading games...");
auto gameList = reinterpret_cast<GameListModel *>(m_games->model());
for (auto &gameId : games) {
if (progress.wasCanceled() || !loadGame(gameId)) {
@ -179,7 +176,10 @@ bool MainWindow::loadGames()
progress.setValue(++step);
}
gameList->sort(0, Qt::AscendingOrder); // TODO add ability to select descending order (button?)
// Update widgets.
m_games->horizontalHeader()->setSortIndicator(0, Qt::AscendingOrder);
m_games->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Stretch);
m_games->horizontalHeader()->setSectionResizeMode(1, QHeaderView::ResizeToContents);
return true;
}
@ -201,7 +201,7 @@ void MainWindow::closeEvent(QCloseEvent *event)
return;
}
killKernel();
m_kernel.kill();
}
// Save geometry.
@ -220,26 +220,6 @@ void MainWindow::closeEvent(QCloseEvent *event)
QMainWindow::closeEvent(event);
}
void MainWindow::resizeEvent(QResizeEvent *event)
{
// Allows the games list to resort if window is resized.
if (m_tab->currentIndex() == 0) {
m_games->updateGeometry();
m_games->doItemsLayout();
}
QMainWindow::resizeEvent(event);
}
void MainWindow::tabChanged()
{
// Update games list if window was resized on another tab.
if (m_tab->currentIndex() == 0) {
m_games->updateGeometry();
m_games->doItemsLayout();
}
}
void MainWindow::installPkg()
{
// Browse a PKG.
@ -341,19 +321,17 @@ void MainWindow::requestGamesContextMenu(const QPoint &pos)
}
}
void MainWindow::startGame(const QModelIndex &index)
void MainWindow::startKernel()
{
if (requireEmulatorStopped()) {
// Just switch to screen tab if currently running.
if (m_kernel) {
m_tab->setCurrentIndex(0);
return;
}
// Get target game.
auto model = reinterpret_cast<GameListModel *>(m_games->model());
auto game = model->get(index.row()); // Qt already guaranteed the index is valid.
// Clear previous log and switch to log view.
// Clear previous log and switch to screen tab.
m_log->reset();
m_tab->setCurrentIndex(1);
m_tab->setCurrentIndex(0);
// Get full path to kernel binary.
QString path;
@ -361,117 +339,32 @@ void MainWindow::startGame(const QModelIndex &index)
if (QFile::exists(".obliteration-development")) {
auto b = std::filesystem::current_path();
b /= STR("src");
b /= STR("target");
#ifdef NDEBUG
b /= STR("release");
#if defined(_WIN32) && defined(NDEBUG)
path = QString::fromStdWString((b / L"src" / L"target" / L"x86_64-unknown-none" / L"release" / L"obkrnl").wstring());
#elif defined(_WIN32) && !defined(NDEBUG)
path = QString::fromStdWString((b / L"src" / L"target" / L"x86_64-unknown-none" / L"debug" / L"obkrnl").wstring());
#elif defined(NDEBUG)
path = QString::fromStdString((b / "src" / "target" / "x86_64-unknown-none" / "release" / "obkrnl").string());
#else
b /= STR("debug");
#endif
#ifdef _WIN32
b /= L"obkrnl.exe";
path = QString::fromStdWString(b.wstring());
#else
b /= "obkrnl";
path = QString::fromStdString(b.string());
path = QString::fromStdString((b / "src" / "target" / "x86_64-unknown-none" / "debug" / "obkrnl").string());
#endif
} else {
#ifdef _WIN32
std::filesystem::path b(QCoreApplication::applicationDirPath().toStdString(), std::filesystem::path::native_format);
b /= L"bin";
b /= L"obkrnl.exe";
b /= L"share";
b /= L"obkrnl";
path = QString::fromStdWString(b.wstring());
#else
std::filesystem::path b(QCoreApplication::applicationDirPath().toStdString(), std::filesystem::path::native_format);
auto b = std::filesystem::path(QCoreApplication::applicationDirPath().toStdString(), std::filesystem::path::native_format).parent_path();
#ifdef __APPLE__
b /= "Resources";
#else
b /= "share";
#endif
b /= "obkrnl";
path = QString::fromStdString(b.string());
#endif
}
// Setup kernel arguments.
QStringList args;
args << "--debug-dump" << kernelDebugDump();
args << "--clear-debug-dump";
args << readSystemDirectorySetting();
args << game->directory();
// Setup environment variable.
auto env = QProcessEnvironment::systemEnvironment();
env.insert("TERM", "xterm");
// Prepare kernel launching.
m_kernel = new QProcess(this);
m_kernel->setProgram(path);
m_kernel->setArguments(args);
m_kernel->setProcessEnvironment(env);
m_kernel->setProcessChannelMode(QProcess::MergedChannels);
connect(m_kernel, &QProcess::errorOccurred, this, &MainWindow::kernelError);
connect(m_kernel, &QIODevice::readyRead, this, &MainWindow::kernelOutput);
connect(m_kernel, &QProcess::finished, this, &MainWindow::kernelTerminated);
// Launch kernel.
m_kernel->start(QIODeviceBase::ReadOnly | QIODeviceBase::Text);
}
void MainWindow::kernelError(QProcess::ProcessError error)
{
// Get error message.
QString msg;
switch (error) {
case QProcess::FailedToStart:
msg = QString("Failed to launch %1.").arg(m_kernel->program());
break;
case QProcess::Crashed:
msg = "The kernel crashed.";
break;
default:
msg = "The kernel encountered an unknown error.";
}
// Flush the kenel log before we destroy its object.
kernelOutput();
// Destroy object.
m_kernel->deleteLater();
m_kernel = nullptr;
// Display error.
QMessageBox::critical(this, "Error", msg);
}
void MainWindow::kernelOutput()
{
// It is possible for Qt to signal this slot after QProcess::errorOccurred or QProcess::finished
// so we need to check if the those signals has been received.
if (!m_kernel) {
return;
}
while (m_kernel->canReadLine()) {
auto line = QString::fromUtf8(m_kernel->readLine());
m_log->appendMessage(line);
}
}
void MainWindow::kernelTerminated(int, QProcess::ExitStatus)
{
// Do nothing if we got QProcess::errorOccurred before this signal.
if (!m_kernel) {
return;
}
kernelOutput();
QMessageBox::critical(this, "Error", "The emulator kernel has stopped unexpectedly. Please take a look at the log and report this issue if possible.");
m_kernel->deleteLater();
m_kernel = nullptr;
}
bool MainWindow::loadGame(const QString &gameId)
@ -507,24 +400,6 @@ bool MainWindow::loadGame(const QString &gameId)
return true;
}
void MainWindow::killKernel()
{
// Do nothing if the kernel already terminated. This prevent a crash if this method is putting
// behind the message box and the kernel itself was terminated while waiting for the user to confirm.
if (!m_kernel) {
return;
}
// We need to disconnect all slots first otherwise the application will be freeze.
disconnect(m_kernel, nullptr, nullptr, nullptr);
m_kernel->kill();
m_kernel->waitForFinished(-1);
delete m_kernel;
m_kernel = nullptr;
}
void MainWindow::restoreGeometry()
{
QSettings settings;
@ -554,13 +429,13 @@ bool MainWindow::requireEmulatorStopped()
killPrompt.setStandardButtons(QMessageBox::Cancel | QMessageBox::Yes);
killPrompt.setDefaultButton(QMessageBox::Cancel);
killPrompt.setIcon(QMessageBox::Warning);
if (killPrompt.exec() == QMessageBox::Yes) {
killKernel();
return false; // Kernel was killed
if (killPrompt.exec() != QMessageBox::Yes) {
return true;
}
return true; // Kernel left running
m_kernel.kill();
}
return false; // Kernel isn't running
return false;
}

View File

@ -1,10 +1,13 @@
#pragma once
#include <QMainWindow>
#include <QProcess>
#include "core.hpp"
#include <QMainWindow>
class LaunchSettings;
class LogFormatter;
class QListView;
class QStackedWidget;
class QTableView;
class MainWindow final : public QMainWindow {
public:
@ -16,29 +19,25 @@ public:
protected:
void closeEvent(QCloseEvent *event) override;
void resizeEvent(QResizeEvent *event) override;
private slots:
void tabChanged();
void installPkg();
void openSystemFolder();
void reportIssue();
void aboutObliteration();
void requestGamesContextMenu(const QPoint &pos);
void startGame(const QModelIndex &index);
void kernelError(QProcess::ProcessError error);
void kernelOutput();
void kernelTerminated(int exitCode, QProcess::ExitStatus exitStatus);
void startKernel();
private:
bool loadGame(const QString &gameId);
void killKernel();
void restoreGeometry();
bool requireEmulatorStopped();
private:
QTabWidget *m_tab;
QListView *m_games;
QStackedWidget *m_screen;
LaunchSettings *m_launch;
QTableView *m_games;
LogFormatter *m_log;
QProcess *m_kernel;
Vmm m_kernel;
};

View File

@ -10,6 +10,7 @@
<file>resources/lightmode/card-text-outline.svg</file>
<file>resources/lightmode/cog-outline.svg</file>
<file>resources/lightmode/folder-open-outline.svg</file>
<file>resources/lightmode/monitor.svg</file>
<file>resources/lightmode/view-comfy.svg</file>
<file>resources/fallbackicon0.png</file>
<file>resources/obliteration-icon.png</file>

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M21,16H3V4H21M21,2H3C1.89,2 1,2.89 1,4V16A2,2 0 0,0 3,18H10V20H8V22H16V20H14V18H21A2,2 0 0,0 23,16V4C23,2.89 22.1,2 21,2Z" /></svg>

After

Width:  |  Height:  |  Size: 200 B

View File

@ -1,7 +0,0 @@
#pragma once
#ifdef _WIN32
#define STR(x) L##x
#else
#define STR(x) x
#endif

View File

@ -2,7 +2,6 @@
#include "path.hpp"
#include "progress_dialog.hpp"
#include "settings.hpp"
#include "string.hpp"
#include "system_downloader.hpp"
#include <QCoreApplication>
@ -20,12 +19,11 @@ bool isSystemInitialized(const QString &path)
auto libkernel = toPath(path);
try {
libkernel /= STR("system");
libkernel /= STR("common");
libkernel /= STR("lib");
libkernel /= STR("libkernel.sprx");
return std::filesystem::exists(libkernel);
#ifdef _WIN32
return std::filesystem::exists(libkernel / L"system" / L"common" / L"lib" / L"libkernel.sprx");
#else
return std::filesystem::exists(libkernel / "system" / "common" / "lib" / "libkernel.sprx");
#endif
} catch (...) {
return false;
}