From b5e8dc63a80237ddceae71d44e8d1ef3ad460a0a Mon Sep 17 00:00:00 2001 From: Collecting Date: Fri, 26 Dec 2025 05:59:33 +0000 Subject: [PATCH] Adjust CLI Updater Signed-off-by: Collecting --- src/citron/main.cpp | 116 ++++++++++++++++++-------------------------- 1 file changed, 46 insertions(+), 70 deletions(-) diff --git a/src/citron/main.cpp b/src/citron/main.cpp index a87b093c6..2b8711e2b 100644 --- a/src/citron/main.cpp +++ b/src/citron/main.cpp @@ -6101,43 +6101,52 @@ int main(int argc, char* argv[]) { } } - // If we are using CLI commands, attach to the terminal so we can see fmt::print output + // If we are using CLI commands, attach to the terminal so we can see output if (has_cli_arg) { if (AttachConsole(ATTACH_PARENT_PROCESS)) { - // Redirect standard output/error to the terminal window - auto redirect = [](DWORD std_handle, FILE* stream, const char* mode) { - HANDLE h = GetStdHandle(std_handle); - if (h != INVALID_HANDLE_VALUE) { - int fd = _open_osfhandle((intptr_t)h, _O_TEXT); - if (fd != -1) { - FILE* fp = _fdopen(fd, mode); - if (fp) { - *stream = *fp; - setvbuf(stream, NULL, _IONBF, 0); - } - } - } - }; - redirect(STD_OUTPUT_HANDLE, stdout, "w"); - redirect(STD_ERROR_HANDLE, stderr, "w"); - redirect(STD_INPUT_HANDLE, stdin, "r"); - std::ios::sync_with_stdio(); + // Using freopen_s is the most reliable way on Windows to redirect + // stdout/stderr/stdin back to the parent console (CMD/PowerShell) + FILE* fpStdout = nullptr; + FILE* fpStderr = nullptr; + FILE* fpStdin = nullptr; + + freopen_s(&fpStdout, "CONOUT$", "w", stdout); + freopen_s(&fpStderr, "CONOUT$", "w", stderr); + freopen_s(&fpStdin, "CONIN$", "r", stdin); + + // Sync C++ streams (std::cout, etc) with the new C streams + std::ios::sync_with_stdio(true); + + // Clear any error states on streams + std::cout.clear(); + std::cerr.clear(); + std::cin.clear(); } } - #endif + const bool is_appimage = !qgetenv("APPIMAGE").isEmpty(); + if (is_appimage) { + // Fixes Wayland crash with NVIDIA drivers by disabling explicit sync. + qputenv("QT_WAYLAND_DISABLE_EXPLICIT_SYNC", "1"); + + // Tell the bundled OpenSSL where to find the bundled certificates. + QString app_dir_path = QFileInfo(QString::fromLocal8Bit(argv[0])).absolutePath(); + QDir app_dir(app_dir_path); + const QString certs_path = app_dir.filePath(QString::fromLatin1("../etc/ssl/certs")); + qputenv("SSL_CERT_DIR", certs_path.toUtf8()); + } + // 1. Programmatic Version Output for (int i = 1; i < argc; ++i) { std::string arg = argv[i]; if (arg == "-v" || arg == "--version") { - // This will now correctly appear in CMD/PowerShell fmt::print("citron {}| {} ({})\n", Common::g_build_fullname, Common::g_citron_version, Common::g_citron_hash); return 0; } } - // 2. Headless Update Flag + // 2. Headless Update Flag Logic for (int i = 1; i < argc; ++i) { if (std::string(argv[i]) == "--update") { QString forced_channel; @@ -6148,10 +6157,12 @@ int main(int argc, char* argv[]) { } } + // Create a Core application for the headless update process QCoreApplication app(argc, argv); auto* service = new Updater::UpdaterService(&app); fmt::print("Checking for {} updates...\n", forced_channel.isEmpty() ? "latest" : forced_channel.toStdString()); + std::fflush(stdout); QObject::connect(service, &Updater::UpdaterService::UpdateCheckCompleted, [&](bool has_update, const Updater::UpdateInfo& info) { if (!has_update) { @@ -6166,6 +6177,7 @@ int main(int argc, char* argv[]) { fmt::print(" [{}] {}\n", k, info.download_options[k].name); } fmt::print("Select variant index (default 0): "); + std::fflush(stdout); std::string input; std::getline(std::cin, input); @@ -6181,6 +6193,7 @@ int main(int argc, char* argv[]) { } fmt::print("Downloading: {}\n", info.download_options[selected_index].name); + std::fflush(stdout); service->DownloadAndInstallUpdate(info.download_options[selected_index].url); } }); @@ -6190,7 +6203,6 @@ int main(int argc, char* argv[]) { fmt::print("\rDownloading: ["); if (total > 0) { - // We know the size, show a proper bar int pos = percentage / 5; for (int j = 0; j < 20; ++j) { if (j < pos) fmt::print("="); else if (j == pos) fmt::print(">"); else fmt::print(" "); @@ -6198,13 +6210,12 @@ int main(int argc, char* argv[]) { double total_mb = static_cast(total) / 1024.0 / 1024.0; fmt::print("] {}% ({:.2f} MB / {:.2f} MB)", percentage, received_mb, total_mb); } else { - // Size is unknown, show a "spinner" or just bytes static int spinner = 0; const char* chars = "|/-\\"; fmt::print(" {} ", chars[spinner++ % 4]); fmt::print(" ] (Size unknown) {:.2f} MB received", received_mb); } - fflush(stdout); + std::fflush(stdout); }); QObject::connect(service, &Updater::UpdaterService::UpdateCompleted, [&](Updater::UpdaterService::UpdateResult result, const QString& message) { @@ -6237,7 +6248,7 @@ int main(int argc, char* argv[]) { }); QObject::connect(service, &Updater::UpdaterService::UpdateError, [&](const QString& err) { - fmt::print("\nError: {}\n", err.toStdString()); + fmt::print("\nError during update: {}\n", err.toStdString()); app.exit(1); }); @@ -6246,23 +6257,13 @@ int main(int argc, char* argv[]) { } } - // Set environment variables for AppImage compatibility - // This must be done before the QApplication is created. - const bool is_appimage = !qgetenv("APPIMAGE").isEmpty(); - if (is_appimage) { - // Fixes Wayland crash with NVIDIA drivers by disabling explicit sync. - qputenv("QT_WAYLAND_DISABLE_EXPLICIT_SYNC", "1"); - - // Tell the bundled OpenSSL where to find the bundled certificates. - const QDir app_dir(QCoreApplication::applicationDirPath()); - const QString certs_path = app_dir.filePath(QString::fromLatin1("../etc/ssl/certs")); - qputenv("SSL_CERT_DIR", certs_path.toUtf8()); - } + // --- STANDARD STARTUP LOGIC --- std::unique_ptr config = std::make_unique(); UISettings::RestoreWindowState(config); bool has_broken_vulkan = false; bool is_child = false; + if (CheckEnvVars(&is_child)) { return 0; } @@ -6284,73 +6285,54 @@ int main(int argc, char* argv[]) { Common::ConfigureNvidiaEnvironmentFlags(); - // Init settings params QCoreApplication::setOrganizationName(QStringLiteral("citron team")); QCoreApplication::setApplicationName(QStringLiteral("citron")); #ifdef _WIN32 - // Increases the maximum open file limit to 8192 _setmaxstdio(8192); #endif #ifdef __APPLE__ - // If you start a bundle (binary) on OSX without the Terminal, the working directory is "/". - // But since we require the working directory to be the executable path for the location of - // the user folder in the Qt Frontend, we need to cd into that working directory const auto bin_path = Common::FS::GetBundleDirectory() / ".."; chdir(Common::FS::PathToUTF8String(bin_path).c_str()); #endif #ifdef __linux__ - // Set the DISPLAY variable in order to open web browsers - // TODO (lat9nq): Find a better solution for AppImages to start external applications if (QString::fromLocal8Bit(qgetenv("DISPLAY")).isEmpty()) { qputenv("DISPLAY", ":0"); } - - // Fix the Wayland appId. This needs to match the name of the .desktop file without the .desktop - // suffix. QGuiApplication::setDesktopFileName(QStringLiteral("org.citron_emu.citron")); #endif SetHighDPIAttributes(); #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) - // Disables the "?" button on all dialogs. Disabled by default on Qt6. QCoreApplication::setAttribute(Qt::AA_DisableWindowContextHelpButton); #endif - // Enables the core to make the qt created contexts current on std::threads QCoreApplication::setAttribute(Qt::AA_DontCheckOpenGLContextThreadAffinity); QApplication app(argc, argv); + #ifdef __linux__ if (QGuiApplication::platformName().startsWith(QStringLiteral("wayland"))) { Settings::values.is_wayland_platform.SetValue(true); } #endif - #ifdef CITRON_USE_AUTO_UPDATER - // Check for and apply staged updates before starting the main application - std::filesystem::path app_dir = std::filesystem::path(QCoreApplication::applicationDirPath().toStdString()); +#ifdef CITRON_USE_AUTO_UPDATER + std::filesystem::path app_dir_fs = std::filesystem::path(QCoreApplication::applicationDirPath().toStdString()); #ifdef _WIN32 - // On Windows, updates are applied by the helper script after the app exits. - // If we find a staging directory here, it means the helper script failed. - // Clean it up to avoid confusion. - std::filesystem::path staging_path = app_dir / "update_staging"; + std::filesystem::path staging_path = app_dir_fs / "update_staging"; if (std::filesystem::exists(staging_path)) { try { std::filesystem::remove_all(staging_path); - } catch (...) { - // Ignore cleanup errors - } + } catch (...) {} } #else - // On Linux, apply staged updates at startup as before - if (Updater::UpdaterService::HasStagedUpdate(app_dir)) { - if (Updater::UpdaterService::ApplyStagedUpdate(app_dir)) { - // Show a simple message that update was applied + if (Updater::UpdaterService::HasStagedUpdate(app_dir_fs)) { + if (Updater::UpdaterService::ApplyStagedUpdate(app_dir_fs)) { QMessageBox::information(nullptr, QObject::tr("Update Applied"), QObject::tr("Citron has been updated successfully!")); } @@ -6362,10 +6344,6 @@ int main(int argc, char* argv[]) { OverrideWindowsFont(); #endif - // Workaround for QTBUG-85409, for Suzhou numerals the number 1 is actually \u3021 - // so we can see if we get \u3008 instead - // TL;DR all other number formats are consecutive in unicode code points - // This bug is fixed in Qt6, specifically 6.0.0-alpha1 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) const QLocale locale = QLocale::system(); if (QStringLiteral("\u3008") == locale.toString(1)) { @@ -6373,8 +6351,6 @@ int main(int argc, char* argv[]) { } #endif - // Qt changes the locale and causes issues in float conversion using std::to_string() when - // generating shaders setlocale(LC_ALL, "C"); GMainWindow main_window{std::move(config), has_broken_vulkan};