diff --git a/src/citron/setup_wizard.cpp b/src/citron/setup_wizard.cpp index 92c3d6cec..a25c6715b 100644 --- a/src/citron/setup_wizard.cpp +++ b/src/citron/setup_wizard.cpp @@ -3,6 +3,7 @@ #include "citron/setup_wizard.h" #include +#include #include #include #include @@ -14,20 +15,20 @@ #include #include #include -#include #include "common/fs/fs.h" #include "common/fs/path_util.h" #include "common/logging/log.h" #include "common/settings.h" #include "core/crypto/key_manager.h" -#include "core/hle/service/acc/profile_manager.h" #include "core/file_sys/vfs/vfs.h" +#include "core/hle/service/acc/profile_manager.h" #include "frontend_common/content_manager.h" #include "ui_setup_wizard.h" -#include "citron/uisettings.h" #include "citron/configuration/configure_input.h" #include "citron/main.h" +#include "citron/theme.h" +#include "citron/uisettings.h" #ifdef CITRON_ENABLE_LIBARCHIVE #include @@ -35,10 +36,29 @@ #endif #ifdef _WIN32 -#include #include +#include #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) : QDialog(parent), ui{std::make_unique()}, system{system_}, 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")); - // Set window flags before setting modality setWindowFlags(Qt::Dialog | Qt::WindowTitleHint | Qt::WindowCloseButtonHint | Qt::WindowSystemMenuHint | Qt::WindowStaysOnTopHint); - - // Set window modality to NonModal to allow interaction with the main window. setWindowModality(Qt::NonModal); - // Get UI elements from the .ui file sidebar_list = ui->sidebarList; content_stack = ui->contentStack; back_button = ui->backButton; next_button = ui->nextButton; cancel_button = ui->cancelButton; - SetupUI(); SetupPages(); - // Connect signals connect(back_button, &QPushButton::clicked, this, &SetupWizard::OnBackClicked); connect(next_button, &QPushButton::clicked, this, &SetupWizard::OnNextClicked); 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; content_stack->setCurrentIndex(0); UpdateNavigationButtons(); + + last_palette_text_color = qApp->palette().color(QPalette::WindowText); + UpdateTheme(); } 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() { // Welcome page auto* welcome_page = new QWidget(); @@ -104,16 +109,14 @@ void SetupWizard::SetupPages() { welcome_layout->setSpacing(20); 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); 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.")); - welcome_text->setStyleSheet(QStringLiteral("color: #cccccc; font-size: 12px;")); welcome_text->setWordWrap(true); welcome_layout->addWidget(welcome_text); welcome_layout->addStretch(); - content_stack->addWidget(welcome_page); sidebar_list->addItem(tr("Welcome")); @@ -124,56 +127,39 @@ void SetupWizard::SetupPages() { install_layout->setSpacing(20); 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); 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); 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* button_group = new QButtonGroup(this); 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; #ifdef _WIN32 const auto appdata_path = Common::FS::GetAppDataRoamingDirectory(); const auto appdata_str = QString::fromStdString(Common::FS::PathToUTF8String(appdata_path)); standard_path_text = tr("Standard (uses %APPDATA%\\citron)").arg(appdata_str); #elif defined(__APPLE__) - // macOS uses ~/Library/Application Support/citron standard_path_text = tr("Standard (uses ~/Library/Application Support/citron)"); #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_str = QString::fromStdString(Common::FS::PathToUTF8String(data_path)); standard_path_text = tr("Standard (uses %1/citron)").arg(data_path_str); #endif - auto* standard_radio = new QRadioButton(standard_path_text); - standard_radio->setStyleSheet(QStringLiteral("color: #cccccc;")); standard_radio->setChecked(true); - button_group->addButton(portable_radio, 0); button_group->addButton(standard_radio, 1); - install_group_layout->addWidget(portable_radio); install_group_layout->addWidget(standard_radio); install_layout->addWidget(install_group); install_layout->addStretch(); - - connect(portable_radio, &QRadioButton::toggled, this, [this](bool checked) { - if (checked) is_portable_mode = true; - }); - connect(standard_radio, &QRadioButton::toggled, this, [this](bool checked) { - if (checked) is_portable_mode = false; - }); - + connect(portable_radio, &QRadioButton::toggled, this, [this](bool checked) { 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); sidebar_list->addItem(tr("Installation Type")); @@ -182,34 +168,25 @@ void SetupWizard::SetupPages() { auto* keys_layout = new QVBoxLayout(keys_page); keys_layout->setContentsMargins(40, 40, 40, 40); keys_layout->setSpacing(20); - 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); - - 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;")); + auto* keys_text = new QLabel(tr("Decryption keys are required to run encrypted games.\nSelect your prod.keys file to install them.")); keys_text->setWordWrap(true); keys_layout->addWidget(keys_text); - auto* keys_button = new QPushButton(tr("Select Keys File")); - keys_button->setStyleSheet(QStringLiteral("color: #ffffff;")); connect(keys_button, &QPushButton::clicked, this, &SetupWizard::OnSelectKeys); keys_layout->addWidget(keys_button); - auto* keys_status = new QLabel(); - keys_status->setStyleSheet(QStringLiteral("color: #aaaaaa; font-size: 11px;")); keys_layout->addWidget(keys_status); - if (CheckKeysInstalled()) { 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 { keys_status->setText(tr("Keys not installed")); + keys_status->setProperty("class", QStringLiteral("wizard-status-default")); } keys_layout->addStretch(); - content_stack->addWidget(keys_page); sidebar_list->addItem(tr("Keys")); @@ -218,34 +195,25 @@ void SetupWizard::SetupPages() { auto* firmware_layout = new QVBoxLayout(firmware_page); firmware_layout->setContentsMargins(40, 40, 40, 40); firmware_layout->setSpacing(20); - 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); - - 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;")); + 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.")); firmware_text->setWordWrap(true); firmware_layout->addWidget(firmware_text); - auto* firmware_button = new QPushButton(tr("Install Firmware")); - firmware_button->setStyleSheet(QStringLiteral("color: #ffffff;")); connect(firmware_button, &QPushButton::clicked, this, &SetupWizard::OnSelectFirmware); firmware_layout->addWidget(firmware_button); - auto* firmware_status = new QLabel(); - firmware_status->setStyleSheet(QStringLiteral("color: #aaaaaa; font-size: 11px;")); firmware_layout->addWidget(firmware_status); - if (CheckFirmwareInstalled() || firmware_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 { firmware_status->setText(tr("Firmware not installed (optional)")); + firmware_status->setProperty("class", QStringLiteral("wizard-status-default")); } firmware_layout->addStretch(); - content_stack->addWidget(firmware_page); sidebar_list->addItem(tr("Firmware")); @@ -254,28 +222,21 @@ void SetupWizard::SetupPages() { auto* games_layout = new QVBoxLayout(games_page); games_layout->setContentsMargins(40, 40, 40, 40); games_layout->setSpacing(20); - 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); - 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_layout->addWidget(games_text); - auto* games_path_layout = new QHBoxLayout(); 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->setPlaceholderText(tr("No directory selected")); if (!games_directory.isEmpty()) { games_path_edit->setText(games_directory); } games_path_layout->addWidget(games_path_edit); - auto* games_button = new QPushButton(tr("Browse...")); - games_button->setStyleSheet(QStringLiteral("color: #ffffff;")); connect(games_button, &QPushButton::clicked, this, [this, games_path_edit]() { OnSelectGamesDirectory(); games_path_edit->setText(games_directory); @@ -283,7 +244,6 @@ void SetupWizard::SetupPages() { games_path_layout->addWidget(games_button); games_layout->addLayout(games_path_layout); games_layout->addStretch(); - content_stack->addWidget(games_page); sidebar_list->addItem(tr("Games Directory")); @@ -292,32 +252,23 @@ void SetupWizard::SetupPages() { auto* paths_layout = new QVBoxLayout(paths_page); paths_layout->setContentsMargins(40, 40, 40, 40); paths_layout->setSpacing(20); - 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); - 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_layout->addWidget(paths_text); - auto* screenshots_label = new QLabel(tr("Screenshots Directory:")); - screenshots_label->setStyleSheet(QStringLiteral("color: #cccccc; font-size: 12px;")); paths_layout->addWidget(screenshots_label); - auto* screenshots_path_layout = new QHBoxLayout(); 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->setPlaceholderText(tr("Default location")); if (!screenshots_path.isEmpty()) { screenshots_path_edit->setText(screenshots_path); } screenshots_path_layout->addWidget(screenshots_path_edit); - auto* screenshots_button = new QPushButton(tr("Browse...")); - screenshots_button->setStyleSheet(QStringLiteral("color: #ffffff;")); connect(screenshots_button, &QPushButton::clicked, this, [this, screenshots_path_edit]() { OnSelectScreenshotsPath(); screenshots_path_edit->setText(screenshots_path); @@ -325,7 +276,6 @@ void SetupWizard::SetupPages() { screenshots_path_layout->addWidget(screenshots_button); paths_layout->addLayout(screenshots_path_layout); paths_layout->addStretch(); - content_stack->addWidget(paths_page); sidebar_list->addItem(tr("Paths")); @@ -334,26 +284,18 @@ void SetupWizard::SetupPages() { auto* profile_layout = new QVBoxLayout(profile_page); profile_layout->setContentsMargins(40, 40, 40, 40); profile_layout->setSpacing(20); - 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); - 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_layout->addWidget(profile_text); - 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->setText(profile_name); - connect(profile_edit, &QLineEdit::textChanged, this, [this](const QString& text) { - profile_name = text; - }); + connect(profile_edit, &QLineEdit::textChanged, this, [this](const QString& text) { profile_name = text; }); profile_layout->addWidget(profile_edit); profile_layout->addStretch(); - content_stack->addWidget(profile_page); sidebar_list->addItem(tr("Profile")); @@ -362,23 +304,16 @@ void SetupWizard::SetupPages() { auto* controller_layout = new QVBoxLayout(controller_page); controller_layout->setContentsMargins(40, 40, 40, 40); controller_layout->setSpacing(20); - 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); - - 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;")); + 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.")); controller_text->setWordWrap(true); controller_layout->addWidget(controller_text); - auto* controller_button = new QPushButton(tr("Open Controller Settings")); - controller_button->setStyleSheet(QStringLiteral("color: #ffffff;")); connect(controller_button, &QPushButton::clicked, this, &SetupWizard::OnControllerSetup); controller_layout->addWidget(controller_button); controller_layout->addStretch(); - content_stack->addWidget(controller_page); sidebar_list->addItem(tr("Controller")); @@ -387,29 +322,53 @@ void SetupWizard::SetupPages() { auto* completion_layout = new QVBoxLayout(completion_page); completion_layout->setContentsMargins(40, 40, 40, 40); completion_layout->setSpacing(20); - 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); - - 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;")); + auto* completion_text = new QLabel(tr("You have completed the setup wizard.\nClick Finish to apply your settings and start using citron.")); completion_text->setWordWrap(true); completion_layout->addWidget(completion_text); completion_layout->addStretch(); - content_stack->addWidget(completion_page); 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) { if (index >= 0 && index < content_stack->count()) { content_stack->setCurrentIndex(index); current_page = index; UpdateNavigationButtons(); - - // Update sidebar selection sidebar_list->setCurrentRow(index); } } @@ -420,7 +379,6 @@ void SetupWizard::OnNextClicked() { current_page++; OnPageChanged(current_page); } else { - // Finished ApplyConfiguration(); accept(); } @@ -435,30 +393,24 @@ void SetupWizard::OnBackClicked() { } void SetupWizard::OnCancelClicked() { - if (QMessageBox::question(this, tr("Cancel Setup"), - tr("Are you sure you want to cancel the setup wizard?"), - QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { + if (QMessageBox::question(this, tr("Cancel Setup"), tr("Are you sure you want to cancel the setup wizard?"), QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { reject(); } } bool SetupWizard::ValidateCurrentPage() { - switch (current_page) { + switch (static_cast(current_page)) { case Page_Keys: if (!CheckKeysInstalled()) { - QMessageBox::warning(this, tr("Keys Required"), - tr("Please install decryption keys before continuing.\n" - "Keys are required to run encrypted games.")); + QMessageBox::warning(this, tr("Keys Required"), tr("Please install decryption keys before continuing.\nKeys are required to run encrypted games.")); return false; } break; case Page_Firmware: - // Firmware is optional, so we always allow proceeding break; case Page_GamesDirectory: if (games_directory.isEmpty()) { - QMessageBox::warning(this, tr("Games Directory Required"), - tr("Please select a games directory before continuing.")); + QMessageBox::warning(this, tr("Games Directory Required"), tr("Please select a games directory before continuing.")); return false; } break; @@ -470,27 +422,18 @@ bool SetupWizard::ValidateCurrentPage() { void SetupWizard::UpdateNavigationButtons() { back_button->setEnabled(current_page > 0); - if (current_page == content_stack->count() - 1) { next_button->setText(tr("Finish")); } else { next_button->setText(tr("Next")); } - - // Highlight current step in sidebar for (int i = 0; i < sidebar_list->count(); ++i) { QListWidgetItem* item = sidebar_list->item(i); - if (i == current_page) { - item->setSelected(true); - } else { - item->setSelected(false); - } + item->setSelected(i == current_page); } } -void SetupWizard::OnInstallationTypeChanged() { - // Installation type change will be applied in ApplyConfiguration -} +void SetupWizard::OnInstallationTypeChanged() {} void SetupWizard::OnSelectKeys() { const QString key_source_location = QFileDialog::getOpenFileName( @@ -499,70 +442,49 @@ void SetupWizard::OnSelectKeys() { if (key_source_location.isEmpty()) { return; } - 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 key_source_path = prod_key_path.parent_path(); if (!Common::FS::IsDir(key_source_path)) { return; } - bool prod_keys_found = false; std::vector source_key_files; - if (Common::FS::Exists(prod_key_path)) { prod_keys_found = true; source_key_files.emplace_back(prod_key_path); } - if (Common::FS::Exists(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")) { source_key_files.emplace_back(key_source_path / "key_retail.bin"); } - if (source_key_files.empty() || !prod_keys_found) { - QMessageBox::warning(this, tr("Decryption Keys install failed"), - tr("prod.keys is a required decryption key file.")); + QMessageBox::warning(this, tr("Decryption Keys install failed"), tr("prod.keys is a required decryption key file.")); return; } - const auto citron_keys_dir = Common::FS::GetCitronPath(Common::FS::CitronPath::KeysDir); for (auto key_file : source_key_files) { std::filesystem::path destination_key_file = citron_keys_dir / key_file.filename(); - if (!std::filesystem::copy_file(key_file, destination_key_file, - std::filesystem::copy_options::overwrite_existing)) { - LOG_ERROR(Frontend, "Failed to copy file {} to {}", key_file.string(), - destination_key_file.string()); - QMessageBox::critical(this, tr("Decryption Keys install failed"), - tr("One or more keys failed to copy.")); + if (!std::filesystem::copy_file(key_file, destination_key_file, std::filesystem::copy_options::overwrite_existing)) { + LOG_ERROR(Frontend, "Failed to copy file {} to {}", key_file.string(), destination_key_file.string()); + QMessageBox::critical(this, tr("Decryption Keys install failed"), tr("One or more keys failed to copy.")); return; } } - - // Reload keys Core::Crypto::KeyManager::Instance().ReloadKeys(); if (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() { - // Check for installed keys first if (!CheckKeysInstalled()) { - QMessageBox::information( - this, tr("Keys not installed"), - tr("Install decryption keys before attempting to install firmware.")); + QMessageBox::information(this, tr("Keys not installed"), tr("Install decryption keys before attempting to install firmware.")); return; } - QMessageBox msgBox(this); msgBox.setWindowTitle(tr("Install Firmware")); msgBox.setText(tr("Choose firmware installation method:")); @@ -570,33 +492,25 @@ void SetupWizard::OnSelectFirmware() { QPushButton* folderButton = msgBox.addButton(tr("Select Folder"), QMessageBox::ActionRole); QPushButton* zipButton = msgBox.addButton(tr("Select ZIP File"), QMessageBox::ActionRole); QPushButton* cancelButton = msgBox.addButton(QMessageBox::Cancel); - msgBox.setDefaultButton(zipButton); msgBox.exec(); - QPushButton* clicked = qobject_cast(msgBox.clickedButton()); if (clicked == cancelButton) { return; } - QString firmware_location; bool is_zip = false; if (clicked == zipButton) { - firmware_location = QFileDialog::getOpenFileName(this, tr("Select Firmware ZIP File"), {}, - QStringLiteral("ZIP Files (*.zip)")); + firmware_location = QFileDialog::getOpenFileName(this, tr("Select Firmware ZIP File"), {}, QStringLiteral("ZIP Files (*.zip)")); is_zip = true; } else if (clicked == folderButton) { firmware_location = QFileDialog::getExistingDirectory(this, tr("Select Firmware Folder")); is_zip = false; } - if (firmware_location.isEmpty()) { return; } - firmware_path = firmware_location; - - // Actually install the firmware InstallFirmware(firmware_location, is_zip); } @@ -609,31 +523,20 @@ void SetupWizard::OnSelectGamesDirectory() { } void SetupWizard::OnSelectScreenshotsPath() { - const QString dir_path = - QFileDialog::getExistingDirectory(this, tr("Select Screenshots Directory"), screenshots_path); + const QString dir_path = QFileDialog::getExistingDirectory(this, tr("Select Screenshots Directory"), screenshots_path); if (dir_path.isEmpty()) { return; } screenshots_path = dir_path; } -void SetupWizard::OnProfileNameChanged() { - // Profile name change will be applied in ApplyConfiguration -} +void SetupWizard::OnProfileNameChanged() {} void SetupWizard::OnControllerSetup() { - // Open controller configuration dialog - // 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.")); + 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.")); } 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) { #ifdef _WIN32 const auto exe_dir = Common::FS::GetExeDirectory(); @@ -641,35 +544,25 @@ void SetupWizard::ApplyConfiguration() { if (!Common::FS::Exists(portable_path)) { void(Common::FS::CreateDirs(Common::FS::PathToUTF8String(portable_path))); } - Common::FS::SetCitronPath(Common::FS::CitronPath::CitronDir, - Common::FS::PathToUTF8String(portable_path)); + Common::FS::SetCitronPath(Common::FS::CitronPath::CitronDir, Common::FS::PathToUTF8String(portable_path)); #else const auto current_dir = std::filesystem::current_path(); const auto portable_path = current_dir / "user"; if (!Common::FS::Exists(portable_path)) { void(Common::FS::CreateDirs(Common::FS::PathToUTF8String(portable_path))); } - Common::FS::SetCitronPath(Common::FS::CitronPath::CitronDir, - Common::FS::PathToUTF8String(portable_path)); + Common::FS::SetCitronPath(Common::FS::CitronPath::CitronDir, Common::FS::PathToUTF8String(portable_path)); #endif } - // Standard mode uses default paths, so no change needed - - // Apply screenshots path if (!screenshots_path.isEmpty()) { - Common::FS::SetCitronPath(Common::FS::CitronPath::ScreenshotsDir, - screenshots_path.toStdString()); + Common::FS::SetCitronPath(Common::FS::CitronPath::ScreenshotsDir, screenshots_path.toStdString()); } - - // Apply games directory if (!games_directory.isEmpty()) { UISettings::GameDir game_dir{games_directory.toStdString(), false, true}; if (!UISettings::values.game_dirs.contains(game_dir)) { UISettings::values.game_dirs.append(game_dir); } } - - // Apply profile name if (!profile_name.isEmpty() && profile_name != QStringLiteral("citron")) { auto& profile_manager = system.GetProfileManager(); 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; - - // Save all configuration if (main_window) { main_window->OnSaveConfig(); - - // Refresh game list to show newly added directories main_window->RefreshGameList(); } } @@ -703,8 +590,6 @@ bool SetupWizard::CheckKeysInstalled() const { } bool SetupWizard::CheckFirmwareInstalled() const { - // Check if firmware is installed by checking for system content - // This is a simplified check try { return system.GetFileSystemController().GetSystemNANDContentDirectory() != nullptr; } catch (...) { @@ -716,169 +601,125 @@ void SetupWizard::InstallFirmware(const QString& firmware_path_param, bool is_zi if (!main_window) { return; } - QProgressDialog progress(tr("Installing Firmware..."), tr("Cancel"), 0, 100, this); progress.setWindowModality(Qt::WindowModal); progress.setMinimumDuration(100); progress.setAutoClose(false); progress.setAutoReset(false); progress.show(); - auto QtProgressCallback = [&](size_t total_size, size_t processed_size) { progress.setValue(static_cast((processed_size * 100) / total_size)); QApplication::processEvents(); return progress.wasCanceled(); }; - std::filesystem::path firmware_source_path; std::filesystem::path temp_extract_path; - if (is_zip) { - // Extract ZIP to temp directory temp_extract_path = std::filesystem::temp_directory_path() / "citron_firmware_temp"; if (std::filesystem::exists(temp_extract_path)) { std::filesystem::remove_all(temp_extract_path); } - progress.setLabelText(tr("Extracting firmware ZIP...")); QtProgressCallback(100, 5); - - // Use main window's ExtractZipToDirectory if (!main_window->ExtractZipToDirectoryPublic(firmware_path_param.toStdString(), temp_extract_path)) { progress.close(); std::filesystem::remove_all(temp_extract_path); - QMessageBox::critical(this, tr("Firmware install failed"), - tr("Failed to extract firmware ZIP file.")); + QMessageBox::critical(this, tr("Firmware install failed"), tr("Failed to extract firmware ZIP file.")); return; } - firmware_source_path = temp_extract_path; QtProgressCallback(100, 15); } else { firmware_source_path = firmware_path_param.toStdString(); QtProgressCallback(100, 10); } - - // Find .nca files std::vector nca_files; - const Common::FS::DirEntryCallable callback = - [&nca_files](const std::filesystem::directory_entry& entry) { - if (entry.path().has_extension() && entry.path().extension() == ".nca") { - nca_files.emplace_back(entry.path()); - } - return true; - }; - + const Common::FS::DirEntryCallable callback = [&nca_files](const std::filesystem::directory_entry& entry) { + if (entry.path().has_extension() && entry.path().extension() == ".nca") { + nca_files.emplace_back(entry.path()); + } + return true; + }; Common::FS::IterateDirEntries(firmware_source_path, callback, Common::FS::DirEntryFilter::File); - if (nca_files.empty()) { progress.close(); if (is_zip) { std::filesystem::remove_all(temp_extract_path); } - QMessageBox::warning(this, tr("Firmware install failed"), - tr("Unable to locate firmware NCA files.")); + QMessageBox::warning(this, tr("Firmware install failed"), tr("Unable to locate firmware NCA files.")); return; } - QtProgressCallback(100, 20); - - // Get system NAND content directory auto sysnand_content_vdir = system.GetFileSystemController().GetSystemNANDContentDirectory(); if (!sysnand_content_vdir) { progress.close(); if (is_zip) { std::filesystem::remove_all(temp_extract_path); } - QMessageBox::critical(this, tr("Firmware install failed"), - tr("Failed to access system NAND directory.")); + QMessageBox::critical(this, tr("Firmware install failed"), tr("Failed to access system NAND directory.")); return; } - - // Clean existing firmware if (!sysnand_content_vdir->CleanSubdirectoryRecursive("registered")) { progress.close(); if (is_zip) { std::filesystem::remove_all(temp_extract_path); } - QMessageBox::critical(this, tr("Firmware install failed"), - tr("Failed to clean existing firmware files.")); + QMessageBox::critical(this, tr("Firmware install failed"), tr("Failed to clean existing firmware files.")); return; } - QtProgressCallback(100, 25); - auto firmware_vdir = sysnand_content_vdir->GetDirectoryRelative("registered"); if (!firmware_vdir) { progress.close(); if (is_zip) { std::filesystem::remove_all(temp_extract_path); } - QMessageBox::critical(this, tr("Firmware install failed"), - tr("Failed to create firmware directory.")); + QMessageBox::critical(this, tr("Firmware install failed"), tr("Failed to create firmware directory.")); return; } - - // Copy firmware files auto vfs = system.GetFilesystem(); if (!vfs) { progress.close(); if (is_zip) { std::filesystem::remove_all(temp_extract_path); } - QMessageBox::critical(this, tr("Firmware install failed"), - tr("Failed to access virtual filesystem.")); + QMessageBox::critical(this, tr("Firmware install failed"), tr("Failed to access virtual filesystem.")); return; } - bool success = true; int i = 0; for (const auto& nca_path : nca_files) { i++; auto src_file = vfs->OpenFile(nca_path.generic_string(), FileSys::OpenMode::Read); auto dst_file = firmware_vdir->CreateFileRelative(nca_path.filename().string()); - if (!src_file || !dst_file) { LOG_ERROR(Frontend, "Failed to open firmware file: {}", nca_path.string()); success = false; continue; } - if (!FileSys::VfsRawCopy(src_file, dst_file)) { LOG_ERROR(Frontend, "Failed to copy firmware file: {}", nca_path.string()); success = false; } - if (QtProgressCallback(100, 25 + static_cast((i * 60) / nca_files.size()))) { progress.close(); if (is_zip) { std::filesystem::remove_all(temp_extract_path); } - QMessageBox::warning(this, tr("Firmware install cancelled"), - tr("Firmware installation was cancelled.")); + QMessageBox::warning(this, tr("Firmware install cancelled"), tr("Firmware installation was cancelled.")); return; } } - - // Clean up temp directory if (is_zip) { std::filesystem::remove_all(temp_extract_path); } - if (!success) { progress.close(); - QMessageBox::critical(this, tr("Firmware install failed"), - tr("One or more firmware files failed to copy.")); + QMessageBox::critical(this, tr("Firmware install failed"), tr("One or more firmware files failed to copy.")); return; } - - // Re-scan 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(); - QMessageBox::information(this, tr("Firmware installed successfully"), - tr("The firmware has been installed successfully.")); + QMessageBox::information(this, tr("Firmware installed successfully"), tr("The firmware has been installed successfully.")); firmware_installed = true; } diff --git a/src/citron/setup_wizard.h b/src/citron/setup_wizard.h index d7b77f9e9..8fe725c8c 100644 --- a/src/citron/setup_wizard.h +++ b/src/citron/setup_wizard.h @@ -4,6 +4,7 @@ #pragma once #include +#include #include #include #include @@ -36,6 +37,9 @@ public: Page_Completion = 8, }; +protected: + void changeEvent(QEvent* event) override; + private slots: void OnPageChanged(int index); void OnNextClicked(); @@ -50,7 +54,6 @@ private slots: void OnControllerSetup(); private: - void SetupUI(); void SetupPages(); void ApplyConfiguration(); void InstallFirmware(const QString& firmware_path, bool is_zip); @@ -58,6 +61,7 @@ private: bool CheckFirmwareInstalled() const; void UpdateNavigationButtons(); bool ValidateCurrentPage(); + void UpdateTheme(); std::unique_ptr ui; QListWidget* sidebar_list; @@ -76,4 +80,5 @@ private: QString screenshots_path; QString profile_name; bool firmware_installed; + QColor last_palette_text_color; }; diff --git a/src/citron/setup_wizard.ui b/src/citron/setup_wizard.ui index 1a3c3ca8a..29a32f653 100644 --- a/src/citron/setup_wizard.ui +++ b/src/citron/setup_wizard.ui @@ -19,6 +19,74 @@ citron Setup Wizard + + 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 & 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; +} + + @@ -37,32 +105,14 @@ - QListWidget { - 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; -} + - QStackedWidget { - background-color: #1e1e1e; -} + diff --git a/src/citron/updater/updater_service.cpp b/src/citron/updater/updater_service.cpp index 592322466..c75806472 100644 --- a/src/citron/updater/updater_service.cpp +++ b/src/citron/updater/updater_service.cpp @@ -125,7 +125,7 @@ void UpdaterService::CheckForUpdates() { return; } 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; LOG_INFO(Frontend, "Selected update channel: {}", channel.toStdString()); 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) { QJsonObject asset_obj = asset_value.toObject(); QString asset_name = asset_obj.value(QStringLiteral("name")).toString(); -#if defined(__linux__) + + #if defined(__linux__) if (asset_name.endsWith(QStringLiteral(".AppImage"))) { -#else - if (asset_name.endsWith(QStringLiteral(".zip"))) { -#endif 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); } + #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()) { diff --git a/src/video_core/renderer_vulkan/vk_swapchain.cpp b/src/video_core/renderer_vulkan/vk_swapchain.cpp index 89636f864..456dcf17c 100644 --- a/src/video_core/renderer_vulkan/vk_swapchain.cpp +++ b/src/video_core/renderer_vulkan/vk_swapchain.cpp @@ -40,22 +40,18 @@ static VkPresentModeKHR ChooseSwapPresentMode(bool has_imm, bool has_mailbox, bool has_fifo_relaxed) { // Wayland-specific optimizations for low-latency presentation. 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. if (has_mailbox) { - LOG_INFO(Render_Vulkan, "Using Mailbox presentation mode for Wayland."); return VK_PRESENT_MODE_MAILBOX_KHR; } // Allow Immediate for lowest latency if the user explicitly chooses it. 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; } // 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; }