In-app updates¶
A Pulp standalone app can check for and install new versions of itself with Sparkle 2. One CMake call turns it on; the app then offers updates in the two places macOS users look for them:
- The application menu: About
, then Check for Updates… directly under it, then Settings…, Services, Hide and Quit. - Settings: an Updates group with an Automatically check for updates toggle, a Check for Updates… button, the current version and when the app last checked, and a short note saying where updates come from and how they install.
Plug-ins (AU, VST3, CLAP, AAX) never contain the updater and never show either surface. They run inside a host's process, where an installer has no business running; updating the app updates them, because the update is the whole installer package.
Turn it on¶
pulp_add_sparkle(MyPlugin_Standalone
FEED_URL "https://github.com/me/myplugin/releases/latest/download/appcast.xml"
PUBLIC_ED_KEY "<44-character base64 Ed25519 public key>"
RELEASES_URL "https://github.com/me/myplugin/releases"
INSTALLER package # the update is the signed .pkg
AUTOMATIC_CHECKS ON) # check on a schedule without asking first
That is all an app that uses Pulp's built-in Settings panel needs. The menu item, the Updates tab and the automatic-check preference come with it. Keys, packaging and publishing the feed are covered in Shipping.
| Argument | What it changes |
|---|---|
AUTOMATIC_CHECKS ON |
Scheduled checks are on from the first launch (the toggle starts on). Omit it and Sparkle asks the user on the second launch instead. Either way the user's choice is saved by Sparkle and the toggle shows it. |
RELEASES_URL |
Linked from the Settings note ("Updates download from MyPlugin's GitHub releases."). |
INSTALLER package |
The note says installing quits and reopens the app and asks for an administrator password, which is what a package update does. app (a replacement .app) says it quits and reopens, without the password. Omitted: the note says nothing about installing. |
AUTOMATIC_INSTALL ON |
Lets Sparkle install updates in the background. Off by default, and the note then says updates are never installed automatically. |
The note is generated from these facts, so it stays true for each app instead of being copied from another one.
A custom editor's own Settings¶
An editor that draws its own Settings (a JS/React document, or a native view tree that replaces Pulp's panel) adds the same group itself. The updater lives in the standalone; the editor code, shared by every format, asks whether one is there.
JS editor. Register the bridge messages once, in the C++ that sets up the
editor's EditorBridge:
#include <pulp/format/app_updates_bridge.hpp>
pulp::format::add_app_update_handlers(editor_bridge_);
and read them from the document with the @pulp/react client:
import { useAppUpdates } from '@pulp/react';
function UpdatesGroup({ dispatch }) {
const [status, updates] = useAppUpdates(dispatch, 1000);
if (!status?.available) return null; // a plug-in: render nothing
return (
<section>
<label>
<input type="checkbox" checked={status.automaticChecks}
onChange={e => updates.setAutomatic(e.target.checked)} />
Automatically check for updates
</label>
<button disabled={!status.canCheckNow} onClick={updates.check}>
Check for Updates…
</button>
<p>{status.versionText} · {status.lastCheckText}</p>
<p>{status.note} <a href={status.releasesUrl}>Releases</a></p>
</section>
);
}
dispatch is the function the editor already uses to reach its
EditorBridge (it takes {type, payload, id} JSON and returns the response).
Without a bundler, appUpdatesClient(dispatch) is the same thing as plain
promises, and the messages themselves are pulp_updates_get,
pulp_updates_check and pulp_updates_set_automatic {on}.
Native editor. Add the ready-made group:
#include <pulp/format/app_updates_settings_view.hpp>
if (pulp::format::app_update_status().available)
settings->add_child(std::make_unique<pulp::format::AppUpdatesSettingsView>());
Call its refresh() from the editor's idle/poll so the last-check time moves
after a scheduled check.
Before and after, for an app author¶
| Before | After | |
|---|---|---|
| App menu | pulp_add_sparkle(); item placed above Settings…, no About |
same call; About, then Check for Updates…, standard layout |
| Pulp Settings panel | nothing | Updates tab, automatic |
| Custom JS Settings | write an EditorBridge handler per action, read Sparkle through the Objective-C runtime yourself, hand-write the note | one add_app_update_handlers() line in C++, useAppUpdates() in the document |
| Plug-in builds | had to be excluded by hand | available: false, nothing renders |
Development builds¶
A build without Sparkle (no pulp_add_sparkle(), or an app identity that reads
no feed) shows neither surface, so it can never update itself from the release
feed. To exercise the wiring anyway, launch it with
PULP_STANDALONE_UPDATER=stub: the menu item and Settings controls appear,
backed by a stub that never contacts a feed. A check only records its time, and
the note says the build has no update feed.
For a build that does embed Sparkle, scheduled checks start only when the app
carries a Developer ID signature; PULP_STANDALONE_UPDATER=on starts them in
an ad hoc build (for a loopback practice feed) and =off disables the updater.
Plug-ins later¶
Nothing is built for plug-ins today. The service a plug-in's editor reads,
pulp::format::AppUpdateService, is deliberately backend-neutral: a future
plug-in service could read the same appcast without running any installer in
the host, report that a newer version exists, and have its
check_for_updates() open releases_url or ask the standalone app to update.
The editor code above would show it with no change.