Repository navigation
Updates
src/update/updater.cpp, src/update/updater.h
Settings → About compares this build against the newest release on GitHub, either once at startup or on request. Everything that touches the network runs on a thread of its own; the UI only ever reads a snapshot.
CheckAsync(bool announce) fetches the latest release from the GitHub API and
compares the tag against the version this build reports.
The repository is asked for by number, not by name:
/repositories/1340564357/releases/latest, with /repos/NuclearMeltdown/qBlank/
kept only as a fallback. A number survives a rename and even a handover to
another account, which a name does not. For the same reason the download is
picked by the release asset's label rather than by its file name — the file
name is the thing that changes when the program is renamed.
PickProgram() asks for the label of this build's architecture first
(app-x64, app-arm64, app-x86), then for a plain app, and if a release
carries no labels at all it takes the largest .exe that is not the migrator —
the migrator is a few hundred kilobytes and the program is megabytes, so even a
release somebody forgot to label lands on the right file. See
src/update/release_source.cpp.
The label is not only a key, though, because GitHub shows it instead of the
file name: a release labelled app-x64 and migrator lists two downloads that
say nothing about what they are. So a label is written for both readers at once
— qBlank.exe (app-x64), CapView.exe (migrator) — and the word is looked for
inside it. As a word, with the hyphen counted as part of one: app must not
match halfway into app-x64, or a plain-app release would hand an ARM build
to an x64 machine. The download button on the website answers the same question
the same way, for the same reason — the migrator is uploaded first, so anything
picking "the first .exe" picks the wrong one.
The announce flag is the whole of the difference between the two entry points:
// Whether finding something is worth interrupting anybody for. True only for
// the check made at startup: somebody who pressed "check now" is already
// looking at the answer, and a dialog on top of it is just in the way.
bool announce;-
At startup —
announce = true. When something is found, a notice appears over the picture, once per session whatever you do with it. -
From the button —
announce = false. The answer appears in the tab you are already looking at, and no popup.
That second case was a bug: manually checking used to raise the popup as well, on top of the tab that had already answered.
Nothing is downloaded until you ask. The startup check only asks.
Opens the GitHub release page for the version found (App::OpenReleasePage())
rather than trying to render release notes in the settings window. GitHub already
formats them, and the page is one click from everything else about the release.
The status carries an enum, not a sentence:
enum class UpdateError {
None, NoNetwork, NoServer, NoRequest, NoAnswer, HttpStatus, Transfer,
Unreadable, NoAsset, NoUrl, NotAProgram, WriteFailed, MoveAsideFailed,
InsertFailed,
};The work happens on a thread of its own and the language can be changed at any
time, so the wording is picked where it is shown rather than where it is
raised — UpdateErrorText(const UpdateStatus&) does that in the language
selected right now.
Before this, update errors were German-only strings baked in at the point of failure.
httpStatus is carried alongside, and is meaningful only with
UpdateError::HttpStatus.
Installing replaces qBlank.exe itself. Windows will not let a running image be
overwritten, but it will let it be renamed — so:
qBlank.exe.new ← the download, written first
qBlank.exe → qBlank.exe.old (MoveFileExW)
qBlank.exe.new → qBlank.exe (MoveFileExW)
CleanUpPreviousBuild() deletes .old at the next start. Nothing has to be running
for that to work, and there is no helper process to go missing — which is how
most self-updaters fail.
Is it actually a program?
if (data.size() < 256 * 1024 || data[0] != 'M' || data[1] != 'Z') { … NotAProgram }Every Windows executable starts with those two bytes, and a redirect page or an error document does not. Overwriting the program with an HTML page would be a remarkably annoying way to find that out later.
If the second rename fails, the first is undone.
if (!::MoveFileExW(fresh.c_str(), exe.c_str(), MOVEFILE_REPLACE_EXISTING)) {
::MoveFileExW(old.c_str(), exe.c_str(), MOVEFILE_REPLACE_EXISTING);
…
}A failed update leaves the program exactly as it was rather than gone.
An installation directory the user cannot write to refuses the swap and says so
(MoveAsideFailed / WriteFailed). qBlank does not elevate to update itself —
if it is installed somewhere that needs rights to write, replacing it is a
decision for whoever put it there.
RestartIntoNewBuild() starts the freshly installed build and asks the caller to
quit. It returns false when nothing is waiting.
This program was called CapView up to and including 3.7. Version 4.0 is that same program under the name it keeps, and getting there had to work through the updater that was already shipped — a build from 3.7 cannot be taught anything after the fact.
The migrator. Every release carries a second, small executable called
CapView.exe (src/migrate/migrator.cpp). The updater in 3.7 looks for a
release asset with exactly that file name, downloads it over itself and restarts
it, so the last build under the old name walks into the bridge on its own. The
bridge asks the release which asset is the program, downloads it under whatever
name the release gives it, unregisters a virtual camera registered from that
folder, repoints the shortcuts, renames itself to <new name>.exe.old and starts
the new build.
It never spells out the new name — it asks the release. That is why it is shipped unchanged with every release from here on and never needs rebuilding: a second rename years from now needs nothing from it, because the only name it knows is the one it was born with, and that one cannot change any more.
An ordinary update across a rename. The updater keeps the old file name when
it swaps a new build in — it replaces the running image, and the running image
is called whatever it is called. AdoptOwnName() in src/app_identity.cpp
corrects that at the next start: it renames the running executable to the name
this build answers to and points every shortcut it can find at the new path.
Windows allows renaming a running image, which is the same trick the update
itself uses.
The settings. They are not moved by anybody. FindForeignSettings()
recognises a settings file by its content, not by its name, so it finds a
CapView.json next to the program without a list of former names having to stay
correct. With only that file there, it is adopted silently. With a qBlank.json
beside it, the program asks once, in the language stored in the file it found,
and the answer it does not get is "do nothing and ask again next time". Nothing
is ever deleted: a file that loses is renamed to .bak, not removed. See
Configuration and profiles.
What keeps the old name on purpose. The migrator and its version strings, the shared section names the virtual camera is found through, and every sentence about a version that really was called CapView.
The virtual camera's filter is installed separately and is not replaced by an update — Windows keeps the DLL locked while a camera is in use, and the registration is machine-wide.
That is why the shared memory carries a version number both halves check: after an update the executable and the installed filter genuinely can be from different builds. See Virtual camera.