fix(installer/windows): only a host that will actually RUN is a conflict
docker / deploy-docs (push) Successful in 41s
ci / web (push) Successful in 56s
ci / docs-site (push) Successful in 1m50s
apple / swift (push) Successful in 5m41s
ci / bench (push) Successful in 7m1s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 2m3s
ci / rust-arm64 (push) Successful in 9m51s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 27s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 27s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 30s
deb / build-publish-host (push) Successful in 11m32s
deb / build-publish-client-arm64 (push) Successful in 10m59s
decky / build-publish (push) Successful in 41s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 30s
docker / build-push (ci, ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Successful in 6m19s
android / android (push) Successful in 17m31s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Failing after 5m35s
windows-host / package (push) Successful in 11m21s
windows-host / winget-source (push) Skipped
arch / build-publish (push) Successful in 19m9s
docker / build-push-arm64cross (push) Successful in 5m7s
ci / rust (push) Successful in 22m25s
deb / build-publish (push) Successful in 10m49s
apple / screenshots (push) Successful in 23m8s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 21m34s

Field report within hours of 0.20.0 (HitFrostbite): `winget install
unom.PunktfunkHost` fails with "Installer failed with exit code: 1",
0x8A150006, after the hash verifies and the install starts.

That is Setup aborting on purpose. `StreamHostPresent` counted a conflicting
host as present if its SCM service key existed at ANY start type, OR if a bare
Program Files\<Name> directory existed — so a disabled service, or a folder left
behind by an uninstall, read as a live conflict. 3d893013 then made the warning
a SuppressibleMsgBox defaulting to IDNO so unattended installs could not hang on
it; under winget's /VERYSILENT /SUPPRESSMSGBOXES that default aborts
InitializeSetup, and Inno exits "failed to initialize" = code 1. Net effect:
anyone with Sunshine on disk — the exact audience most likely to migrate — could
not install via winget at all, with no visible reason.

The other candidate for exit 1 is excluded rather than assumed: the manifest
declares MinimumOSVersion 10.0.22621.0 matching the script's MinVersion, so
winget refuses an unsupported OS BEFORE downloading, and the report shows the
hash verified and the install starting. The conflict check is also the only
`Result := False` in InitializeSetup.

Detection now keys on the service's `Start` value (0 boot / 1 system /
2 automatic / 3 manual / 4 disabled), counting only 0-2 — a host that comes up
on its own is the only kind that can take the GameStream ports or load a second
virtual-display driver. Directories no longer count at all: they were the
noisiest signal and the one that survives an uninstall. This makes the installer
agree with the tray, which dropped its always-on warning over a merely-INSTALLED
Sunshine in this same release (3e782852) for exactly the same reason. The
message now says "installed and set to start automatically" and offers disabling
the service as a remedy, both of which are what is actually detected.

A conflicting host running as a plain user process is deliberately not detected:
pure Pascal cannot enumerate processes, it is a runtime rather than install-time
condition, and the host already reports it via the `punktfunk::detect` startup
warning, `detect-conflicts`, and /api/v1/local/summary.

Verified by compiling the [Code] section with ISCC 6.7.1 on the windows-amd64
runner (WithWeb defined, as CI does). That caught two defects in my own first
draft that review would not have: a continuation line STARTING with #13#10, which
ISPP reads as a preprocessor directive ("Unknown preprocessor directive", compile
aborted), and `{commonpf}` written inside a `{ }` comment, which closes it early
since Inno comments do not nest. Both are noted at the sites so they are not
reintroduced.
This commit is contained in:
2026-07-27 09:00:34 +02:00
parent ec9aa415f6
commit 37781a610f
+34 -15
View File
@@ -354,15 +354,31 @@ begin
Result := ExpandConstant('{commonappdata}\punktfunk\host.env'); Result := ExpandConstant('{commonappdata}\punktfunk\host.env');
end; end;
{ True if another Moonlight-compatible streaming host is installed - by its SCM service key or its { True if another Moonlight-compatible streaming host is not merely present but will actually RUN.
Program Files directory. Sunshine and its forks all register a "<Name>Service" and install under Sunshine and its forks register a "<Name>Service"; `Start` is that service's start type
Program Files, so a registry + directory probe catches them without enumerating processes (which (REG_DWORD): 0 boot, 1 system, 2 automatic, 3 manual, 4 disabled. Only 0-2 come up on their own,
pure Pascal can't do cleanly). } and only a host that comes up can take the GameStream ports or load a second virtual-display
function StreamHostPresent(SvcKey, DirName: String): Boolean; driver - which is the entire content of the warning below.
DELIBERATELY NARROWER than it was. The old probe also counted the service key existing at ANY
start type, plus a bare Program Files\<Name> directory - so a disabled service, or a leftover folder
from an uninstall, read as a live conflict. Combined with the silent-install default of IDNO that
aborted setup, and a field report followed within hours of 0.20.0: `winget install
unom.PunktfunkHost` failed with exit code 1 (0x8A150006) on a box whose Sunshine was not running.
Nothing about a dormant install can clash, and the tray reached the same conclusion in this same
release (3e782852 dropped its always-on warning over a merely-INSTALLED Sunshine as a false
alarm) - the two surfaces now agree.
A host running as a plain user process rather than a service is not detected here, by choice:
that cannot be seen from pure Pascal, it is a runtime condition rather than an install-time one,
and the host already reports it where it belongs (the `punktfunk::detect` startup warning, the
`detect-conflicts` subcommand, and /api/v1/local/summary). }
function StreamHostEnabled(SvcKey: String): Boolean;
var
StartType: Cardinal;
begin begin
Result := RegKeyExists(HKLM, 'SYSTEM\CurrentControlSet\Services\' + SvcKey) Result := RegQueryDWordValue(HKLM, 'SYSTEM\CurrentControlSet\Services\' + SvcKey, 'Start', StartType)
or DirExists(ExpandConstant('{commonpf}\' + DirName)) and (StartType <= 2);
or DirExists(ExpandConstant('{commonpf32}\' + DirName));
end; end;
{ Runs before any wizard page - the earliest point we can warn. Detect a conflicting host and let { Runs before any wizard page - the earliest point we can warn. Detect a conflicting host and let
@@ -375,11 +391,11 @@ begin
{ Record the fresh-vs-upgrade verdict while host.env still reflects the PREVIOUS run. } { Record the fresh-vs-upgrade verdict while host.env still reflects the PREVIOUS run. }
FreshHostInstall := not FileExists(HostEnvPath); FreshHostInstall := not FileExists(HostEnvPath);
Found := ''; Found := '';
if StreamHostPresent('SunshineService', 'Sunshine') then Found := Found + ' - Sunshine' + #13#10; if StreamHostEnabled('SunshineService') then Found := Found + ' - Sunshine' + #13#10;
if StreamHostPresent('ApolloService', 'Apollo') then Found := Found + ' - Apollo' + #13#10; if StreamHostEnabled('ApolloService') then Found := Found + ' - Apollo' + #13#10;
if StreamHostPresent('VibeshineService', 'Vibeshine') then Found := Found + ' - Vibeshine' + #13#10; if StreamHostEnabled('VibeshineService') then Found := Found + ' - Vibeshine' + #13#10;
if StreamHostPresent('VibepolloService', 'Vibepollo') then Found := Found + ' - Vibepollo' + #13#10; if StreamHostEnabled('VibepolloService') then Found := Found + ' - Vibepollo' + #13#10;
if StreamHostPresent('LuminalShineService', 'LuminalShine') then Found := Found + ' - LuminalShine' + #13#10; if StreamHostEnabled('LuminalShineService') then Found := Found + ' - LuminalShine' + #13#10;
if Found <> '' then if Found <> '' then
{ SuppressibleMsgBox, NOT MsgBox: a plain MsgBox ignores /SUPPRESSMSGBOXES and displays even { SuppressibleMsgBox, NOT MsgBox: a plain MsgBox ignores /SUPPRESSMSGBOXES and displays even
under /VERYSILENT - i.e. an unattended install (winget) would block on a modal dialog with no under /VERYSILENT - i.e. an unattended install (winget) would block on a modal dialog with no
@@ -387,12 +403,15 @@ begin
install onto a box that already runs Sunshine/Apollo ABORTS (Setup exits non-zero) instead of install onto a box that already runs Sunshine/Apollo ABORTS (Setup exits non-zero) instead of
proceeding into the unsupported dual-host state the message describes. } proceeding into the unsupported dual-host state the message describes. }
Result := SuppressibleMsgBox( Result := SuppressibleMsgBox(
'Another game-streaming host is already installed on this PC:' + #13#10#13#10 + Found + #13#10 + { NB: keep #13#10 off the START of a line - ISPP reads a leading '#' as a preprocessor
directive and aborts the compile with "Unknown preprocessor directive". }
'Another game-streaming host is installed on this PC and set to start automatically:' + #13#10#13#10 + Found + #13#10 +
'Running Punktfunk alongside Sunshine / Apollo / other Moonlight-compatible hosts is NOT ' + 'Running Punktfunk alongside Sunshine / Apollo / other Moonlight-compatible hosts is NOT ' +
'supported. They bind the same GameStream network ports (47984, 47989, 47998-48010) and ' + 'supported. They bind the same GameStream network ports (47984, 47989, 47998-48010) and ' +
'install a conflicting virtual-display driver, which causes pairing failures, "address ' + 'install a conflicting virtual-display driver, which causes pairing failures, "address ' +
'already in use" errors and capture glitches.' + #13#10#13#10 + 'already in use" errors and capture glitches.' + #13#10#13#10 +
'Stop and uninstall the other host before using Punktfunk.' + #13#10#13#10 + 'Stop and disable its service (or uninstall it) before using Punktfunk. A host that is ' +
'installed but disabled does not clash and is not reported here.' + #13#10#13#10 +
'Continue with the installation anyway?', 'Continue with the installation anyway?',
mbConfirmation, MB_YESNO or MB_DEFBUTTON2, IDNO) = IDYES; mbConfirmation, MB_YESNO or MB_DEFBUTTON2, IDNO) = IDYES;
end; end;