refactor: Clean up setup wizard and improve updater

- Refactor setup_wizard code (remove 100+ lines of unused code)
- Change default update channel from Stable to Nightly
- Fix Windows build detection in updater (skip PGO builds)
- Remove verbose Wayland logging from Vulkan swapchain

Signed-off-by: Zephyron <zephyron@citron-emu.org>
This commit is contained in:
Zephyron
2025-12-03 11:56:01 +10:00
parent ff3759409b
commit 45d58d53aa
5 changed files with 201 additions and 301 deletions

View File

@@ -3,6 +3,7 @@
#include "citron/setup_wizard.h" #include "citron/setup_wizard.h"
#include <QApplication> #include <QApplication>
#include <QButtonGroup>
#include <QFileDialog> #include <QFileDialog>
#include <QGroupBox> #include <QGroupBox>
#include <QHBoxLayout> #include <QHBoxLayout>
@@ -14,20 +15,20 @@
#include <QPushButton> #include <QPushButton>
#include <QRadioButton> #include <QRadioButton>
#include <QVBoxLayout> #include <QVBoxLayout>
#include <QButtonGroup>
#include "common/fs/fs.h" #include "common/fs/fs.h"
#include "common/fs/path_util.h" #include "common/fs/path_util.h"
#include "common/logging/log.h" #include "common/logging/log.h"
#include "common/settings.h" #include "common/settings.h"
#include "core/crypto/key_manager.h" #include "core/crypto/key_manager.h"
#include "core/hle/service/acc/profile_manager.h"
#include "core/file_sys/vfs/vfs.h" #include "core/file_sys/vfs/vfs.h"
#include "core/hle/service/acc/profile_manager.h"
#include "frontend_common/content_manager.h" #include "frontend_common/content_manager.h"
#include "ui_setup_wizard.h" #include "ui_setup_wizard.h"
#include "citron/uisettings.h"
#include "citron/configuration/configure_input.h" #include "citron/configuration/configure_input.h"
#include "citron/main.h" #include "citron/main.h"
#include "citron/theme.h"
#include "citron/uisettings.h"
#ifdef CITRON_ENABLE_LIBARCHIVE #ifdef CITRON_ENABLE_LIBARCHIVE
#include <archive.h> #include <archive.h>
@@ -35,10 +36,29 @@
#endif #endif
#ifdef _WIN32 #ifdef _WIN32
#include <windows.h>
#include <shlobj.h> #include <shlobj.h>
#include <windows.h>
#endif #endif
// Helper function to detect if the application is using a dark theme
static bool IsDarkMode() {
const std::string& theme_name = UISettings::values.theme;
if (theme_name == "qdarkstyle" || theme_name == "colorful_dark" ||
theme_name == "qdarkstyle_midnight_blue" || theme_name == "colorful_midnight_blue") {
return true;
}
if (theme_name == "default" || theme_name == "colorful") {
const QPalette palette = qApp->palette();
const QColor text_color = palette.color(QPalette::WindowText);
const QColor base_color = palette.color(QPalette::Window);
return text_color.value() > base_color.value();
}
return false;
}
SetupWizard::SetupWizard(Core::System& system_, GMainWindow* main_window_, QWidget* parent) SetupWizard::SetupWizard(Core::System& system_, GMainWindow* main_window_, QWidget* parent)
: QDialog(parent), ui{std::make_unique<Ui::SetupWizard>()}, system{system_}, : QDialog(parent), ui{std::make_unique<Ui::SetupWizard>()}, system{system_},
main_window{main_window_}, current_page{0}, is_portable_mode{false}, main_window{main_window_}, current_page{0}, is_portable_mode{false},
@@ -47,24 +67,18 @@ SetupWizard::SetupWizard(Core::System& system_, GMainWindow* main_window_, QWidg
setWindowTitle(tr("citron Setup Wizard")); setWindowTitle(tr("citron Setup Wizard"));
// Set window flags before setting modality
setWindowFlags(Qt::Dialog | Qt::WindowTitleHint | Qt::WindowCloseButtonHint | setWindowFlags(Qt::Dialog | Qt::WindowTitleHint | Qt::WindowCloseButtonHint |
Qt::WindowSystemMenuHint | Qt::WindowStaysOnTopHint); Qt::WindowSystemMenuHint | Qt::WindowStaysOnTopHint);
// Set window modality to NonModal to allow interaction with the main window.
setWindowModality(Qt::NonModal); setWindowModality(Qt::NonModal);
// Get UI elements from the .ui file
sidebar_list = ui->sidebarList; sidebar_list = ui->sidebarList;
content_stack = ui->contentStack; content_stack = ui->contentStack;
back_button = ui->backButton; back_button = ui->backButton;
next_button = ui->nextButton; next_button = ui->nextButton;
cancel_button = ui->cancelButton; cancel_button = ui->cancelButton;
SetupUI();
SetupPages(); SetupPages();
// Connect signals
connect(back_button, &QPushButton::clicked, this, &SetupWizard::OnBackClicked); connect(back_button, &QPushButton::clicked, this, &SetupWizard::OnBackClicked);
connect(next_button, &QPushButton::clicked, this, &SetupWizard::OnNextClicked); connect(next_button, &QPushButton::clicked, this, &SetupWizard::OnNextClicked);
connect(cancel_button, &QPushButton::clicked, this, &SetupWizard::OnCancelClicked); connect(cancel_button, &QPushButton::clicked, this, &SetupWizard::OnCancelClicked);
@@ -77,25 +91,16 @@ SetupWizard::SetupWizard(Core::System& system_, GMainWindow* main_window_, QWidg
} }
}); });
// Initialize to first page
current_page = 0; current_page = 0;
content_stack->setCurrentIndex(0); content_stack->setCurrentIndex(0);
UpdateNavigationButtons(); UpdateNavigationButtons();
last_palette_text_color = qApp->palette().color(QPalette::WindowText);
UpdateTheme();
} }
SetupWizard::~SetupWizard() = default; SetupWizard::~SetupWizard() = default;
void SetupWizard::SetupUI() {
// Apply dark theme styling
setStyleSheet(QStringLiteral(
"QDialog { background-color: #1e1e1e; }"
"QPushButton { background-color: #3d3d3d; color: #ffffff; border: 1px solid #555555; padding: 8px; border-radius: 4px; }"
"QPushButton:hover { background-color: #4d4d4d; }"
"QPushButton:pressed { background-color: #2d2d2d; }"
"QPushButton:disabled { background-color: #2b2b2b; color: #666666; }"
));
}
void SetupWizard::SetupPages() { void SetupWizard::SetupPages() {
// Welcome page // Welcome page
auto* welcome_page = new QWidget(); auto* welcome_page = new QWidget();
@@ -104,16 +109,14 @@ void SetupWizard::SetupPages() {
welcome_layout->setSpacing(20); welcome_layout->setSpacing(20);
auto* welcome_title = new QLabel(tr("Welcome to citron Setup Wizard")); auto* welcome_title = new QLabel(tr("Welcome to citron Setup Wizard"));
welcome_title->setStyleSheet(QStringLiteral("color: #ffffff; font-size: 24px; font-weight: bold;")); welcome_title->setProperty("class", QStringLiteral("wizard-title"));
welcome_layout->addWidget(welcome_title); welcome_layout->addWidget(welcome_title);
auto* welcome_text = new QLabel(tr("This wizard will help you configure citron for first-time use.\n" auto* welcome_text = new QLabel(tr("This wizard will help you configure citron for first-time use.\n"
"You'll be able to set up keys, firmware, game directories, and more.")); "You'll be able to set up keys, firmware, game directories, and more."));
welcome_text->setStyleSheet(QStringLiteral("color: #cccccc; font-size: 12px;"));
welcome_text->setWordWrap(true); welcome_text->setWordWrap(true);
welcome_layout->addWidget(welcome_text); welcome_layout->addWidget(welcome_text);
welcome_layout->addStretch(); welcome_layout->addStretch();
content_stack->addWidget(welcome_page); content_stack->addWidget(welcome_page);
sidebar_list->addItem(tr("Welcome")); sidebar_list->addItem(tr("Welcome"));
@@ -124,56 +127,39 @@ void SetupWizard::SetupPages() {
install_layout->setSpacing(20); install_layout->setSpacing(20);
auto* install_title = new QLabel(tr("Installation Type")); auto* install_title = new QLabel(tr("Installation Type"));
install_title->setStyleSheet(QStringLiteral("color: #ffffff; font-size: 18px; font-weight: bold;")); install_title->setProperty("class", QStringLiteral("wizard-title"));
install_layout->addWidget(install_title); install_layout->addWidget(install_title);
auto* install_subtitle = new QLabel(tr("Choose how you want to store citron's data:")); auto* install_subtitle = new QLabel(tr("Choose how you want to store citron's data:"));
install_subtitle->setStyleSheet(QStringLiteral("color: #aaaaaa; font-size: 12px;"));
install_layout->addWidget(install_subtitle); install_layout->addWidget(install_subtitle);
auto* install_group = new QGroupBox(); auto* install_group = new QGroupBox();
install_group->setStyleSheet(QStringLiteral("QGroupBox { color: #ffffff; border: 1px solid #444444; padding: 15px; }"));
auto* install_group_layout = new QVBoxLayout(install_group); auto* install_group_layout = new QVBoxLayout(install_group);
auto* button_group = new QButtonGroup(this); auto* button_group = new QButtonGroup(this);
auto* portable_radio = new QRadioButton(tr("Portable (creates 'user' folder in executable directory)")); auto* portable_radio = new QRadioButton(tr("Portable (creates 'user' folder in executable directory)"));
portable_radio->setStyleSheet(QStringLiteral("color: #cccccc;"));
// Get platform-specific standard path
QString standard_path_text; QString standard_path_text;
#ifdef _WIN32 #ifdef _WIN32
const auto appdata_path = Common::FS::GetAppDataRoamingDirectory(); const auto appdata_path = Common::FS::GetAppDataRoamingDirectory();
const auto appdata_str = QString::fromStdString(Common::FS::PathToUTF8String(appdata_path)); const auto appdata_str = QString::fromStdString(Common::FS::PathToUTF8String(appdata_path));
standard_path_text = tr("Standard (uses %APPDATA%\\citron)").arg(appdata_str); standard_path_text = tr("Standard (uses %APPDATA%\\citron)").arg(appdata_str);
#elif defined(__APPLE__) #elif defined(__APPLE__)
// macOS uses ~/Library/Application Support/citron
standard_path_text = tr("Standard (uses ~/Library/Application Support/citron)"); standard_path_text = tr("Standard (uses ~/Library/Application Support/citron)");
#else #else
// Linux/Unix - uses XDG_DATA_HOME (defaults to ~/.local/share)
const auto data_path = Common::FS::GetDataDirectory("XDG_DATA_HOME"); const auto data_path = Common::FS::GetDataDirectory("XDG_DATA_HOME");
const auto data_path_str = QString::fromStdString(Common::FS::PathToUTF8String(data_path)); const auto data_path_str = QString::fromStdString(Common::FS::PathToUTF8String(data_path));
standard_path_text = tr("Standard (uses %1/citron)").arg(data_path_str); standard_path_text = tr("Standard (uses %1/citron)").arg(data_path_str);
#endif #endif
auto* standard_radio = new QRadioButton(standard_path_text); auto* standard_radio = new QRadioButton(standard_path_text);
standard_radio->setStyleSheet(QStringLiteral("color: #cccccc;"));
standard_radio->setChecked(true); standard_radio->setChecked(true);
button_group->addButton(portable_radio, 0); button_group->addButton(portable_radio, 0);
button_group->addButton(standard_radio, 1); button_group->addButton(standard_radio, 1);
install_group_layout->addWidget(portable_radio); install_group_layout->addWidget(portable_radio);
install_group_layout->addWidget(standard_radio); install_group_layout->addWidget(standard_radio);
install_layout->addWidget(install_group); install_layout->addWidget(install_group);
install_layout->addStretch(); install_layout->addStretch();
connect(portable_radio, &QRadioButton::toggled, this, [this](bool checked) { if (checked) is_portable_mode = true; });
connect(portable_radio, &QRadioButton::toggled, this, [this](bool checked) { connect(standard_radio, &QRadioButton::toggled, this, [this](bool checked) { if (checked) is_portable_mode = false; });
if (checked) is_portable_mode = true;
});
connect(standard_radio, &QRadioButton::toggled, this, [this](bool checked) {
if (checked) is_portable_mode = false;
});
content_stack->addWidget(install_page); content_stack->addWidget(install_page);
sidebar_list->addItem(tr("Installation Type")); sidebar_list->addItem(tr("Installation Type"));
@@ -182,34 +168,25 @@ void SetupWizard::SetupPages() {
auto* keys_layout = new QVBoxLayout(keys_page); auto* keys_layout = new QVBoxLayout(keys_page);
keys_layout->setContentsMargins(40, 40, 40, 40); keys_layout->setContentsMargins(40, 40, 40, 40);
keys_layout->setSpacing(20); keys_layout->setSpacing(20);
auto* keys_title = new QLabel(tr("Decryption Keys")); auto* keys_title = new QLabel(tr("Decryption Keys"));
keys_title->setStyleSheet(QStringLiteral("color: #ffffff; font-size: 18px; font-weight: bold;")); keys_title->setProperty("class", QStringLiteral("wizard-title"));
keys_layout->addWidget(keys_title); keys_layout->addWidget(keys_title);
auto* keys_text = new QLabel(tr("Decryption keys are required to run encrypted games.\nSelect your prod.keys file to install them."));
auto* keys_text = new QLabel(tr("Decryption keys are required to run encrypted games.\n"
"Select your prod.keys file to install them."));
keys_text->setStyleSheet(QStringLiteral("color: #cccccc; font-size: 12px;"));
keys_text->setWordWrap(true); keys_text->setWordWrap(true);
keys_layout->addWidget(keys_text); keys_layout->addWidget(keys_text);
auto* keys_button = new QPushButton(tr("Select Keys File")); auto* keys_button = new QPushButton(tr("Select Keys File"));
keys_button->setStyleSheet(QStringLiteral("color: #ffffff;"));
connect(keys_button, &QPushButton::clicked, this, &SetupWizard::OnSelectKeys); connect(keys_button, &QPushButton::clicked, this, &SetupWizard::OnSelectKeys);
keys_layout->addWidget(keys_button); keys_layout->addWidget(keys_button);
auto* keys_status = new QLabel(); auto* keys_status = new QLabel();
keys_status->setStyleSheet(QStringLiteral("color: #aaaaaa; font-size: 11px;"));
keys_layout->addWidget(keys_status); keys_layout->addWidget(keys_status);
if (CheckKeysInstalled()) { if (CheckKeysInstalled()) {
keys_status->setText(tr("✓ Keys are installed")); keys_status->setText(tr("✓ Keys are installed"));
keys_status->setStyleSheet(QStringLiteral("color: #4caf50; font-size: 11px;")); keys_status->setProperty("class", QStringLiteral("wizard-status-success"));
} else { } else {
keys_status->setText(tr("Keys not installed")); keys_status->setText(tr("Keys not installed"));
keys_status->setProperty("class", QStringLiteral("wizard-status-default"));
} }
keys_layout->addStretch(); keys_layout->addStretch();
content_stack->addWidget(keys_page); content_stack->addWidget(keys_page);
sidebar_list->addItem(tr("Keys")); sidebar_list->addItem(tr("Keys"));
@@ -218,34 +195,25 @@ void SetupWizard::SetupPages() {
auto* firmware_layout = new QVBoxLayout(firmware_page); auto* firmware_layout = new QVBoxLayout(firmware_page);
firmware_layout->setContentsMargins(40, 40, 40, 40); firmware_layout->setContentsMargins(40, 40, 40, 40);
firmware_layout->setSpacing(20); firmware_layout->setSpacing(20);
auto* firmware_title = new QLabel(tr("Firmware")); auto* firmware_title = new QLabel(tr("Firmware"));
firmware_title->setStyleSheet(QStringLiteral("color: #ffffff; font-size: 18px; font-weight: bold;")); firmware_title->setProperty("class", QStringLiteral("wizard-title"));
firmware_layout->addWidget(firmware_title); firmware_layout->addWidget(firmware_title);
auto* firmware_text = new QLabel(tr("Firmware is required to run system applications and some games.\nYou can install it from a ZIP file or a folder containing NCA files."));
auto* firmware_text = new QLabel(tr("Firmware is required to run system applications and some games.\n"
"You can install it from a ZIP file or a folder containing NCA files."));
firmware_text->setStyleSheet(QStringLiteral("color: #cccccc; font-size: 12px;"));
firmware_text->setWordWrap(true); firmware_text->setWordWrap(true);
firmware_layout->addWidget(firmware_text); firmware_layout->addWidget(firmware_text);
auto* firmware_button = new QPushButton(tr("Install Firmware")); auto* firmware_button = new QPushButton(tr("Install Firmware"));
firmware_button->setStyleSheet(QStringLiteral("color: #ffffff;"));
connect(firmware_button, &QPushButton::clicked, this, &SetupWizard::OnSelectFirmware); connect(firmware_button, &QPushButton::clicked, this, &SetupWizard::OnSelectFirmware);
firmware_layout->addWidget(firmware_button); firmware_layout->addWidget(firmware_button);
auto* firmware_status = new QLabel(); auto* firmware_status = new QLabel();
firmware_status->setStyleSheet(QStringLiteral("color: #aaaaaa; font-size: 11px;"));
firmware_layout->addWidget(firmware_status); firmware_layout->addWidget(firmware_status);
if (CheckFirmwareInstalled() || firmware_installed) { if (CheckFirmwareInstalled() || firmware_installed) {
firmware_status->setText(tr("✓ Firmware is installed")); firmware_status->setText(tr("✓ Firmware is installed"));
firmware_status->setStyleSheet(QStringLiteral("color: #4caf50; font-size: 11px;")); firmware_status->setProperty("class", QStringLiteral("wizard-status-success"));
} else { } else {
firmware_status->setText(tr("Firmware not installed (optional)")); firmware_status->setText(tr("Firmware not installed (optional)"));
firmware_status->setProperty("class", QStringLiteral("wizard-status-default"));
} }
firmware_layout->addStretch(); firmware_layout->addStretch();
content_stack->addWidget(firmware_page); content_stack->addWidget(firmware_page);
sidebar_list->addItem(tr("Firmware")); sidebar_list->addItem(tr("Firmware"));
@@ -254,28 +222,21 @@ void SetupWizard::SetupPages() {
auto* games_layout = new QVBoxLayout(games_page); auto* games_layout = new QVBoxLayout(games_page);
games_layout->setContentsMargins(40, 40, 40, 40); games_layout->setContentsMargins(40, 40, 40, 40);
games_layout->setSpacing(20); games_layout->setSpacing(20);
auto* games_title = new QLabel(tr("Games Directory")); auto* games_title = new QLabel(tr("Games Directory"));
games_title->setStyleSheet(QStringLiteral("color: #ffffff; font-size: 18px; font-weight: bold;")); games_title->setProperty("class", QStringLiteral("wizard-title"));
games_layout->addWidget(games_title); games_layout->addWidget(games_title);
auto* games_text = new QLabel(tr("Select the directory where your game files are located.")); auto* games_text = new QLabel(tr("Select the directory where your game files are located."));
games_text->setStyleSheet(QStringLiteral("color: #cccccc; font-size: 12px;"));
games_text->setWordWrap(true); games_text->setWordWrap(true);
games_layout->addWidget(games_text); games_layout->addWidget(games_text);
auto* games_path_layout = new QHBoxLayout(); auto* games_path_layout = new QHBoxLayout();
auto* games_path_edit = new QLineEdit(); auto* games_path_edit = new QLineEdit();
games_path_edit->setStyleSheet(QStringLiteral("color: #ffffff; background-color: #2b2b2b; border: 1px solid #444444; padding: 5px;"));
games_path_edit->setReadOnly(true); games_path_edit->setReadOnly(true);
games_path_edit->setPlaceholderText(tr("No directory selected")); games_path_edit->setPlaceholderText(tr("No directory selected"));
if (!games_directory.isEmpty()) { if (!games_directory.isEmpty()) {
games_path_edit->setText(games_directory); games_path_edit->setText(games_directory);
} }
games_path_layout->addWidget(games_path_edit); games_path_layout->addWidget(games_path_edit);
auto* games_button = new QPushButton(tr("Browse...")); auto* games_button = new QPushButton(tr("Browse..."));
games_button->setStyleSheet(QStringLiteral("color: #ffffff;"));
connect(games_button, &QPushButton::clicked, this, [this, games_path_edit]() { connect(games_button, &QPushButton::clicked, this, [this, games_path_edit]() {
OnSelectGamesDirectory(); OnSelectGamesDirectory();
games_path_edit->setText(games_directory); games_path_edit->setText(games_directory);
@@ -283,7 +244,6 @@ void SetupWizard::SetupPages() {
games_path_layout->addWidget(games_button); games_path_layout->addWidget(games_button);
games_layout->addLayout(games_path_layout); games_layout->addLayout(games_path_layout);
games_layout->addStretch(); games_layout->addStretch();
content_stack->addWidget(games_page); content_stack->addWidget(games_page);
sidebar_list->addItem(tr("Games Directory")); sidebar_list->addItem(tr("Games Directory"));
@@ -292,32 +252,23 @@ void SetupWizard::SetupPages() {
auto* paths_layout = new QVBoxLayout(paths_page); auto* paths_layout = new QVBoxLayout(paths_page);
paths_layout->setContentsMargins(40, 40, 40, 40); paths_layout->setContentsMargins(40, 40, 40, 40);
paths_layout->setSpacing(20); paths_layout->setSpacing(20);
auto* paths_title = new QLabel(tr("Paths")); auto* paths_title = new QLabel(tr("Paths"));
paths_title->setStyleSheet(QStringLiteral("color: #ffffff; font-size: 18px; font-weight: bold;")); paths_title->setProperty("class", QStringLiteral("wizard-title"));
paths_layout->addWidget(paths_title); paths_layout->addWidget(paths_title);
auto* paths_text = new QLabel(tr("Configure additional paths for screenshots and other files.")); auto* paths_text = new QLabel(tr("Configure additional paths for screenshots and other files."));
paths_text->setStyleSheet(QStringLiteral("color: #cccccc; font-size: 12px;"));
paths_text->setWordWrap(true); paths_text->setWordWrap(true);
paths_layout->addWidget(paths_text); paths_layout->addWidget(paths_text);
auto* screenshots_label = new QLabel(tr("Screenshots Directory:")); auto* screenshots_label = new QLabel(tr("Screenshots Directory:"));
screenshots_label->setStyleSheet(QStringLiteral("color: #cccccc; font-size: 12px;"));
paths_layout->addWidget(screenshots_label); paths_layout->addWidget(screenshots_label);
auto* screenshots_path_layout = new QHBoxLayout(); auto* screenshots_path_layout = new QHBoxLayout();
auto* screenshots_path_edit = new QLineEdit(); auto* screenshots_path_edit = new QLineEdit();
screenshots_path_edit->setStyleSheet(QStringLiteral("color: #ffffff; background-color: #2b2b2b; border: 1px solid #444444; padding: 5px;"));
screenshots_path_edit->setReadOnly(true); screenshots_path_edit->setReadOnly(true);
screenshots_path_edit->setPlaceholderText(tr("Default location")); screenshots_path_edit->setPlaceholderText(tr("Default location"));
if (!screenshots_path.isEmpty()) { if (!screenshots_path.isEmpty()) {
screenshots_path_edit->setText(screenshots_path); screenshots_path_edit->setText(screenshots_path);
} }
screenshots_path_layout->addWidget(screenshots_path_edit); screenshots_path_layout->addWidget(screenshots_path_edit);
auto* screenshots_button = new QPushButton(tr("Browse...")); auto* screenshots_button = new QPushButton(tr("Browse..."));
screenshots_button->setStyleSheet(QStringLiteral("color: #ffffff;"));
connect(screenshots_button, &QPushButton::clicked, this, [this, screenshots_path_edit]() { connect(screenshots_button, &QPushButton::clicked, this, [this, screenshots_path_edit]() {
OnSelectScreenshotsPath(); OnSelectScreenshotsPath();
screenshots_path_edit->setText(screenshots_path); screenshots_path_edit->setText(screenshots_path);
@@ -325,7 +276,6 @@ void SetupWizard::SetupPages() {
screenshots_path_layout->addWidget(screenshots_button); screenshots_path_layout->addWidget(screenshots_button);
paths_layout->addLayout(screenshots_path_layout); paths_layout->addLayout(screenshots_path_layout);
paths_layout->addStretch(); paths_layout->addStretch();
content_stack->addWidget(paths_page); content_stack->addWidget(paths_page);
sidebar_list->addItem(tr("Paths")); sidebar_list->addItem(tr("Paths"));
@@ -334,26 +284,18 @@ void SetupWizard::SetupPages() {
auto* profile_layout = new QVBoxLayout(profile_page); auto* profile_layout = new QVBoxLayout(profile_page);
profile_layout->setContentsMargins(40, 40, 40, 40); profile_layout->setContentsMargins(40, 40, 40, 40);
profile_layout->setSpacing(20); profile_layout->setSpacing(20);
auto* profile_title = new QLabel(tr("Profile Name")); auto* profile_title = new QLabel(tr("Profile Name"));
profile_title->setStyleSheet(QStringLiteral("color: #ffffff; font-size: 18px; font-weight: bold;")); profile_title->setProperty("class", QStringLiteral("wizard-title"));
profile_layout->addWidget(profile_title); profile_layout->addWidget(profile_title);
auto* profile_text = new QLabel(tr("Set your profile name (default: 'citron').")); auto* profile_text = new QLabel(tr("Set your profile name (default: 'citron')."));
profile_text->setStyleSheet(QStringLiteral("color: #cccccc; font-size: 12px;"));
profile_text->setWordWrap(true); profile_text->setWordWrap(true);
profile_layout->addWidget(profile_text); profile_layout->addWidget(profile_text);
auto* profile_edit = new QLineEdit(); auto* profile_edit = new QLineEdit();
profile_edit->setStyleSheet(QStringLiteral("color: #ffffff; background-color: #2b2b2b; border: 1px solid #444444; padding: 5px;"));
profile_edit->setPlaceholderText(tr("citron")); profile_edit->setPlaceholderText(tr("citron"));
profile_edit->setText(profile_name); profile_edit->setText(profile_name);
connect(profile_edit, &QLineEdit::textChanged, this, [this](const QString& text) { connect(profile_edit, &QLineEdit::textChanged, this, [this](const QString& text) { profile_name = text; });
profile_name = text;
});
profile_layout->addWidget(profile_edit); profile_layout->addWidget(profile_edit);
profile_layout->addStretch(); profile_layout->addStretch();
content_stack->addWidget(profile_page); content_stack->addWidget(profile_page);
sidebar_list->addItem(tr("Profile")); sidebar_list->addItem(tr("Profile"));
@@ -362,23 +304,16 @@ void SetupWizard::SetupPages() {
auto* controller_layout = new QVBoxLayout(controller_page); auto* controller_layout = new QVBoxLayout(controller_page);
controller_layout->setContentsMargins(40, 40, 40, 40); controller_layout->setContentsMargins(40, 40, 40, 40);
controller_layout->setSpacing(20); controller_layout->setSpacing(20);
auto* controller_title = new QLabel(tr("Controller Setup")); auto* controller_title = new QLabel(tr("Controller Setup"));
controller_title->setStyleSheet(QStringLiteral("color: #ffffff; font-size: 18px; font-weight: bold;")); controller_title->setProperty("class", QStringLiteral("wizard-title"));
controller_layout->addWidget(controller_title); controller_layout->addWidget(controller_title);
auto* controller_text = new QLabel(tr("You can configure your controller after setup is complete.\nGo to Settings > Configure > Controls to set up your controller."));
auto* controller_text = new QLabel(tr("You can configure your controller after setup is complete.\n"
"Go to Settings > Configure > Controls to set up your controller."));
controller_text->setStyleSheet(QStringLiteral("color: #cccccc; font-size: 12px;"));
controller_text->setWordWrap(true); controller_text->setWordWrap(true);
controller_layout->addWidget(controller_text); controller_layout->addWidget(controller_text);
auto* controller_button = new QPushButton(tr("Open Controller Settings")); auto* controller_button = new QPushButton(tr("Open Controller Settings"));
controller_button->setStyleSheet(QStringLiteral("color: #ffffff;"));
connect(controller_button, &QPushButton::clicked, this, &SetupWizard::OnControllerSetup); connect(controller_button, &QPushButton::clicked, this, &SetupWizard::OnControllerSetup);
controller_layout->addWidget(controller_button); controller_layout->addWidget(controller_button);
controller_layout->addStretch(); controller_layout->addStretch();
content_stack->addWidget(controller_page); content_stack->addWidget(controller_page);
sidebar_list->addItem(tr("Controller")); sidebar_list->addItem(tr("Controller"));
@@ -387,29 +322,53 @@ void SetupWizard::SetupPages() {
auto* completion_layout = new QVBoxLayout(completion_page); auto* completion_layout = new QVBoxLayout(completion_page);
completion_layout->setContentsMargins(40, 40, 40, 40); completion_layout->setContentsMargins(40, 40, 40, 40);
completion_layout->setSpacing(20); completion_layout->setSpacing(20);
auto* completion_title = new QLabel(tr("Setup Complete!")); auto* completion_title = new QLabel(tr("Setup Complete!"));
completion_title->setStyleSheet(QStringLiteral("color: #ffffff; font-size: 24px; font-weight: bold;")); completion_title->setProperty("class", QStringLiteral("wizard-title"));
completion_layout->addWidget(completion_title); completion_layout->addWidget(completion_title);
auto* completion_text = new QLabel(tr("You have completed the setup wizard.\nClick Finish to apply your settings and start using citron."));
auto* completion_text = new QLabel(tr("You have completed the setup wizard.\n"
"Click Finish to apply your settings and start using citron."));
completion_text->setStyleSheet(QStringLiteral("color: #cccccc; font-size: 12px;"));
completion_text->setWordWrap(true); completion_text->setWordWrap(true);
completion_layout->addWidget(completion_text); completion_layout->addWidget(completion_text);
completion_layout->addStretch(); completion_layout->addStretch();
content_stack->addWidget(completion_page); content_stack->addWidget(completion_page);
sidebar_list->addItem(tr("Complete")); sidebar_list->addItem(tr("Complete"));
} }
void SetupWizard::changeEvent(QEvent* event) {
if (event->type() == QEvent::PaletteChange) {
const QColor current_color = qApp->palette().color(QPalette::WindowText);
if (current_color != last_palette_text_color) {
last_palette_text_color = current_color;
UpdateTheme();
}
}
QDialog::changeEvent(event);
}
void SetupWizard::UpdateTheme() {
const bool is_dark = IsDarkMode();
const QString bg_color = is_dark ? QStringLiteral("#1e1e1e") : QStringLiteral("#f5f5f5");
const QString text_color = is_dark ? QStringLiteral("#ffffff") : QStringLiteral("#000000");
const QString secondary_bg_color = is_dark ? QStringLiteral("#2b2b2b") : QStringLiteral("#e9e9e9");
const QString tertiary_bg_color = is_dark ? QStringLiteral("#3d3d3d") : QStringLiteral("#dcdcdc");
const QString button_bg_color = is_dark ? QStringLiteral("#383838") : QStringLiteral("#e1e1e1");
const QString hover_bg_color = is_dark ? QStringLiteral("#4d4d4d") : QStringLiteral("#e8f0fe");
const QString disabled_text_color = is_dark ? QStringLiteral("#666666") : QStringLiteral("#a0a0a0");
QString style_sheet = property("templateStyleSheet").toString();
style_sheet.replace(QStringLiteral("%%BACKGROUND_COLOR%%"), bg_color);
style_sheet.replace(QStringLiteral("%%TEXT_COLOR%%"), text_color);
style_sheet.replace(QStringLiteral("%%SECONDARY_BG_COLOR%%"), secondary_bg_color);
style_sheet.replace(QStringLiteral("%%TERTIARY_BG_COLOR%%"), tertiary_bg_color);
style_sheet.replace(QStringLiteral("%%BUTTON_BG_COLOR%%"), button_bg_color);
style_sheet.replace(QStringLiteral("%%HOVER_BG_COLOR%%"), hover_bg_color);
style_sheet.replace(QStringLiteral("%%DISABLED_TEXT_COLOR%%"), disabled_text_color);
setStyleSheet(style_sheet);
}
void SetupWizard::OnPageChanged(int index) { void SetupWizard::OnPageChanged(int index) {
if (index >= 0 && index < content_stack->count()) { if (index >= 0 && index < content_stack->count()) {
content_stack->setCurrentIndex(index); content_stack->setCurrentIndex(index);
current_page = index; current_page = index;
UpdateNavigationButtons(); UpdateNavigationButtons();
// Update sidebar selection
sidebar_list->setCurrentRow(index); sidebar_list->setCurrentRow(index);
} }
} }
@@ -420,7 +379,6 @@ void SetupWizard::OnNextClicked() {
current_page++; current_page++;
OnPageChanged(current_page); OnPageChanged(current_page);
} else { } else {
// Finished
ApplyConfiguration(); ApplyConfiguration();
accept(); accept();
} }
@@ -435,30 +393,24 @@ void SetupWizard::OnBackClicked() {
} }
void SetupWizard::OnCancelClicked() { void SetupWizard::OnCancelClicked() {
if (QMessageBox::question(this, tr("Cancel Setup"), if (QMessageBox::question(this, tr("Cancel Setup"), tr("Are you sure you want to cancel the setup wizard?"), QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) {
tr("Are you sure you want to cancel the setup wizard?"),
QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) {
reject(); reject();
} }
} }
bool SetupWizard::ValidateCurrentPage() { bool SetupWizard::ValidateCurrentPage() {
switch (current_page) { switch (static_cast<Page>(current_page)) {
case Page_Keys: case Page_Keys:
if (!CheckKeysInstalled()) { if (!CheckKeysInstalled()) {
QMessageBox::warning(this, tr("Keys Required"), QMessageBox::warning(this, tr("Keys Required"), tr("Please install decryption keys before continuing.\nKeys are required to run encrypted games."));
tr("Please install decryption keys before continuing.\n"
"Keys are required to run encrypted games."));
return false; return false;
} }
break; break;
case Page_Firmware: case Page_Firmware:
// Firmware is optional, so we always allow proceeding
break; break;
case Page_GamesDirectory: case Page_GamesDirectory:
if (games_directory.isEmpty()) { if (games_directory.isEmpty()) {
QMessageBox::warning(this, tr("Games Directory Required"), QMessageBox::warning(this, tr("Games Directory Required"), tr("Please select a games directory before continuing."));
tr("Please select a games directory before continuing."));
return false; return false;
} }
break; break;
@@ -470,27 +422,18 @@ bool SetupWizard::ValidateCurrentPage() {
void SetupWizard::UpdateNavigationButtons() { void SetupWizard::UpdateNavigationButtons() {
back_button->setEnabled(current_page > 0); back_button->setEnabled(current_page > 0);
if (current_page == content_stack->count() - 1) { if (current_page == content_stack->count() - 1) {
next_button->setText(tr("Finish")); next_button->setText(tr("Finish"));
} else { } else {
next_button->setText(tr("Next")); next_button->setText(tr("Next"));
} }
// Highlight current step in sidebar
for (int i = 0; i < sidebar_list->count(); ++i) { for (int i = 0; i < sidebar_list->count(); ++i) {
QListWidgetItem* item = sidebar_list->item(i); QListWidgetItem* item = sidebar_list->item(i);
if (i == current_page) { item->setSelected(i == current_page);
item->setSelected(true);
} else {
item->setSelected(false);
}
} }
} }
void SetupWizard::OnInstallationTypeChanged() { void SetupWizard::OnInstallationTypeChanged() {}
// Installation type change will be applied in ApplyConfiguration
}
void SetupWizard::OnSelectKeys() { void SetupWizard::OnSelectKeys() {
const QString key_source_location = QFileDialog::getOpenFileName( const QString key_source_location = QFileDialog::getOpenFileName(
@@ -499,70 +442,49 @@ void SetupWizard::OnSelectKeys() {
if (key_source_location.isEmpty()) { if (key_source_location.isEmpty()) {
return; return;
} }
keys_path = key_source_location; keys_path = key_source_location;
// Copy keys to citron keys directory
const std::filesystem::path prod_key_path = key_source_location.toStdString(); const std::filesystem::path prod_key_path = key_source_location.toStdString();
const std::filesystem::path key_source_path = prod_key_path.parent_path(); const std::filesystem::path key_source_path = prod_key_path.parent_path();
if (!Common::FS::IsDir(key_source_path)) { if (!Common::FS::IsDir(key_source_path)) {
return; return;
} }
bool prod_keys_found = false; bool prod_keys_found = false;
std::vector<std::filesystem::path> source_key_files; std::vector<std::filesystem::path> source_key_files;
if (Common::FS::Exists(prod_key_path)) { if (Common::FS::Exists(prod_key_path)) {
prod_keys_found = true; prod_keys_found = true;
source_key_files.emplace_back(prod_key_path); source_key_files.emplace_back(prod_key_path);
} }
if (Common::FS::Exists(key_source_path / "title.keys")) { if (Common::FS::Exists(key_source_path / "title.keys")) {
source_key_files.emplace_back(key_source_path / "title.keys"); source_key_files.emplace_back(key_source_path / "title.keys");
} }
if (Common::FS::Exists(key_source_path / "key_retail.bin")) { if (Common::FS::Exists(key_source_path / "key_retail.bin")) {
source_key_files.emplace_back(key_source_path / "key_retail.bin"); source_key_files.emplace_back(key_source_path / "key_retail.bin");
} }
if (source_key_files.empty() || !prod_keys_found) { if (source_key_files.empty() || !prod_keys_found) {
QMessageBox::warning(this, tr("Decryption Keys install failed"), QMessageBox::warning(this, tr("Decryption Keys install failed"), tr("prod.keys is a required decryption key file."));
tr("prod.keys is a required decryption key file."));
return; return;
} }
const auto citron_keys_dir = Common::FS::GetCitronPath(Common::FS::CitronPath::KeysDir); const auto citron_keys_dir = Common::FS::GetCitronPath(Common::FS::CitronPath::KeysDir);
for (auto key_file : source_key_files) { for (auto key_file : source_key_files) {
std::filesystem::path destination_key_file = citron_keys_dir / key_file.filename(); std::filesystem::path destination_key_file = citron_keys_dir / key_file.filename();
if (!std::filesystem::copy_file(key_file, destination_key_file, if (!std::filesystem::copy_file(key_file, destination_key_file, std::filesystem::copy_options::overwrite_existing)) {
std::filesystem::copy_options::overwrite_existing)) { LOG_ERROR(Frontend, "Failed to copy file {} to {}", key_file.string(), destination_key_file.string());
LOG_ERROR(Frontend, "Failed to copy file {} to {}", key_file.string(), QMessageBox::critical(this, tr("Decryption Keys install failed"), tr("One or more keys failed to copy."));
destination_key_file.string());
QMessageBox::critical(this, tr("Decryption Keys install failed"),
tr("One or more keys failed to copy."));
return; return;
} }
} }
// Reload keys
Core::Crypto::KeyManager::Instance().ReloadKeys(); Core::Crypto::KeyManager::Instance().ReloadKeys();
if (system.GetFilesystem()) { if (system.GetFilesystem()) {
system.GetFileSystemController().CreateFactories(*system.GetFilesystem()); system.GetFileSystemController().CreateFactories(*system.GetFilesystem());
} }
QMessageBox::information(this, tr("Keys Installed"), tr("Decryption keys have been installed successfully."));
QMessageBox::information(this, tr("Keys Installed"),
tr("Decryption keys have been installed successfully."));
} }
void SetupWizard::OnSelectFirmware() { void SetupWizard::OnSelectFirmware() {
// Check for installed keys first
if (!CheckKeysInstalled()) { if (!CheckKeysInstalled()) {
QMessageBox::information( QMessageBox::information(this, tr("Keys not installed"), tr("Install decryption keys before attempting to install firmware."));
this, tr("Keys not installed"),
tr("Install decryption keys before attempting to install firmware."));
return; return;
} }
QMessageBox msgBox(this); QMessageBox msgBox(this);
msgBox.setWindowTitle(tr("Install Firmware")); msgBox.setWindowTitle(tr("Install Firmware"));
msgBox.setText(tr("Choose firmware installation method:")); msgBox.setText(tr("Choose firmware installation method:"));
@@ -570,33 +492,25 @@ void SetupWizard::OnSelectFirmware() {
QPushButton* folderButton = msgBox.addButton(tr("Select Folder"), QMessageBox::ActionRole); QPushButton* folderButton = msgBox.addButton(tr("Select Folder"), QMessageBox::ActionRole);
QPushButton* zipButton = msgBox.addButton(tr("Select ZIP File"), QMessageBox::ActionRole); QPushButton* zipButton = msgBox.addButton(tr("Select ZIP File"), QMessageBox::ActionRole);
QPushButton* cancelButton = msgBox.addButton(QMessageBox::Cancel); QPushButton* cancelButton = msgBox.addButton(QMessageBox::Cancel);
msgBox.setDefaultButton(zipButton); msgBox.setDefaultButton(zipButton);
msgBox.exec(); msgBox.exec();
QPushButton* clicked = qobject_cast<QPushButton*>(msgBox.clickedButton()); QPushButton* clicked = qobject_cast<QPushButton*>(msgBox.clickedButton());
if (clicked == cancelButton) { if (clicked == cancelButton) {
return; return;
} }
QString firmware_location; QString firmware_location;
bool is_zip = false; bool is_zip = false;
if (clicked == zipButton) { if (clicked == zipButton) {
firmware_location = QFileDialog::getOpenFileName(this, tr("Select Firmware ZIP File"), {}, firmware_location = QFileDialog::getOpenFileName(this, tr("Select Firmware ZIP File"), {}, QStringLiteral("ZIP Files (*.zip)"));
QStringLiteral("ZIP Files (*.zip)"));
is_zip = true; is_zip = true;
} else if (clicked == folderButton) { } else if (clicked == folderButton) {
firmware_location = QFileDialog::getExistingDirectory(this, tr("Select Firmware Folder")); firmware_location = QFileDialog::getExistingDirectory(this, tr("Select Firmware Folder"));
is_zip = false; is_zip = false;
} }
if (firmware_location.isEmpty()) { if (firmware_location.isEmpty()) {
return; return;
} }
firmware_path = firmware_location; firmware_path = firmware_location;
// Actually install the firmware
InstallFirmware(firmware_location, is_zip); InstallFirmware(firmware_location, is_zip);
} }
@@ -609,31 +523,20 @@ void SetupWizard::OnSelectGamesDirectory() {
} }
void SetupWizard::OnSelectScreenshotsPath() { void SetupWizard::OnSelectScreenshotsPath() {
const QString dir_path = const QString dir_path = QFileDialog::getExistingDirectory(this, tr("Select Screenshots Directory"), screenshots_path);
QFileDialog::getExistingDirectory(this, tr("Select Screenshots Directory"), screenshots_path);
if (dir_path.isEmpty()) { if (dir_path.isEmpty()) {
return; return;
} }
screenshots_path = dir_path; screenshots_path = dir_path;
} }
void SetupWizard::OnProfileNameChanged() { void SetupWizard::OnProfileNameChanged() {}
// Profile name change will be applied in ApplyConfiguration
}
void SetupWizard::OnControllerSetup() { void SetupWizard::OnControllerSetup() {
// Open controller configuration dialog QMessageBox::information(this, tr("Controller Setup"), tr("Controller configuration will be available after setup is complete.\nYou can configure your controller from the Settings menu."));
// This would need access to the main window's controller dialog
// For now, we'll just mark that controller setup was attempted
QMessageBox::information(this, tr("Controller Setup"),
tr("Controller configuration will be available after setup is complete.\n"
"You can configure your controller from the Settings menu."));
} }
void SetupWizard::ApplyConfiguration() { void SetupWizard::ApplyConfiguration() {
// Apply installation type (portable vs standard)
// Note: Portable mode is automatically detected by the presence of a "user" folder
// in the executable directory. We just need to create it if it doesn't exist.
if (is_portable_mode) { if (is_portable_mode) {
#ifdef _WIN32 #ifdef _WIN32
const auto exe_dir = Common::FS::GetExeDirectory(); const auto exe_dir = Common::FS::GetExeDirectory();
@@ -641,35 +544,25 @@ void SetupWizard::ApplyConfiguration() {
if (!Common::FS::Exists(portable_path)) { if (!Common::FS::Exists(portable_path)) {
void(Common::FS::CreateDirs(Common::FS::PathToUTF8String(portable_path))); void(Common::FS::CreateDirs(Common::FS::PathToUTF8String(portable_path)));
} }
Common::FS::SetCitronPath(Common::FS::CitronPath::CitronDir, Common::FS::SetCitronPath(Common::FS::CitronPath::CitronDir, Common::FS::PathToUTF8String(portable_path));
Common::FS::PathToUTF8String(portable_path));
#else #else
const auto current_dir = std::filesystem::current_path(); const auto current_dir = std::filesystem::current_path();
const auto portable_path = current_dir / "user"; const auto portable_path = current_dir / "user";
if (!Common::FS::Exists(portable_path)) { if (!Common::FS::Exists(portable_path)) {
void(Common::FS::CreateDirs(Common::FS::PathToUTF8String(portable_path))); void(Common::FS::CreateDirs(Common::FS::PathToUTF8String(portable_path)));
} }
Common::FS::SetCitronPath(Common::FS::CitronPath::CitronDir, Common::FS::SetCitronPath(Common::FS::CitronPath::CitronDir, Common::FS::PathToUTF8String(portable_path));
Common::FS::PathToUTF8String(portable_path));
#endif #endif
} }
// Standard mode uses default paths, so no change needed
// Apply screenshots path
if (!screenshots_path.isEmpty()) { if (!screenshots_path.isEmpty()) {
Common::FS::SetCitronPath(Common::FS::CitronPath::ScreenshotsDir, Common::FS::SetCitronPath(Common::FS::CitronPath::ScreenshotsDir, screenshots_path.toStdString());
screenshots_path.toStdString());
} }
// Apply games directory
if (!games_directory.isEmpty()) { if (!games_directory.isEmpty()) {
UISettings::GameDir game_dir{games_directory.toStdString(), false, true}; UISettings::GameDir game_dir{games_directory.toStdString(), false, true};
if (!UISettings::values.game_dirs.contains(game_dir)) { if (!UISettings::values.game_dirs.contains(game_dir)) {
UISettings::values.game_dirs.append(game_dir); UISettings::values.game_dirs.append(game_dir);
} }
} }
// Apply profile name
if (!profile_name.isEmpty() && profile_name != QStringLiteral("citron")) { if (!profile_name.isEmpty() && profile_name != QStringLiteral("citron")) {
auto& profile_manager = system.GetProfileManager(); auto& profile_manager = system.GetProfileManager();
const auto current_user_index = Settings::values.current_user.GetValue(); const auto current_user_index = Settings::values.current_user.GetValue();
@@ -685,15 +578,9 @@ void SetupWizard::ApplyConfiguration() {
} }
} }
} }
// Mark setup as complete
UISettings::values.first_start = false; UISettings::values.first_start = false;
// Save all configuration
if (main_window) { if (main_window) {
main_window->OnSaveConfig(); main_window->OnSaveConfig();
// Refresh game list to show newly added directories
main_window->RefreshGameList(); main_window->RefreshGameList();
} }
} }
@@ -703,8 +590,6 @@ bool SetupWizard::CheckKeysInstalled() const {
} }
bool SetupWizard::CheckFirmwareInstalled() const { bool SetupWizard::CheckFirmwareInstalled() const {
// Check if firmware is installed by checking for system content
// This is a simplified check
try { try {
return system.GetFileSystemController().GetSystemNANDContentDirectory() != nullptr; return system.GetFileSystemController().GetSystemNANDContentDirectory() != nullptr;
} catch (...) { } catch (...) {
@@ -716,169 +601,125 @@ void SetupWizard::InstallFirmware(const QString& firmware_path_param, bool is_zi
if (!main_window) { if (!main_window) {
return; return;
} }
QProgressDialog progress(tr("Installing Firmware..."), tr("Cancel"), 0, 100, this); QProgressDialog progress(tr("Installing Firmware..."), tr("Cancel"), 0, 100, this);
progress.setWindowModality(Qt::WindowModal); progress.setWindowModality(Qt::WindowModal);
progress.setMinimumDuration(100); progress.setMinimumDuration(100);
progress.setAutoClose(false); progress.setAutoClose(false);
progress.setAutoReset(false); progress.setAutoReset(false);
progress.show(); progress.show();
auto QtProgressCallback = [&](size_t total_size, size_t processed_size) { auto QtProgressCallback = [&](size_t total_size, size_t processed_size) {
progress.setValue(static_cast<int>((processed_size * 100) / total_size)); progress.setValue(static_cast<int>((processed_size * 100) / total_size));
QApplication::processEvents(); QApplication::processEvents();
return progress.wasCanceled(); return progress.wasCanceled();
}; };
std::filesystem::path firmware_source_path; std::filesystem::path firmware_source_path;
std::filesystem::path temp_extract_path; std::filesystem::path temp_extract_path;
if (is_zip) { if (is_zip) {
// Extract ZIP to temp directory
temp_extract_path = std::filesystem::temp_directory_path() / "citron_firmware_temp"; temp_extract_path = std::filesystem::temp_directory_path() / "citron_firmware_temp";
if (std::filesystem::exists(temp_extract_path)) { if (std::filesystem::exists(temp_extract_path)) {
std::filesystem::remove_all(temp_extract_path); std::filesystem::remove_all(temp_extract_path);
} }
progress.setLabelText(tr("Extracting firmware ZIP...")); progress.setLabelText(tr("Extracting firmware ZIP..."));
QtProgressCallback(100, 5); QtProgressCallback(100, 5);
// Use main window's ExtractZipToDirectory
if (!main_window->ExtractZipToDirectoryPublic(firmware_path_param.toStdString(), temp_extract_path)) { if (!main_window->ExtractZipToDirectoryPublic(firmware_path_param.toStdString(), temp_extract_path)) {
progress.close(); progress.close();
std::filesystem::remove_all(temp_extract_path); std::filesystem::remove_all(temp_extract_path);
QMessageBox::critical(this, tr("Firmware install failed"), QMessageBox::critical(this, tr("Firmware install failed"), tr("Failed to extract firmware ZIP file."));
tr("Failed to extract firmware ZIP file."));
return; return;
} }
firmware_source_path = temp_extract_path; firmware_source_path = temp_extract_path;
QtProgressCallback(100, 15); QtProgressCallback(100, 15);
} else { } else {
firmware_source_path = firmware_path_param.toStdString(); firmware_source_path = firmware_path_param.toStdString();
QtProgressCallback(100, 10); QtProgressCallback(100, 10);
} }
// Find .nca files
std::vector<std::filesystem::path> nca_files; std::vector<std::filesystem::path> nca_files;
const Common::FS::DirEntryCallable callback = const Common::FS::DirEntryCallable callback = [&nca_files](const std::filesystem::directory_entry& entry) {
[&nca_files](const std::filesystem::directory_entry& entry) { if (entry.path().has_extension() && entry.path().extension() == ".nca") {
if (entry.path().has_extension() && entry.path().extension() == ".nca") { nca_files.emplace_back(entry.path());
nca_files.emplace_back(entry.path()); }
} return true;
return true; };
};
Common::FS::IterateDirEntries(firmware_source_path, callback, Common::FS::DirEntryFilter::File); Common::FS::IterateDirEntries(firmware_source_path, callback, Common::FS::DirEntryFilter::File);
if (nca_files.empty()) { if (nca_files.empty()) {
progress.close(); progress.close();
if (is_zip) { if (is_zip) {
std::filesystem::remove_all(temp_extract_path); std::filesystem::remove_all(temp_extract_path);
} }
QMessageBox::warning(this, tr("Firmware install failed"), QMessageBox::warning(this, tr("Firmware install failed"), tr("Unable to locate firmware NCA files."));
tr("Unable to locate firmware NCA files."));
return; return;
} }
QtProgressCallback(100, 20); QtProgressCallback(100, 20);
// Get system NAND content directory
auto sysnand_content_vdir = system.GetFileSystemController().GetSystemNANDContentDirectory(); auto sysnand_content_vdir = system.GetFileSystemController().GetSystemNANDContentDirectory();
if (!sysnand_content_vdir) { if (!sysnand_content_vdir) {
progress.close(); progress.close();
if (is_zip) { if (is_zip) {
std::filesystem::remove_all(temp_extract_path); std::filesystem::remove_all(temp_extract_path);
} }
QMessageBox::critical(this, tr("Firmware install failed"), QMessageBox::critical(this, tr("Firmware install failed"), tr("Failed to access system NAND directory."));
tr("Failed to access system NAND directory."));
return; return;
} }
// Clean existing firmware
if (!sysnand_content_vdir->CleanSubdirectoryRecursive("registered")) { if (!sysnand_content_vdir->CleanSubdirectoryRecursive("registered")) {
progress.close(); progress.close();
if (is_zip) { if (is_zip) {
std::filesystem::remove_all(temp_extract_path); std::filesystem::remove_all(temp_extract_path);
} }
QMessageBox::critical(this, tr("Firmware install failed"), QMessageBox::critical(this, tr("Firmware install failed"), tr("Failed to clean existing firmware files."));
tr("Failed to clean existing firmware files."));
return; return;
} }
QtProgressCallback(100, 25); QtProgressCallback(100, 25);
auto firmware_vdir = sysnand_content_vdir->GetDirectoryRelative("registered"); auto firmware_vdir = sysnand_content_vdir->GetDirectoryRelative("registered");
if (!firmware_vdir) { if (!firmware_vdir) {
progress.close(); progress.close();
if (is_zip) { if (is_zip) {
std::filesystem::remove_all(temp_extract_path); std::filesystem::remove_all(temp_extract_path);
} }
QMessageBox::critical(this, tr("Firmware install failed"), QMessageBox::critical(this, tr("Firmware install failed"), tr("Failed to create firmware directory."));
tr("Failed to create firmware directory."));
return; return;
} }
// Copy firmware files
auto vfs = system.GetFilesystem(); auto vfs = system.GetFilesystem();
if (!vfs) { if (!vfs) {
progress.close(); progress.close();
if (is_zip) { if (is_zip) {
std::filesystem::remove_all(temp_extract_path); std::filesystem::remove_all(temp_extract_path);
} }
QMessageBox::critical(this, tr("Firmware install failed"), QMessageBox::critical(this, tr("Firmware install failed"), tr("Failed to access virtual filesystem."));
tr("Failed to access virtual filesystem."));
return; return;
} }
bool success = true; bool success = true;
int i = 0; int i = 0;
for (const auto& nca_path : nca_files) { for (const auto& nca_path : nca_files) {
i++; i++;
auto src_file = vfs->OpenFile(nca_path.generic_string(), FileSys::OpenMode::Read); auto src_file = vfs->OpenFile(nca_path.generic_string(), FileSys::OpenMode::Read);
auto dst_file = firmware_vdir->CreateFileRelative(nca_path.filename().string()); auto dst_file = firmware_vdir->CreateFileRelative(nca_path.filename().string());
if (!src_file || !dst_file) { if (!src_file || !dst_file) {
LOG_ERROR(Frontend, "Failed to open firmware file: {}", nca_path.string()); LOG_ERROR(Frontend, "Failed to open firmware file: {}", nca_path.string());
success = false; success = false;
continue; continue;
} }
if (!FileSys::VfsRawCopy(src_file, dst_file)) { if (!FileSys::VfsRawCopy(src_file, dst_file)) {
LOG_ERROR(Frontend, "Failed to copy firmware file: {}", nca_path.string()); LOG_ERROR(Frontend, "Failed to copy firmware file: {}", nca_path.string());
success = false; success = false;
} }
if (QtProgressCallback(100, 25 + static_cast<int>((i * 60) / nca_files.size()))) { if (QtProgressCallback(100, 25 + static_cast<int>((i * 60) / nca_files.size()))) {
progress.close(); progress.close();
if (is_zip) { if (is_zip) {
std::filesystem::remove_all(temp_extract_path); std::filesystem::remove_all(temp_extract_path);
} }
QMessageBox::warning(this, tr("Firmware install cancelled"), QMessageBox::warning(this, tr("Firmware install cancelled"), tr("Firmware installation was cancelled."));
tr("Firmware installation was cancelled."));
return; return;
} }
} }
// Clean up temp directory
if (is_zip) { if (is_zip) {
std::filesystem::remove_all(temp_extract_path); std::filesystem::remove_all(temp_extract_path);
} }
if (!success) { if (!success) {
progress.close(); progress.close();
QMessageBox::critical(this, tr("Firmware install failed"), QMessageBox::critical(this, tr("Firmware install failed"), tr("One or more firmware files failed to copy."));
tr("One or more firmware files failed to copy."));
return; return;
} }
// Re-scan VFS
system.GetFileSystemController().CreateFactories(*vfs); system.GetFileSystemController().CreateFactories(*vfs);
// Note: Verification would require access to the provider which is private in GMainWindow
// The firmware files have been successfully copied, so installation is complete
progress.close(); progress.close();
QMessageBox::information(this, tr("Firmware installed successfully"), QMessageBox::information(this, tr("Firmware installed successfully"), tr("The firmware has been installed successfully."));
tr("The firmware has been installed successfully."));
firmware_installed = true; firmware_installed = true;
} }

View File

@@ -4,6 +4,7 @@
#pragma once #pragma once
#include <memory> #include <memory>
#include <QColor>
#include <QDialog> #include <QDialog>
#include <QListWidget> #include <QListWidget>
#include <QStackedWidget> #include <QStackedWidget>
@@ -36,6 +37,9 @@ public:
Page_Completion = 8, Page_Completion = 8,
}; };
protected:
void changeEvent(QEvent* event) override;
private slots: private slots:
void OnPageChanged(int index); void OnPageChanged(int index);
void OnNextClicked(); void OnNextClicked();
@@ -50,7 +54,6 @@ private slots:
void OnControllerSetup(); void OnControllerSetup();
private: private:
void SetupUI();
void SetupPages(); void SetupPages();
void ApplyConfiguration(); void ApplyConfiguration();
void InstallFirmware(const QString& firmware_path, bool is_zip); void InstallFirmware(const QString& firmware_path, bool is_zip);
@@ -58,6 +61,7 @@ private:
bool CheckFirmwareInstalled() const; bool CheckFirmwareInstalled() const;
void UpdateNavigationButtons(); void UpdateNavigationButtons();
bool ValidateCurrentPage(); bool ValidateCurrentPage();
void UpdateTheme();
std::unique_ptr<Ui::SetupWizard> ui; std::unique_ptr<Ui::SetupWizard> ui;
QListWidget* sidebar_list; QListWidget* sidebar_list;
@@ -76,4 +80,5 @@ private:
QString screenshots_path; QString screenshots_path;
QString profile_name; QString profile_name;
bool firmware_installed; bool firmware_installed;
QColor last_palette_text_color;
}; };

View File

@@ -19,6 +19,74 @@
<property name="windowTitle"> <property name="windowTitle">
<string>citron Setup Wizard</string> <string>citron Setup Wizard</string>
</property> </property>
<property name="templateStyleSheet" stdset="0">
<string>QDialog {
background-color: %%BACKGROUND_COLOR%%;
}
/* Sidebar List */
QListWidget {
background-color: %%SECONDARY_BG_COLOR%%;
border: none;
}
QListWidget::item {
color: %%TEXT_COLOR%%;
padding: 10px;
}
QListWidget::item:selected {
background-color: %%TERTIARY_BG_COLOR%%;
font-weight: bold;
}
QListWidget::item:hover {
background-color: %%HOVER_BG_COLOR%%;
}
/* Content Stack &amp; General Widgets */
QStackedWidget, QGroupBox {
background-color: %%BACKGROUND_COLOR%%;
}
QLabel, QRadioButton, QCheckBox {
color: %%TEXT_COLOR%%;
background-color: transparent;
}
QLabel[class="wizard-title"] {
font-size: 18px;
font-weight: bold;
}
QLabel[class="wizard-status-success"] {
color: #4caf50;
}
QLabel[class="wizard-status-default"] {
color: %%DISABLED_TEXT_COLOR%%;
}
/* Buttons */
QPushButton {
background-color: %%BUTTON_BG_COLOR%%;
color: %%TEXT_COLOR%%;
border: 1px solid %%TERTIARY_BG_COLOR%%;
padding: 8px 16px;
border-radius: 4px;
}
QPushButton:hover {
background-color: %%HOVER_BG_COLOR%%;
}
QPushButton:disabled {
background-color: %%SECONDARY_BG_COLOR%%;
color: %%DISABLED_TEXT_COLOR%%;
}
/* Inputs */
QLineEdit {
color: %%TEXT_COLOR%%;
background-color: %%SECONDARY_BG_COLOR%%;
border: 1px solid %%TERTIARY_BG_COLOR%%;
padding: 5px;
border-radius: 4px;
}
</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout"> <layout class="QVBoxLayout" name="verticalLayout">
<item> <item>
<layout class="QHBoxLayout" name="mainLayout"> <layout class="QHBoxLayout" name="mainLayout">
@@ -37,32 +105,14 @@
</size> </size>
</property> </property>
<property name="styleSheet"> <property name="styleSheet">
<string>QListWidget { <string notr="true"/>
background-color: #2b2b2b;
border: none;
outline: none;
}
QListWidget::item {
padding: 8px;
border: none;
color: #cccccc;
}
QListWidget::item:selected {
background-color: #3d3d3d;
color: #ffffff;
}
QListWidget::item:hover {
background-color: #353535;
}</string>
</property> </property>
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QStackedWidget" name="contentStack"> <widget class="QStackedWidget" name="contentStack">
<property name="styleSheet"> <property name="styleSheet">
<string>QStackedWidget { <string notr="true"/>
background-color: #1e1e1e;
}</string>
</property> </property>
</widget> </widget>
</item> </item>

View File

@@ -125,7 +125,7 @@ void UpdaterService::CheckForUpdates() {
return; return;
} }
QSettings settings; QSettings settings;
QString channel = settings.value(QStringLiteral("updater/channel"), QStringLiteral("Stable")).toString(); QString channel = settings.value(QStringLiteral("updater/channel"), QStringLiteral("Nightly")).toString();
std::string update_url = (channel == QStringLiteral("Nightly")) ? NIGHTLY_UPDATE_URL : STABLE_UPDATE_URL; std::string update_url = (channel == QStringLiteral("Nightly")) ? NIGHTLY_UPDATE_URL : STABLE_UPDATE_URL;
LOG_INFO(Frontend, "Selected update channel: {}", channel.toStdString()); LOG_INFO(Frontend, "Selected update channel: {}", channel.toStdString());
LOG_INFO(Frontend, "Checking for updates from: {}", update_url); LOG_INFO(Frontend, "Checking for updates from: {}", update_url);
@@ -414,16 +414,24 @@ void UpdaterService::ParseUpdateResponse(const QByteArray& response, const QStri
for (const QJsonValue& asset_value : assets) { for (const QJsonValue& asset_value : assets) {
QJsonObject asset_obj = asset_value.toObject(); QJsonObject asset_obj = asset_value.toObject();
QString asset_name = asset_obj.value(QStringLiteral("name")).toString(); QString asset_name = asset_obj.value(QStringLiteral("name")).toString();
#if defined(__linux__)
#if defined(__linux__)
if (asset_name.endsWith(QStringLiteral(".AppImage"))) { if (asset_name.endsWith(QStringLiteral(".AppImage"))) {
#else
if (asset_name.endsWith(QStringLiteral(".zip"))) {
#endif
DownloadOption option; DownloadOption option;
option.name = asset_name.toStdString(); option.name = asset_name.toStdString();
option.url = asset_obj.value(QStringLiteral("browser_download_url")).toString().toStdString(); option.url = asset_obj.value(QStringLiteral("browser_download_url")).toString().toStdString();
update_info.download_options.push_back(option); update_info.download_options.push_back(option);
} }
#elif defined(_WIN32)
// For Windows, find the .zip file but explicitly skip PGO builds.
if (asset_name.endsWith(QStringLiteral(".zip")) && !asset_name.contains(QStringLiteral("PGO"), Qt::CaseInsensitive)) {
DownloadOption option;
option.name = asset_name.toStdString();
option.url = asset_obj.value(QStringLiteral("browser_download_url")).toString().toStdString();
update_info.download_options.push_back(option);
}
#endif
} }
if (!update_info.download_options.empty()) { if (!update_info.download_options.empty()) {

View File

@@ -40,22 +40,18 @@ static VkPresentModeKHR ChooseSwapPresentMode(bool has_imm, bool has_mailbox,
bool has_fifo_relaxed) { bool has_fifo_relaxed) {
// Wayland-specific optimizations for low-latency presentation. // Wayland-specific optimizations for low-latency presentation.
if (Settings::values.is_wayland_platform.GetValue()) { if (Settings::values.is_wayland_platform.GetValue()) {
LOG_INFO(Render_Vulkan, "Wayland platform detected. Prioritizing low-latency presentation modes.");
// On Wayland, Mailbox is strongly preferred for smooth, low-latency rendering. // On Wayland, Mailbox is strongly preferred for smooth, low-latency rendering.
if (has_mailbox) { if (has_mailbox) {
LOG_INFO(Render_Vulkan, "Using Mailbox presentation mode for Wayland.");
return VK_PRESENT_MODE_MAILBOX_KHR; return VK_PRESENT_MODE_MAILBOX_KHR;
} }
// Allow Immediate for lowest latency if the user explicitly chooses it. // Allow Immediate for lowest latency if the user explicitly chooses it.
if (has_imm && Settings::values.vsync_mode.GetValue() == Settings::VSyncMode::Immediate) { if (has_imm && Settings::values.vsync_mode.GetValue() == Settings::VSyncMode::Immediate) {
LOG_INFO(Render_Vulkan, "Using Immediate presentation mode for Wayland (tearing may occur).");
return VK_PRESENT_MODE_IMMEDIATE_KHR; return VK_PRESENT_MODE_IMMEDIATE_KHR;
} }
// Fallback to standard FIFO (V-Sync) if Mailbox is not available. // Fallback to standard FIFO (V-Sync) if Mailbox is not available.
LOG_INFO(Render_Vulkan, "Mailbox not available. Falling back to FIFO presentation mode for Wayland.");
return VK_PRESENT_MODE_FIFO_KHR; return VK_PRESENT_MODE_FIFO_KHR;
} }