diff --git a/README.md b/README.md index f0e2508..6681d12 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,13 @@ Windows packaging for [Helium](https://github.com/imputnet/helium). +## Updates + +Windows builds include built-in update checks while Helium is running. +Update fetching is controlled by the existing Helium services toggle in +`Settings > Privacy and security > Helium services`, and uses the configured +Helium services origin for the update appcast. + ## Credits This repo is based on @@ -166,4 +173,3 @@ ln -s /usr/bin/vim /usr/bin/vi 1. Download nightly rust build from: `https://static.rust-lang.org/dist//rust-nightly-aarch64-pc-windows-msvc.tar.gz` 1. Replace `build-date` with the obtained value 1. Get the SHA-512 checksum using `sha512sum` in **`MSYS2 MSYS`**. - diff --git a/downloads.ini b/downloads.ini index 1bf0241..64f879d 100644 --- a/downloads.ini +++ b/downloads.ini @@ -21,6 +21,15 @@ extractor = 7z output_path = third_party/nsis strip_leading_dirs = nsis-%(version)s +[winsparkle] +version = 0.9.2 +url = https://github.com/vslavik/winsparkle/releases/download/v%(version)s/WinSparkle-%(version)s.zip +download_filename = WinSparkle-%(version)s.zip +sha512 = 73ea15e81a6bffd5268674f633f0aa55660764c8b8f35b7fac073e5f82c85b4e888172b3dfd9c3e71341bd23e5314539dd0f47360f89473f94b39f2352910012 +extractor = 7z +output_path = third_party/winsparkle +strip_leading_dirs = WinSparkle-%(version)s + # Pre-built GNU bison from GnuWin32 [bison-bin] version = 2.4.1 diff --git a/installer/helium.nsi b/installer/helium.nsi index e0f69d4..ff3fd99 100644 --- a/installer/helium.nsi +++ b/installer/helium.nsi @@ -44,6 +44,7 @@ Var InstallFailed Var RadioUser Var RadioSystem Var SystemInstallExists +Var UpdateMode ; --- MUI2 Configuration --- !define MUI_ICON "${ICON_FILE}" @@ -53,10 +54,14 @@ Var SystemInstallExists ; Welcome page !define MUI_WELCOMEPAGE_TITLE "Welcome to ${PRODUCT_NAME} Setup" !define MUI_WELCOMEPAGE_TEXT "Setup will install ${PRODUCT_NAME} ${VERSION} on your computer.$\r$\n$\r$\nClick Next to continue." +!define MUI_PAGE_CUSTOMFUNCTION_PRE SkipPageInUpdateMode !insertmacro MUI_PAGE_WELCOME +!undef MUI_PAGE_CUSTOMFUNCTION_PRE ; License page +!define MUI_PAGE_CUSTOMFUNCTION_PRE SkipPageInUpdateMode !insertmacro MUI_PAGE_LICENSE "${LICENSE_FILE}" +!undef MUI_PAGE_CUSTOMFUNCTION_PRE ; Install type selection (custom page) Page custom InstallTypePage InstallTypePageLeave @@ -70,7 +75,9 @@ Page custom InstallTypePage InstallTypePageLeave !define MUI_FINISHPAGE_RUN !define MUI_FINISHPAGE_RUN_TEXT "Launch ${PRODUCT_NAME}" !define MUI_FINISHPAGE_RUN_FUNCTION LaunchHelium +!define MUI_PAGE_CUSTOMFUNCTION_PRE SkipPageInUpdateMode !insertmacro MUI_PAGE_FINISH +!undef MUI_PAGE_CUSTOMFUNCTION_PRE ; --- Language --- !insertmacro MUI_LANGUAGE "English" @@ -83,11 +90,21 @@ VIAddVersionKey "FileDescription" "${PRODUCT_NAME} Installer" VIAddVersionKey "FileVersion" "${VERSION}" VIAddVersionKey "LegalCopyright" "Copyright The Helium Authors" +Function SkipPageInUpdateMode + ${If} $UpdateMode == "1" + Abort + ${EndIf} +FunctionEnd + ; ============================================================================= ; Custom Install Type Page ; ============================================================================= Function InstallTypePage + ${If} $UpdateMode == "1" + Abort + ${EndIf} + !insertmacro MUI_HEADER_TEXT "Installation Type" "Choose how you want to install ${PRODUCT_NAME}." nsDialogs::Create 1018 @@ -145,8 +162,15 @@ Function .onInit ; Default to user install; parse command-line flags StrCpy $InstallType "user" StrCpy $SetupFlags "" + StrCpy $UpdateMode "0" ${GetParameters} $0 + ${GetOptions} $0 "/UPDATE" $1 + ${IfNot} ${Errors} + StrCpy $UpdateMode "1" + ${EndIf} + ClearErrors + ${GetOptions} $0 "/SYSTEM" $1 ${IfNot} ${Errors} StrCpy $InstallType "system" @@ -200,13 +224,14 @@ Section "Install" SecInstall StrCpy $InstallFailed "" ; Extract setup files to temp directory - SetOutPath "$TEMP\helium_install" + InitPluginsDir + SetOutPath "$PLUGINSDIR" DetailPrint "Extracting installation files..." File "${SETUP_EXE}" File /oname=helium.7z "${HELIUM_7Z}" ; Build setup.exe command line - StrCpy $0 '"$TEMP\helium_install\setup.exe" --install-archive="$TEMP\helium_install\helium.7z" --do-not-launch-chrome' + StrCpy $0 '"$PLUGINSDIR\setup.exe" --install-archive="$PLUGINSDIR\helium.7z" --do-not-launch-chrome' ${If} $InstallType == "system" StrCpy $0 '$0 --system-level' @@ -309,12 +334,6 @@ Section "Install" SecInstall ${EndSwitch} - ; Clean up extracted files - DetailPrint "Cleaning up..." - Delete "$TEMP\helium_install\setup.exe" - Delete "$TEMP\helium_install\helium.7z" - RMDir "$TEMP\helium_install" - ; Abort if installation failed (prevents finish page from showing success) ${If} $InstallFailed == "1" Abort diff --git a/patches/helium/windows/enable-browser-updates.patch b/patches/helium/windows/enable-browser-updates.patch new file mode 100644 index 0000000..51758d6 --- /dev/null +++ b/patches/helium/windows/enable-browser-updates.patch @@ -0,0 +1,62 @@ +--- a/components/helium_services/helium_services_helpers.cc ++++ b/components/helium_services/helium_services_helpers.cc +@@ -6,6 +6,7 @@ + + #include + ++#include "build/build_config.h" + #include "base/functional/bind.h" + #include "base/strings/stringprintf.h" + #include "components/helium_services/pref_names.h" +@@ -19,9 +20,15 @@ + + #if BUILDFLAG(IS_MAC) + #if defined(ARCH_CPU_ARM_FAMILY) +-constexpr std::string cpu_arch = "arm64"; ++constexpr char kBrowserUpdateArch[] = "arm64"; + #else +-constexpr std::string cpu_arch = "x86_64"; ++constexpr char kBrowserUpdateArch[] = "x86_64"; ++#endif ++#elif BUILDFLAG(IS_WIN) ++#if defined(ARCH_CPU_ARM64) ++constexpr char kBrowserUpdateArch[] = "arm64"; ++#else ++constexpr char kBrowserUpdateArch[] = "x64"; + #endif + #endif + +@@ -130,11 +137,14 @@ + + #if BUILDFLAG(IS_MAC) + std::string path = base::StringPrintf( +- "/updates/mac/appcast-%s.xml", cpu_arch); +- return GetServicesBaseURL(prefs).Resolve(path); ++ "/updates/mac/appcast-%s.xml", kBrowserUpdateArch); ++#elif BUILDFLAG(IS_WIN) ++ std::string path = base::StringPrintf( ++ "/updates/win/appcast-%s.xml", kBrowserUpdateArch); + #else + return GetDummyURL(); + #endif ++ return GetServicesBaseURL(prefs).Resolve(path); + } + + GURL GetComponentUpdateURL(const PrefService* prefs) { +--- a/chrome/app/settings_strings.grdp ++++ b/chrome/app/settings_strings.grdp +@@ -2075,12 +2075,12 @@ + + Allow automatic browser and component updates + +- ++ + + Helium will automatically download and install browser and component updates as they become available. We recommend keeping this setting enabled to ensure you get the latest features and security updates. + + +- ++ + + Helium will automatically download and install browser and component updates as they become available. We recommend keeping this setting enabled to ensure you get the latest features and security updates. Automatic core browser updates are not available on this platform yet, but component updates are. Please use external software to keep Helium up to date. + diff --git a/patches/helium/windows/winsparkle-integration.patch b/patches/helium/windows/winsparkle-integration.patch new file mode 100644 index 0000000..fe76dcd --- /dev/null +++ b/patches/helium/windows/winsparkle-integration.patch @@ -0,0 +1,600 @@ +--- a/chrome/browser/ui/webui/help/version_updater_win.cc ++++ b/chrome/browser/ui/webui/help/version_updater_win.cc +@@ -2,163 +2,340 @@ + // Use of this source code is governed by a BSD-style license that can be + // found in the LICENSE file. + ++#include "chrome/browser/ui/webui/help/version_updater_win.h" ++ + #include "chrome/browser/ui/webui/help/version_updater.h" ++ ++// windows.h must be included before shellapi.h. ++#include ++ ++#include ++#include ++#include + + #include + #include + ++#include "base/callback_list.h" ++#include "base/files/file_path.h" + #include "base/functional/bind.h" ++#include "base/memory/no_destructor.h" + #include "base/memory/weak_ptr.h" +-#include "base/task/thread_pool.h" +-#include "base/win/win_util.h" ++#include "base/strings/utf_string_conversions.h" + #include "chrome/browser/browser_process.h" +-#include "chrome/browser/first_run/upgrade_util.h" +-#include "chrome/browser/google/google_update_win.h" ++#include "chrome/browser/lifetime/application_lifetime.h" ++#include "chrome/browser/profiles/profile.h" ++#include "chrome/browser/profiles/profile_manager.h" + #include "chrome/grit/generated_resources.h" ++#include "chrome/install_static/install_util.h" ++#include "components/helium_services/helium_services_helpers.h" ++#include "components/helium_services/pref_names.h" ++#include "components/prefs/pref_change_registrar.h" ++#include "components/version_info/version_info.h" ++#include "content/public/browser/browser_task_traits.h" + #include "content/public/browser/browser_thread.h" +-#include "content/public/browser/web_contents.h" +-#include "ui/aura/window.h" +-#include "ui/aura/window_tree_host.h" + #include "ui/base/l10n/l10n_util.h" +-#include "ui/gfx/native_ui_types.h" ++#include "winsparkle.h" + + namespace { ++ ++constexpr char kWinSparkleRegistryPath[] = ++ "Software\\imput\\Helium\\WinSparkle"; ++constexpr wchar_t kUpdateInstallerArgs[] = L"/UPDATE"; ++constexpr int kUpdateCheckIntervalSeconds = 60 * 60; ++ ++using WinSparkleStatusCallbackList = ++ base::RepeatingCallbackList; ++ ++bool VerifyInstallerSignature(const std::wstring& installer_path) { ++ WINTRUST_FILE_INFO file_info = {}; ++ file_info.cbStruct = sizeof(file_info); ++ file_info.pcwszFilePath = installer_path.c_str(); ++ ++ WINTRUST_DATA trust_data = {}; ++ trust_data.cbStruct = sizeof(trust_data); ++ trust_data.dwUIChoice = WTD_UI_NONE; ++ trust_data.fdwRevocationChecks = WTD_REVOKE_WHOLECHAIN; ++ trust_data.dwUnionChoice = WTD_CHOICE_FILE; ++ trust_data.dwStateAction = WTD_STATEACTION_VERIFY; ++ trust_data.dwProvFlags = WTD_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT; ++ trust_data.pFile = &file_info; ++ ++ GUID policy = WINTRUST_ACTION_GENERIC_VERIFY_V2; ++ const LONG verify_result = WinVerifyTrust(nullptr, &policy, &trust_data); ++ ++ trust_data.dwStateAction = WTD_STATEACTION_CLOSE; ++ WinVerifyTrust(nullptr, &policy, &trust_data); ++ ++ return verify_result == ERROR_SUCCESS; ++} ++ ++class WinSparkleController { ++ public: ++ static WinSparkleController& Get() { ++ static base::NoDestructor instance; ++ return *instance; ++ } ++ ++ WinSparkleController(const WinSparkleController&) = delete; ++ WinSparkleController& operator=(const WinSparkleController&) = delete; ++ ++ void Initialize() { ++ DCHECK_CURRENTLY_ON(content::BrowserThread::UI); ++ ++ if (!g_browser_process) { ++ return; ++ } ++ ++ ProfileManager* profile_manager = g_browser_process->profile_manager(); ++ if (!profile_manager) { ++ return; ++ } ++ ++ Profile* profile = profile_manager->GetLastUsedProfileIfLoaded(); ++ if (!profile) { ++ return; ++ } ++ ++ if (observed_profile_path_ != profile->GetPath()) { ++ observed_profile_path_ = profile->GetPath(); ++ pref_change_registrar_ = std::make_unique(); ++ pref_change_registrar_->Init(profile->GetPrefs()); ++ helium::ConfigurePrefChangeRegistrarFor( ++ prefs::kHeliumUpdateFetchingEnabled, *pref_change_registrar_, ++ base::BindRepeating(&WinSparkleController::OnPrefsChangedCallback)); ++ needs_reconfigure_ = true; ++ } ++ ++ if (needs_reconfigure_) { ++ Reconfigure(); ++ } ++ } ++ ++ bool IsAvailable() const { return initialized_; } ++ ++ base::CallbackListSubscription RegisterStatusCallback( ++ WinSparkleStatusCallbackList::CallbackType callback) { ++ return status_callbacks_.Add(std::move(callback)); ++ } ++ ++ void CheckForUpdatesFromUi() { ++ DCHECK_CURRENTLY_ON(content::BrowserThread::UI); ++ Initialize(); ++ if (!initialized_) { ++ Notify(VersionUpdater::DISABLED, 0, std::u16string()); ++ return; ++ } ++ win_sparkle_check_update_without_ui(); ++ } ++ ++ private: ++ WinSparkleController() = default; ++ ~WinSparkleController() = default; ++ ++ void OnPrefsChanged() { ++ DCHECK_CURRENTLY_ON(content::BrowserThread::UI); ++ needs_reconfigure_ = true; ++ Reconfigure(); ++ } ++ ++ void Reconfigure() { ++ DCHECK_CURRENTLY_ON(content::BrowserThread::UI); ++ ++ if (initialized_) { ++ win_sparkle_cleanup(); ++ initialized_ = false; ++ } ++ ++ needs_reconfigure_ = false; ++ ++ Profile* profile = GetLoadedProfile(); ++ if (!profile) { ++ pref_change_registrar_.reset(); ++ observed_profile_path_.clear(); ++ Notify(VersionUpdater::DISABLED, 0, std::u16string()); ++ return; ++ } ++ ++ PrefService* prefs = profile->GetPrefs(); ++ if (!prefs || !helium::ShouldAccessUpdateService(*prefs)) { ++ Notify(VersionUpdater::DISABLED, 0, std::u16string()); ++ return; ++ } ++ ++ appcast_url_ = helium::GetBrowserUpdateURL(*prefs).spec(); ++ if (appcast_url_.empty()) { ++ Notify(VersionUpdater::FAILED, 0, ++ l10n_util::GetStringUTF16(IDS_ABOUT_BOX_ERROR_UPDATE_CHECK_FAILED)); ++ return; ++ } ++ ++ const std::wstring version = ++ base::UTF8ToWide(version_info::GetHeliumVersionNumber()); ++ win_sparkle_set_registry_path(kWinSparkleRegistryPath); ++ win_sparkle_set_app_details(L"imput", L"Helium", version.c_str()); ++ win_sparkle_set_appcast_url(appcast_url_.c_str()); ++ win_sparkle_set_automatic_check_for_updates(1); ++ win_sparkle_set_update_check_interval(kUpdateCheckIntervalSeconds); ++ win_sparkle_set_can_shutdown_callback(&CanShutdownCallback); ++ win_sparkle_set_shutdown_request_callback(&ShutdownRequestCallback); ++ win_sparkle_set_did_find_update_callback(&DidFindUpdateCallback); ++ win_sparkle_set_did_not_find_update_callback(&DidNotFindUpdateCallback); ++ win_sparkle_set_update_cancelled_callback(&UpdateDismissedCallback); ++ win_sparkle_set_update_skipped_callback(&UpdateDismissedCallback); ++ win_sparkle_set_update_postponed_callback(&UpdateDismissedCallback); ++ win_sparkle_set_update_dismissed_callback(&UpdateDismissedCallback); ++ win_sparkle_set_error_callback(&ErrorCallback); ++ win_sparkle_set_user_run_installer_callback(&RunInstallerCallback); ++ win_sparkle_init(); ++ initialized_ = true; ++ } ++ ++ void PostStatus(VersionUpdater::Status status, ++ int progress, ++ std::u16string message) { ++ content::GetUIThreadTaskRunner({})->PostTask( ++ FROM_HERE, ++ base::BindOnce( ++ [](VersionUpdater::Status status, int progress, ++ std::u16string message) { ++ WinSparkleController::Get().Notify(status, progress, message); ++ }, ++ status, progress, std::move(message))); ++ } ++ ++ Profile* GetLoadedProfile() const { ++ if (!g_browser_process) { ++ return nullptr; ++ } ++ ++ ProfileManager* profile_manager = g_browser_process->profile_manager(); ++ if (!profile_manager) { ++ return nullptr; ++ } ++ ++ return profile_manager->GetLastUsedProfileIfLoaded(); ++ } ++ ++ static void OnPrefsChangedCallback() { Get().OnPrefsChanged(); } ++ ++ void Notify(VersionUpdater::Status status, ++ int progress, ++ const std::u16string& message) { ++ DCHECK_CURRENTLY_ON(content::BrowserThread::UI); ++ status_callbacks_.Notify(status, progress, message); ++ } ++ ++ static int __cdecl CanShutdownCallback() { return 1; } ++ ++ static void __cdecl ShutdownRequestCallback() { ++ content::GetUIThreadTaskRunner({})->PostTask( ++ FROM_HERE, ++ base::BindOnce([]() { chrome::ExitIgnoreUnloadHandlers(); })); ++ } ++ ++ static void __cdecl DidFindUpdateCallback() { ++ Get().PostStatus(VersionUpdater::UPDATING, 0, std::u16string()); ++ } ++ ++ static void __cdecl DidNotFindUpdateCallback() { ++ Get().PostStatus(VersionUpdater::UPDATED, 0, std::u16string()); ++ } ++ ++ static void __cdecl UpdateDismissedCallback() { ++ Get().PostStatus(VersionUpdater::UPDATED, 0, std::u16string()); ++ } ++ ++ static void __cdecl ErrorCallback() { ++ Get().PostStatus( ++ VersionUpdater::FAILED, 0, ++ l10n_util::GetStringUTF16(IDS_ABOUT_BOX_ERROR_UPDATE_CHECK_FAILED)); ++ } ++ ++ static int __cdecl RunInstallerCallback(const wchar_t* installer_path) { ++ if (!installer_path || !VerifyInstallerSignature(installer_path)) { ++ Get().PostStatus( ++ VersionUpdater::FAILED, 0, ++ l10n_util::GetStringUTF16(IDS_ABOUT_BOX_ERROR_UPDATE_CHECK_FAILED)); ++ return WINSPARKLE_RETURN_ERROR; ++ } ++ ++ SHELLEXECUTEINFOW shell_execute_info = {}; ++ shell_execute_info.cbSize = sizeof(shell_execute_info); ++ shell_execute_info.fMask = SEE_MASK_FLAG_NO_UI | SEE_MASK_NOASYNC; ++ shell_execute_info.lpVerb = L"open"; ++ shell_execute_info.lpFile = installer_path; ++ shell_execute_info.lpParameters = kUpdateInstallerArgs; ++ shell_execute_info.nShow = SW_SHOWNORMAL; ++ ++ if (!ShellExecuteExW(&shell_execute_info)) { ++ Get().PostStatus( ++ VersionUpdater::FAILED, 0, ++ l10n_util::GetStringUTF16(IDS_ABOUT_BOX_ERROR_UPDATE_CHECK_FAILED)); ++ return WINSPARKLE_RETURN_ERROR; ++ } ++ ++ Get().PostStatus(VersionUpdater::NEARLY_UPDATED, 0, std::u16string()); ++ return 1; ++ } ++ ++ base::FilePath observed_profile_path_; ++ bool initialized_ = false; ++ bool needs_reconfigure_ = false; ++ std::string appcast_url_; ++ std::unique_ptr pref_change_registrar_; ++ WinSparkleStatusCallbackList status_callbacks_; ++}; + + // Windows implementation of version update functionality, used by the WebUI + // About/Help page. +-class VersionUpdaterWin : public VersionUpdater, public UpdateCheckDelegate { ++class VersionUpdaterWin : public VersionUpdater { + public: +- // |owner_widget| is the parent widget hosting the update check UI. Any UI +- // needed to install an update (e.g., a UAC prompt for a system-level install) +- // will be parented to this widget. |owner_widget| may be given a value of +- // nullptr in which case the UAC prompt will be parented to the desktop. +- explicit VersionUpdaterWin(gfx::AcceleratedWidget owner_widget) +- : owner_widget_(owner_widget), weak_factory_(this) {} +- ++ VersionUpdaterWin() : weak_factory_(this) {} + VersionUpdaterWin(const VersionUpdaterWin&) = delete; + VersionUpdaterWin& operator=(const VersionUpdaterWin&) = delete; +- + ~VersionUpdaterWin() override = default; + + // VersionUpdater: + void CheckForUpdate(StatusCallback callback, PromoteCallback) override { +- // There is no supported integration with Google Update for Chromium. + callback_ = std::move(callback); +- ++ status_subscription_ = WinSparkleController::Get().RegisterStatusCallback( ++ base::BindRepeating(&VersionUpdaterWin::OnStatusChanged, ++ weak_factory_.GetWeakPtr())); + callback_.Run(CHECKING, 0, false, false, std::string(), 0, + std::u16string()); +- DoBeginUpdateCheck(false /* !install_update_if_possible */); +- } +- +- // UpdateCheckDelegate: +- void OnUpdateCheckComplete(const std::u16string& new_version) override { +- DCHECK_CURRENTLY_ON(content::BrowserThread::UI); +- if (new_version.empty()) { +- // Google Update says that no new version is available. Check to see if a +- // restart is needed for a previously-applied update to take effect. +- base::ThreadPool::PostTaskAndReplyWithResult( +- FROM_HERE, {base::MayBlock(), base::TaskPriority::USER_VISIBLE}, +- base::BindOnce(&upgrade_util::IsUpdatePendingRestart), +- base::BindOnce(&VersionUpdaterWin::OnPendingRestartCheck, +- weak_factory_.GetWeakPtr())); +- // Early exit since callback_ will be Run in OnPendingRestartCheck. +- return; +- } +- +- // Notify the caller that the update is now beginning and initiate it. +- DoBeginUpdateCheck(true /* install_update_if_possible */); +- callback_.Run(UPDATING, 0, false, false, std::string(), 0, +- std::u16string()); +- } +- +- void OnUpgradeProgress(int progress, +- const std::u16string& new_version) override { +- DCHECK_CURRENTLY_ON(content::BrowserThread::UI); +- callback_.Run(UPDATING, progress, false, false, std::string(), 0, +- std::u16string()); +- } +- +- void OnUpgradeComplete(const std::u16string& new_version) override { +- DCHECK_CURRENTLY_ON(content::BrowserThread::UI); +- callback_.Run(NEARLY_UPDATED, 0, false, false, std::string(), 0, +- std::u16string()); +- } +- +- void OnError(GoogleUpdateErrorCode error_code, +- const std::u16string& html_error_message, +- const std::u16string& new_version) override { +- DCHECK_CURRENTLY_ON(content::BrowserThread::UI); +- std::u16string message; +- Status status = FAILED; +- +- switch (error_code) { +- case GOOGLE_UPDATE_DISABLED_BY_POLICY: +- status = DISABLED_BY_ADMIN; +- message = l10n_util::GetStringUTF16(IDS_UPGRADE_DISABLED_BY_POLICY); +- break; +- case GOOGLE_UPDATE_DISABLED_BY_POLICY_AUTO_ONLY: +- status = DISABLED_BY_ADMIN; +- message = +- l10n_util::GetStringUTF16(IDS_UPGRADE_DISABLED_BY_POLICY_MANUAL); +- break; +- default: +- // html_error_message mentions error_code so don't combine messages. +- if (html_error_message.empty()) { +- message = +- l10n_util::GetStringFUTF16Int(IDS_UPGRADE_ERROR, error_code); +- } else { +- message = l10n_util::GetStringFUTF16( +- IDS_ABOUT_BOX_ERROR_DURING_UPDATE_CHECK, html_error_message); +- } +- break; +- } +- callback_.Run(status, 0, false, false, std::string(), 0, message); ++ helium::InitializeWinSparkleUpdater(); ++ WinSparkleController::Get().CheckForUpdatesFromUi(); + } + + private: +- void DoBeginUpdateCheck(bool install_update_if_possible) { +- // Disconnect from any previous attempts to avoid redundant callbacks. +- weak_factory_.InvalidateWeakPtrs(); +- BeginUpdateCheck(g_browser_process->GetApplicationLocale(), +- install_update_if_possible, owner_widget_, +- weak_factory_.GetWeakPtr()); +- } +- +- // A task run on the UI thread with the result of checking for a pending +- // restart. +- void OnPendingRestartCheck(bool is_update_pending_restart) { +- callback_.Run(is_update_pending_restart ? NEARLY_UPDATED : UPDATED, 0, +- false, false, std::string(), 0, std::u16string()); +- } +- +- // The widget owning the UI for the update check. +- gfx::AcceleratedWidget owner_widget_; +- +- // Callback used to communicate update status to the client. ++ void OnStatusChanged(Status status, ++ int progress, ++ const std::u16string& message) { ++ if (callback_.is_null()) { ++ return; ++ } ++ callback_.Run(status, progress, false, false, std::string(), 0, message); ++ } ++ + StatusCallback callback_; +- +- // Used for callbacks. ++ base::CallbackListSubscription status_subscription_; + base::WeakPtrFactory weak_factory_; + }; + + } // namespace + ++namespace helium { ++ ++void InitializeWinSparkleUpdater() { ++ WinSparkleController::Get().Initialize(); ++} ++ ++} // namespace helium ++ + std::unique_ptr VersionUpdater::Create( + content::WebContents* web_contents) { +- // Retrieve the HWND for the browser window that is hosting the update check. +- // This will be used as the parent for a UAC prompt, if needed. It's possible +- // this this window will no longer have focus by the time UAC is needed. In +- // that case, the UAC prompt will appear in the taskbar and will require a +- // user click. This is the least surprising thing we can do for the user, and +- // is the intended behavior for Windows applications. +- // It's also possible that the browser window hosting the update check will +- // have been closed by the time the UAC prompt is needed. In this case, the +- // web contents may no longer be hosted in a window, leading either +- // GetTopLevelNativeWindow or GetHost to return null. Passing nullptr to +- // VersionUpdaterWin will then also cause the UAC prompt to appear in the task +- // bar. +- gfx::NativeWindow window = web_contents->GetTopLevelNativeWindow(); +- aura::WindowTreeHost* window_tree_host = window ? window->GetHost() : nullptr; +- return std::make_unique( +- window_tree_host ? window_tree_host->GetAcceleratedWidget() : nullptr); ++ return std::make_unique(); + } +--- a/chrome/browser/ui/BUILD.gn ++++ b/chrome/browser/ui/BUILD.gn +@@ -3432,17 +3432,16 @@ + "//ui/events:dom_keycode_converter", + ] + +- libs += [ "crypt32.lib" ] +- +- if (is_chrome_branded) { +- sources += [ "webui/help/version_updater_win.cc" ] +- deps += [ +- "//chrome/browser/win/installer_downloader:prefs", +- "//chrome/updater/app/server/win:updater_legacy_idl", +- ] +- } else { +- sources += [ "webui/help/version_updater_basic.cc" ] +- } ++ libs += [ ++ "crypt32.lib", ++ "wintrust.lib", ++ ] ++ ++ sources += [ "webui/help/version_updater_win.cc" ] ++ deps += [ ++ "//components/helium_services", ++ "//third_party/winsparkle", ++ ] + } + + if (is_linux || is_mac) { +--- a/chrome/browser/chrome_browser_main_win.cc ++++ b/chrome/browser/chrome_browser_main_win.cc +@@ -56,6 +56,7 @@ + #include "chrome/browser/browser_features.h" + #include "chrome/browser/browser_process.h" + #include "chrome/browser/enterprise/platform_auth/platform_auth_policy_observer.h" ++#include "chrome/browser/ui/webui/help/version_updater_win.h" + #include "chrome/browser/first_run/first_run.h" + #include "chrome/browser/first_run/upgrade_util.h" + #include "chrome/browser/first_run/upgrade_util_win.h" +@@ -670,6 +671,8 @@ + + InitializeChromeElf(); + ++ helium::InitializeWinSparkleUpdater(); ++ + #if BUILDFLAG(USE_GOOGLE_UPDATE_INTEGRATION) + if constexpr (kShouldRecordActiveUse) { + did_run_updater_.emplace(); +--- a/chrome/tools/build/win/FILES.cfg ++++ b/chrome/tools/build/win/FILES.cfg +@@ -79,6 +79,11 @@ + 'filegroup': ['default'], + }, + { ++ 'filename': 'WinSparkle.dll', ++ 'buildtype': ['official'], ++ 'filegroup': ['default'], ++ }, ++ { + 'filename': '*.manifest', + 'buildtype': ['official'], + 'filegroup': ['default'], +--- a/chrome/browser/ui/webui/help/version_updater_win.h ++++ b/chrome/browser/ui/webui/help/version_updater_win.h +@@ -0,0 +1,15 @@ ++// Copyright 2026 The Helium Authors ++// You can use, redistribute, and/or modify this source code under ++// the terms of the GPL-3.0 license that can be found in the LICENSE file. ++ ++#ifndef CHROME_BROWSER_UI_WEBUI_HELP_VERSION_UPDATER_WIN_H_ ++#define CHROME_BROWSER_UI_WEBUI_HELP_VERSION_UPDATER_WIN_H_ ++ ++namespace helium { ++ ++// Initializes the in-process WinSparkle updater after browser startup. ++void InitializeWinSparkleUpdater(); ++ ++} // namespace helium ++ ++#endif // CHROME_BROWSER_UI_WEBUI_HELP_VERSION_UPDATER_WIN_H_ +--- a/third_party/winsparkle/BUILD.gn ++++ b/third_party/winsparkle/BUILD.gn +@@ -0,0 +1,34 @@ ++# Copyright 2026 The Helium Authors ++# You can use, redistribute, and/or modify this source code under ++# the terms of the GPL-3.0 license that can be found in the LICENSE file. ++ ++config("winsparkle_config") { ++ include_dirs = [ "//third_party/winsparkle/include" ] ++ ++ if (target_cpu == "arm64") { ++ lib_dirs = [ "//third_party/winsparkle/ARM64/Release" ] ++ } else if (target_cpu == "x64") { ++ lib_dirs = [ "//third_party/winsparkle/x64/Release" ] ++ } else { ++ assert(false, "WinSparkle only supports x64 and arm64 Windows builds") ++ } ++ ++ libs = [ "WinSparkle.lib" ] ++} ++ ++copy("winsparkle_runtime") { ++ if (target_cpu == "arm64") { ++ sources = [ "//third_party/winsparkle/ARM64/Release/WinSparkle.dll" ] ++ } else if (target_cpu == "x64") { ++ sources = [ "//third_party/winsparkle/x64/Release/WinSparkle.dll" ] ++ } else { ++ assert(false, "WinSparkle only supports x64 and arm64 Windows builds") ++ } ++ ++ outputs = [ "$root_out_dir/{{source_file_part}}" ] ++} ++ ++group("winsparkle") { ++ public_configs = [ ":winsparkle_config" ] ++ deps = [ ":winsparkle_runtime" ] ++} +--- a/third_party/winsparkle/README.chromium ++++ b/third_party/winsparkle/README.chromium +@@ -0,0 +1,14 @@ ++# Copyright 2026 The Helium Authors ++# You can use, redistribute, and/or modify this source code under ++# the terms of the GPL-3.0 license that can be found in the LICENSE file. ++ ++Name: WinSparkle ++Short Name: winsparkle ++URL: https://github.com/vslavik/winsparkle ++Version: 0.9.2 ++License: MIT ++License File: COPYING ++Description: ++WinSparkle is a software update framework for Windows applications. ++Local Modifications: ++Prebuilt binaries are downloaded and unpacked by helium-windows. diff --git a/patches/series b/patches/series index 13f21ee..fe724f0 100644 --- a/patches/series +++ b/patches/series @@ -21,3 +21,5 @@ ungoogled-chromium/windows/windows-fix-remove-unused-preferences-fields.patch ungoogled-chromium/windows/windows-fix-missing-includes.patch helium/windows/change-branding.patch +helium/windows/winsparkle-integration.patch +helium/windows/enable-browser-updates.patch