Files
Kiwy-Signage/windows/webview2_runtime/download_runtime_installers.ps1
T
ske087 9f5409685d Embedded WebView2 engine for web links (Windows)
Web links previously launched a separate Chrome/Edge kiosk process, which
caused the whole class of bugs in the tracker: the browser opening behind the
player, being handed off to an already-running instance and exiting instantly,
fighting for foreground/z-order, and leaking msedge.exe/chrome.exe processes
that were never closed.

WebView2 renders as a CHILD HWND of Kivy's own SDL window instead, so there is
no separate top-level browser to open behind the player, nothing to hand the
URL off to, no z-order contest, and no leaked browser process.

Windows/webview2_browser.py
  - Environment -> controller -> navigate, driven through pythonnet.
  - Async .NET Tasks are polled from Kivy's Clock. Calling GetAwaiter()
    .GetResult() would deadlock: the continuation needs the same thread's
    message pump.
  - The controller is a .NET IntPtr, not a Python int (CreateAsync overloads
    do not match otherwise).
  - NavigationCompleted is tracked so a page that never loads can be told
    apart from one that did. This matters on a closed network: an unreachable
    host paints a Chromium error page, and without this the player would show
    a blank/error screen for the item's whole slot instead of skipping it.
  - is_alive() reports True while starting up. Start-up is async, so a
    controller that does not exist yet is not a dead browser; treating it as
    one made the first web link after a cold start be skipped instantly.

Windows/webview2_runtime.py
  - Detects the Runtime (registry pv value, SDK probe as fallback) and
    installs it silently when missing, unelevated, which produces a per-user
    install and therefore never raises a UAC prompt on the signage display.
  - Success is decided by RE-READING the installed version, not by the
    installer exit code: Edge Update returns a non-zero HRESULT
    (-2147219416) when the Runtime is already current, which is not a failure.
  - On a closed network the online bootstrapper can never succeed, so it fails
    fast with an actionable message instead of hanging for the full timeout.
  - Failed attempts are cooldown-gated so a broken machine does not re-run an
    installer on every start.

Offline hardening
  - Browser arguments disable component updates, field trials, safe-browsing
    list fetches, translate and other internet chatter. On an isolated LAN
    each of those would otherwise have to time out, costing start-up latency.
    Pages on the local server are unaffected.

Engine order (best first): WebView2 -> CEF -> Chrome/Edge subprocess. CEF has
no wheels past Python 3.9 so it is dormant on this build; the subprocess engine
remains only as a last resort.

Also fixes the reason the Windows adapters were never used at all:
SignagePlayer.__init__ assigned self.weblink_adapter_factory = None, which
shadowed the CLASS attribute that run_win.py injects. play_weblink() therefore
fell back to the generic adapter, whose find_browser() uses shutil.which() and
finds nothing on Windows because Chrome/Edge are not on PATH. The instance
attribute is now only set when the class attribute is absent.

Verified: windows/test_webview2_embed.py, test_webview2_navigation.py and
test_webview2_offline.py all pass (a locally served page renders with all
internet traffic disabled), and the packaged exe reports
"weblink_launch engine=webview2-embedded" -> "weblink_launched" on every cycle
with no leaked browser processes.
2026-09-13 10:14:18 +03:00

66 lines
2.4 KiB
PowerShell

# Downloads the WebView2 Runtime installers into windows\webview2_runtime\.
#
# build.spec bundles:
# - MicrosoftEdgeWebview2Setup.exe (~1.7 MB) always
# - MicrosoftEdgeWebView2RuntimeInstallerX64.exe (~203 MB) only if present
#
# The bootstrapper is the small online installer (it downloads the Runtime
# from Microsoft). Run this script with -Offline to also fetch the standalone
# installer for machines that have no internet access — note that it makes the
# built .exe about 200 MB larger.
#
# Usage:
# .\download_runtime_installers.ps1
# .\download_runtime_installers.ps1 -Offline
[CmdletBinding()]
param(
[switch]$Offline
)
$ErrorActionPreference = 'Stop'
$dest = Join-Path $PSScriptRoot 'webview2_runtime'
if (-not (Test-Path $dest)) {
New-Item -ItemType Directory -Force -Path $dest | Out-Null
}
$bootstrapperUrl = 'https://go.microsoft.com/fwlink/p/?LinkId=2124703'
$standaloneUrl = 'https://go.microsoft.com/fwlink/?linkid=2124701' # x64
function Get-Installer {
param([string]$Url, [string]$FileName, [string]$Label)
$target = Join-Path $dest $FileName
Write-Host "[INFO] Downloading $Label ..." -ForegroundColor Cyan
Invoke-WebRequest -Uri $Url -OutFile $target -UseBasicParsing -MaximumRedirection 10
$file = Get-Item -LiteralPath $target
$sig = Get-AuthenticodeSignature -LiteralPath $target
$sizeMb = [math]::Round($file.Length / 1MB, 1)
Write-Host (" {0} {1} MB" -f $file.Name, $sizeMb)
if ($sig.Status -eq 'Valid' -and $sig.SignerCertificate.Subject -like '*Microsoft*') {
Write-Host " signature: Valid (Microsoft)" -ForegroundColor Green
}
else {
Write-Warning " signature: $($sig.Status) — verify this download!"
}
}
Get-Installer -Url $bootstrapperUrl -FileName 'MicrosoftEdgeWebview2Setup.exe' -Label 'Runtime bootstrapper (online)'
if ($Offline) {
Get-Installer -Url $standaloneUrl -FileName 'MicrosoftEdgeWebView2RuntimeInstallerX64.exe' -Label 'Runtime standalone installer (offline, x64)'
Write-Host ''
Write-Host '[WARN] The standalone installer adds ~203 MB to the built .exe.' -ForegroundColor Yellow
}
else {
Write-Host ''
Write-Host '[INFO] Offline installer skipped. Re-run with -Offline to include it.' -ForegroundColor DarkGray
}
Write-Host ''
Write-Host "[OK] Installers are in $dest" -ForegroundColor Green
Write-Host ' Next: rebuild with venv\Scripts\python.exe -m PyInstaller build.spec --clean --noconfirm'