From ec7f5e438abee47117f91ae922236e56fe865eb4 Mon Sep 17 00:00:00 2001 From: Matt Borgerson Date: Wed, 21 Jan 2026 16:52:35 -0700 Subject: [PATCH] ui: Fetch latest release info via GitHub API --- ui/xui/update.cc | 61 ++++++++++++++++++++++++++++++++++-------------- ui/xui/update.hh | 7 ++++-- util/http.c | 13 ++++++++++- 3 files changed, 61 insertions(+), 20 deletions(-) diff --git a/ui/xui/update.cc b/ui/xui/update.cc index 1abb991cee..a5ecd36fa5 100644 --- a/ui/xui/update.cc +++ b/ui/xui/update.cc @@ -25,20 +25,19 @@ #include #include "util/miniz/miniz.h" #include "xemu-version.h" +#include + +using json = nlohmann::json; + +const char *releases_url = "https://api.github.com/repos/xemu-project/xemu/releases/latest"; -#if defined(_WIN32) -const char *version_url = "https://raw.githubusercontent.com/xemu-project/xemu/ppa-snapshot/XEMU_VERSION"; #if defined(__x86_64__) -const char *download_url = "https://github.com/xemu-project/xemu/releases/latest/download/xemu-win-x86_64-release.zip"; +#define PACKAGE_ARCH "x86_64" #elif defined(__aarch64__) -const char *download_url = "https://github.com/xemu-project/xemu/releases/latest/download/xemu-win-aarch64-release.zip"; +#define PACKAGE_ARCH "arm64" #else -#error Unknown update path +#error Unhandled package arch #endif -#else -FIXME -#endif - #define DPRINTF(fmt, ...) fprintf(stderr, fmt, ##__VA_ARGS__); @@ -89,6 +88,7 @@ void AutoUpdateWindow::Draw() } if (updater.is_updating()) { + ImGui::Dummy(ImVec2(0.0f, ImGui::GetStyle().ItemSpacing.y)); ImGui::ProgressBar(updater.get_update_progress_percentage()/100.0f, ImVec2(-1.0f, 0.0f)); } @@ -129,7 +129,7 @@ Updater::Updater() m_status = UPDATER_IDLE; m_update_availability = UPDATE_AVAILABILITY_UNKNOWN; m_update_percentage = 0; - m_latest_version = "Unknown"; + m_release_version = "Unknown"; m_should_cancel = false; } @@ -152,7 +152,7 @@ void *Updater::checker_thread_worker_func(void *updater) void Updater::check_for_update_internal() { g_autoptr(GByteArray) data = g_byte_array_new(); - int res = http_get(version_url, data, NULL, NULL); + int res = http_get(releases_url, data, NULL, NULL); if (m_should_cancel) { m_should_cancel = false; @@ -163,12 +163,39 @@ void Updater::check_for_update_internal() goto finished; } - m_latest_version = std::string((const char *)data->data, data->len); + try { + json release = json::parse(std::string((const char *)data->data, data->len)); + m_release_url = release.value("html_url", "https://github.com/xemu-project/xemu/releases/latest"); + m_release_version = release["tag_name"].get(); + if (!m_release_version.empty() && m_release_version[0] == 'v') { + m_release_version = m_release_version.substr(1); + } - if (m_latest_version != xemu_version) { - m_update_availability = UPDATE_AVAILABLE; - } else { - m_update_availability = UPDATE_NOT_AVAILABLE; + m_release_package_url.clear(); + std::string expected_filename = "xemu-" + m_release_version + "-windows-" PACKAGE_ARCH ".zip"; + for (const auto &asset : release["assets"]) { + std::string name = asset["name"].get(); + if (name == expected_filename) { + m_release_package_url = asset["browser_download_url"].get(); + break; + } + } + + if (m_release_package_url.empty()) { + DPRINTF("Could not find asset matching %s\n", expected_filename.c_str()); + m_status = UPDATER_ERROR; + goto finished; + } + + if (m_release_version != xemu_version) { + m_update_availability = UPDATE_AVAILABLE; + } else { + m_update_availability = UPDATE_NOT_AVAILABLE; + } + } catch (const json::exception &e) { + DPRINTF("JSON parse error: %s\n", e.what()); + m_status = UPDATER_ERROR; + goto finished; } m_status = UPDATER_IDLE; @@ -215,7 +242,7 @@ void Updater::update_internal() return static_cast(info->userptr)->progress_cb(info); }; - int res = http_get(download_url, data, &progress_info, NULL); + int res = http_get(m_release_package_url.c_str(), data, &progress_info, NULL); if (m_should_cancel) { m_should_cancel = false; diff --git a/ui/xui/update.hh b/ui/xui/update.hh index efb1ce147c..d50006778f 100644 --- a/ui/xui/update.hh +++ b/ui/xui/update.hh @@ -49,7 +49,9 @@ private: UpdateAvailability m_update_availability; int m_update_percentage; QemuThread m_thread; - std::string m_latest_version; + std::string m_release_version; + std::string m_release_url; + std::string m_release_package_url; bool m_should_cancel; UpdateStatus m_status; UpdaterCallback m_on_complete; @@ -66,7 +68,8 @@ public: bool is_update_available() { return m_update_availability == UPDATE_AVAILABLE; } bool is_checking_for_update() { return m_status == UPDATER_CHECKING_FOR_UPDATE; } bool is_updating() { return m_status == UPDATER_UPDATING; } - std::string get_update_version() { return m_latest_version; } + const std::string& get_release_version() { return m_release_version; } + const std::string& get_release_url() { return m_release_url; } void cancel() { m_should_cancel = true; } void update(); void update_internal(); diff --git a/util/http.c b/util/http.c index de51514498..6f6897dc2e 100644 --- a/util/http.c +++ b/util/http.c @@ -20,6 +20,7 @@ #include "qemu/osdep.h" #include "qapi/error.h" #include "qemu/http.h" +#include "xemu-version.h" #include #include @@ -29,6 +30,13 @@ static bool libcurl_init_called = false; static bool libcurl_init_success = false; +static char *xemu_user_agent = NULL; + +static void libcurl_cleanup(void) +{ + curl_global_cleanup(); + g_free(xemu_user_agent); +} static bool ensure_libcurl_initialized(Error **errp) { @@ -37,7 +45,8 @@ static bool ensure_libcurl_initialized(Error **errp) libcurl_init_called = true; if (res == CURLE_OK) { libcurl_init_success = true; - atexit(curl_global_cleanup); + xemu_user_agent = g_strdup_printf("xemu/%s", xemu_version); + atexit(libcurl_cleanup); } } @@ -88,6 +97,7 @@ int http_get(const char *url, GByteArray *response_body, curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, http_get_cb); curl_easy_setopt(curl, CURLOPT_WRITEDATA, response_body); curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); // Follow redirects + curl_easy_setopt(curl, CURLOPT_USERAGENT, xemu_user_agent); #if ALLOW_INSECURE_HOSTS curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L); curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L); @@ -136,6 +146,7 @@ int http_post_json(const char *url, const char *json_data, Error **errp) curl_easy_setopt(curl, CURLOPT_POST, 1L); curl_easy_setopt(curl, CURLOPT_POSTFIELDS, json_data); curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); + curl_easy_setopt(curl, CURLOPT_USERAGENT, xemu_user_agent); #if ALLOW_INSECURE_HOSTS curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L); curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);