Adjust CLI Updater

Signed-off-by: Collecting <collecting@noreply.localhost>
This commit is contained in:
Collecting
2025-12-26 05:59:33 +00:00
parent 8688d106c9
commit b5e8dc63a8

View File

@@ -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 (has_cli_arg) {
if (AttachConsole(ATTACH_PARENT_PROCESS)) { if (AttachConsole(ATTACH_PARENT_PROCESS)) {
// Redirect standard output/error to the terminal window // Using freopen_s is the most reliable way on Windows to redirect
auto redirect = [](DWORD std_handle, FILE* stream, const char* mode) { // stdout/stderr/stdin back to the parent console (CMD/PowerShell)
HANDLE h = GetStdHandle(std_handle); FILE* fpStdout = nullptr;
if (h != INVALID_HANDLE_VALUE) { FILE* fpStderr = nullptr;
int fd = _open_osfhandle((intptr_t)h, _O_TEXT); FILE* fpStdin = nullptr;
if (fd != -1) {
FILE* fp = _fdopen(fd, mode); freopen_s(&fpStdout, "CONOUT$", "w", stdout);
if (fp) { freopen_s(&fpStderr, "CONOUT$", "w", stderr);
*stream = *fp; freopen_s(&fpStdin, "CONIN$", "r", stdin);
setvbuf(stream, NULL, _IONBF, 0);
} // Sync C++ streams (std::cout, etc) with the new C streams
} std::ios::sync_with_stdio(true);
}
}; // Clear any error states on streams
redirect(STD_OUTPUT_HANDLE, stdout, "w"); std::cout.clear();
redirect(STD_ERROR_HANDLE, stderr, "w"); std::cerr.clear();
redirect(STD_INPUT_HANDLE, stdin, "r"); std::cin.clear();
std::ios::sync_with_stdio();
} }
} }
#endif #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 // 1. Programmatic Version Output
for (int i = 1; i < argc; ++i) { for (int i = 1; i < argc; ++i) {
std::string arg = argv[i]; std::string arg = argv[i];
if (arg == "-v" || arg == "--version") { 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); fmt::print("citron {}| {} ({})\n", Common::g_build_fullname, Common::g_citron_version, Common::g_citron_hash);
return 0; return 0;
} }
} }
// 2. Headless Update Flag // 2. Headless Update Flag Logic
for (int i = 1; i < argc; ++i) { for (int i = 1; i < argc; ++i) {
if (std::string(argv[i]) == "--update") { if (std::string(argv[i]) == "--update") {
QString forced_channel; 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); QCoreApplication app(argc, argv);
auto* service = new Updater::UpdaterService(&app); auto* service = new Updater::UpdaterService(&app);
fmt::print("Checking for {} updates...\n", forced_channel.isEmpty() ? "latest" : forced_channel.toStdString()); 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) { QObject::connect(service, &Updater::UpdaterService::UpdateCheckCompleted, [&](bool has_update, const Updater::UpdateInfo& info) {
if (!has_update) { if (!has_update) {
@@ -6166,6 +6177,7 @@ int main(int argc, char* argv[]) {
fmt::print(" [{}] {}\n", k, info.download_options[k].name); fmt::print(" [{}] {}\n", k, info.download_options[k].name);
} }
fmt::print("Select variant index (default 0): "); fmt::print("Select variant index (default 0): ");
std::fflush(stdout);
std::string input; std::string input;
std::getline(std::cin, 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); fmt::print("Downloading: {}\n", info.download_options[selected_index].name);
std::fflush(stdout);
service->DownloadAndInstallUpdate(info.download_options[selected_index].url); service->DownloadAndInstallUpdate(info.download_options[selected_index].url);
} }
}); });
@@ -6190,7 +6203,6 @@ int main(int argc, char* argv[]) {
fmt::print("\rDownloading: ["); fmt::print("\rDownloading: [");
if (total > 0) { if (total > 0) {
// We know the size, show a proper bar
int pos = percentage / 5; int pos = percentage / 5;
for (int j = 0; j < 20; ++j) { for (int j = 0; j < 20; ++j) {
if (j < pos) fmt::print("="); else if (j == pos) fmt::print(">"); else fmt::print(" "); 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<double>(total) / 1024.0 / 1024.0; double total_mb = static_cast<double>(total) / 1024.0 / 1024.0;
fmt::print("] {}% ({:.2f} MB / {:.2f} MB)", percentage, received_mb, total_mb); fmt::print("] {}% ({:.2f} MB / {:.2f} MB)", percentage, received_mb, total_mb);
} else { } else {
// Size is unknown, show a "spinner" or just bytes
static int spinner = 0; static int spinner = 0;
const char* chars = "|/-\\"; const char* chars = "|/-\\";
fmt::print(" {} ", chars[spinner++ % 4]); fmt::print(" {} ", chars[spinner++ % 4]);
fmt::print(" ] (Size unknown) {:.2f} MB received", received_mb); 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) { 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) { 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); app.exit(1);
}); });
@@ -6246,23 +6257,13 @@ int main(int argc, char* argv[]) {
} }
} }
// Set environment variables for AppImage compatibility // --- STANDARD STARTUP LOGIC ---
// 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());
}
std::unique_ptr<QtConfig> config = std::make_unique<QtConfig>(); std::unique_ptr<QtConfig> config = std::make_unique<QtConfig>();
UISettings::RestoreWindowState(config); UISettings::RestoreWindowState(config);
bool has_broken_vulkan = false; bool has_broken_vulkan = false;
bool is_child = false; bool is_child = false;
if (CheckEnvVars(&is_child)) { if (CheckEnvVars(&is_child)) {
return 0; return 0;
} }
@@ -6284,73 +6285,54 @@ int main(int argc, char* argv[]) {
Common::ConfigureNvidiaEnvironmentFlags(); Common::ConfigureNvidiaEnvironmentFlags();
// Init settings params
QCoreApplication::setOrganizationName(QStringLiteral("citron team")); QCoreApplication::setOrganizationName(QStringLiteral("citron team"));
QCoreApplication::setApplicationName(QStringLiteral("citron")); QCoreApplication::setApplicationName(QStringLiteral("citron"));
#ifdef _WIN32 #ifdef _WIN32
// Increases the maximum open file limit to 8192
_setmaxstdio(8192); _setmaxstdio(8192);
#endif #endif
#ifdef __APPLE__ #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() / ".."; const auto bin_path = Common::FS::GetBundleDirectory() / "..";
chdir(Common::FS::PathToUTF8String(bin_path).c_str()); chdir(Common::FS::PathToUTF8String(bin_path).c_str());
#endif #endif
#ifdef __linux__ #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()) { if (QString::fromLocal8Bit(qgetenv("DISPLAY")).isEmpty()) {
qputenv("DISPLAY", ":0"); 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")); QGuiApplication::setDesktopFileName(QStringLiteral("org.citron_emu.citron"));
#endif #endif
SetHighDPIAttributes(); SetHighDPIAttributes();
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) #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); QCoreApplication::setAttribute(Qt::AA_DisableWindowContextHelpButton);
#endif #endif
// Enables the core to make the qt created contexts current on std::threads
QCoreApplication::setAttribute(Qt::AA_DontCheckOpenGLContextThreadAffinity); QCoreApplication::setAttribute(Qt::AA_DontCheckOpenGLContextThreadAffinity);
QApplication app(argc, argv); QApplication app(argc, argv);
#ifdef __linux__ #ifdef __linux__
if (QGuiApplication::platformName().startsWith(QStringLiteral("wayland"))) { if (QGuiApplication::platformName().startsWith(QStringLiteral("wayland"))) {
Settings::values.is_wayland_platform.SetValue(true); Settings::values.is_wayland_platform.SetValue(true);
} }
#endif #endif
#ifdef CITRON_USE_AUTO_UPDATER #ifdef CITRON_USE_AUTO_UPDATER
// Check for and apply staged updates before starting the main application std::filesystem::path app_dir_fs = std::filesystem::path(QCoreApplication::applicationDirPath().toStdString());
std::filesystem::path app_dir = std::filesystem::path(QCoreApplication::applicationDirPath().toStdString());
#ifdef _WIN32 #ifdef _WIN32
// On Windows, updates are applied by the helper script after the app exits. std::filesystem::path staging_path = app_dir_fs / "update_staging";
// 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";
if (std::filesystem::exists(staging_path)) { if (std::filesystem::exists(staging_path)) {
try { try {
std::filesystem::remove_all(staging_path); std::filesystem::remove_all(staging_path);
} catch (...) { } catch (...) {}
// Ignore cleanup errors
}
} }
#else #else
// On Linux, apply staged updates at startup as before if (Updater::UpdaterService::HasStagedUpdate(app_dir_fs)) {
if (Updater::UpdaterService::HasStagedUpdate(app_dir)) { if (Updater::UpdaterService::ApplyStagedUpdate(app_dir_fs)) {
if (Updater::UpdaterService::ApplyStagedUpdate(app_dir)) {
// Show a simple message that update was applied
QMessageBox::information(nullptr, QObject::tr("Update Applied"), QMessageBox::information(nullptr, QObject::tr("Update Applied"),
QObject::tr("Citron has been updated successfully!")); QObject::tr("Citron has been updated successfully!"));
} }
@@ -6362,10 +6344,6 @@ int main(int argc, char* argv[]) {
OverrideWindowsFont(); OverrideWindowsFont();
#endif #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) #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
const QLocale locale = QLocale::system(); const QLocale locale = QLocale::system();
if (QStringLiteral("\u3008") == locale.toString(1)) { if (QStringLiteral("\u3008") == locale.toString(1)) {
@@ -6373,8 +6351,6 @@ int main(int argc, char* argv[]) {
} }
#endif #endif
// Qt changes the locale and causes issues in float conversion using std::to_string() when
// generating shaders
setlocale(LC_ALL, "C"); setlocale(LC_ALL, "C");
GMainWindow main_window{std::move(config), has_broken_vulkan}; GMainWindow main_window{std::move(config), has_broken_vulkan};