Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
029840e
chore: stage Windows package hardening
ami3go Aug 20, 2026
7ab6de9
chore: apply Windows package hardening
ami3go Aug 20, 2026
0aa835d
chore: trigger Windows hardening apply workflow
ami3go Aug 20, 2026
ad35db5
chore: run Windows hardening workflow on PR
ami3go Aug 20, 2026
1bce6e6
chore: remove Windows hardening trigger
ami3go Aug 20, 2026
1c433bf
chore: remove Windows hardening helper
ami3go Aug 20, 2026
9474247
chore: remove Windows hardening workflow
ami3go Aug 20, 2026
6ed8b29
fix: add hardened Windows portable package
ami3go Aug 20, 2026
b789b6a
fix: add stable Windows onedir package
ami3go Aug 20, 2026
fba6962
chore: stage Windows application icon
ami3go Aug 20, 2026
a2aecf2
fix: validate bundled ADB from Windows package
ami3go Aug 20, 2026
dfdac5b
fix: generate Windows icon during package build
ami3go Aug 20, 2026
fd1aaa5
fix: generate Windows icon for onedir build
ami3go Aug 20, 2026
1d30c63
fix: classify known Android 16 scrcpy display failure
ami3go Aug 20, 2026
9deb882
ci: harden Windows package validation
ami3go Aug 20, 2026
407ad32
ci: publish stable Windows onedir ZIP
ami3go Aug 20, 2026
447d7fd
test: protect Windows packaging hardening
ami3go Aug 20, 2026
8f991b0
test: classify Android 16 scrcpy display-listener failure
ami3go Aug 20, 2026
ba478c4
docs: add Windows packaging and troubleshooting guide
ami3go Aug 20, 2026
84eff34
docs: add Windows release HIL checklist
ami3go Aug 20, 2026
f8ab1b0
fix: expose browser diagnostics for Windows video issues
ami3go Aug 20, 2026
d8d1026
fix: surface Windows browser and ADB diagnostics
ami3go Aug 20, 2026
5c14c70
build: update browser diagnostics runtime
ami3go Aug 20, 2026
a501f9c
build: update Windows diagnostics runtime
ami3go Aug 20, 2026
1bc7c39
test: cover browser diagnostics
ami3go Aug 20, 2026
767958a
ci: prebuild web client before integrity gate
ami3go Aug 20, 2026
c477409
test: protect Windows user diagnostics
ami3go Aug 20, 2026
1e09778
ci: update pinned setup-python action
ami3go Aug 20, 2026
2dbf9f8
test: match generated Windows icon source path
ami3go Aug 20, 2026
3378d49
ci: lock setup-python v6 action
ami3go Aug 20, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 58 additions & 18 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ jobs:
libxcb-render-util0 \
libxcb-shape0 \
libxcb-xkb1
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6
with:
python-version: '3.11.15'
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
Expand All @@ -50,10 +50,16 @@ jobs:
test "$(.tools/uv/uv --version | awk '{print $1 " " $2}')" = "uv 0.12.4"
- name: Sync locked Python environment
run: .tools/uv/uv sync --locked --extra dev
- working-directory: packages/scrcpy-protocol
run: npm ci
- working-directory: apps/web-client
run: npm ci
- name: Build protocol package before integrity checks
working-directory: packages/scrcpy-protocol
run: |
npm ci
npm run build
- name: Build web client before integrity checks
working-directory: apps/web-client
run: |
npm ci
npm run build
- name: Run release gate
run: .tools/uv/uv run python tools/release_gate.py --require-web-client-build --output /tmp/release-gate.json
- name: Generate build SBOM and license inventory
Expand All @@ -68,7 +74,7 @@ jobs:
runs-on: windows-2025
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6
with:
python-version: '3.11.9'
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
Expand Down Expand Up @@ -99,24 +105,58 @@ jobs:
Expand-Archive -Path platform-tools.zip -DestinationPath platform-tools
$adb = (Resolve-Path "platform-tools/platform-tools").Path
"DWD_ADB_DIR=$adb" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8
- name: Build Windows executable
run: python -m PyInstaller --noconfirm --clean packaging/pyinstaller/DroidWebDisplay.spec
- name: Smoke-test Windows desktop host
- name: Build Windows portable executable
run: python -m PyInstaller --noconfirm --clean packaging/pyinstaller/DroidWebDisplayWindows.spec
- name: Build Windows stable onedir package
run: python -m PyInstaller --noconfirm --clean packaging/pyinstaller/DroidWebDisplayWindowsOnedir.spec
- name: Verify Windows PE metadata and bundled ADB
shell: pwsh
run: |
$version = (Get-Content VERSION -Raw).Trim()
$packages = @(
".\dist\DroidWebDisplay.exe",
".\dist\DroidWebDisplayWindowsOnedir\DroidWebDisplay.exe"
)
foreach ($path in $packages) {
$info = (Get-Item $path).VersionInfo
if ($info.ProductName -ne "DroidWebDisplay") { throw "ProductName missing from $path" }
if (-not $info.FileVersion.StartsWith($version)) { throw "FileVersion $($info.FileVersion) does not match $version in $path" }
if (-not $info.ProductVersion.StartsWith($version)) { throw "ProductVersion $($info.ProductVersion) does not match $version in $path" }
if ($info.OriginalFilename -ne "DroidWebDisplay.exe") { throw "OriginalFilename missing from $path" }
}
$adb = Get-ChildItem ".\dist\DroidWebDisplayWindowsOnedir" -Recurse -Filter adb.exe | Select-Object -First 1
if (-not $adb) { throw "Bundled adb.exe missing from onedir package" }
& $adb.FullName version
if ($LASTEXITCODE -ne 0) { throw "Bundled adb.exe failed to execute" }
- name: Smoke-test Windows portable desktop, CLI and bundled ADB
shell: pwsh
run: |
$process = Start-Process -FilePath ".\dist\DroidWebDisplay.exe" -ArgumentList "--desktop-smoke" -Wait -PassThru
if ($process.ExitCode -ne 0) { exit $process.ExitCode }
- name: Smoke-test Windows executable CLI
foreach ($arg in @("--desktop-smoke", "--help", "--adb-smoke")) {
$process = Start-Process -FilePath ".\dist\DroidWebDisplay.exe" -ArgumentList $arg -Wait -PassThru
if ($process.ExitCode -ne 0) { throw "Portable smoke $arg failed: $($process.ExitCode)" }
}
- name: Smoke-test Windows onedir desktop, CLI and bundled ADB
shell: pwsh
run: |
$process = Start-Process -FilePath ".\dist\DroidWebDisplay.exe" -ArgumentList "--help" -Wait -PassThru
if ($process.ExitCode -ne 0) { exit $process.ExitCode }
- name: Smoke-test Windows executable service
run: python tools/smoke_desktop_package.py .\dist\DroidWebDisplay.exe --timeout 60
$exe = ".\dist\DroidWebDisplayWindowsOnedir\DroidWebDisplay.exe"
foreach ($arg in @("--desktop-smoke", "--help", "--adb-smoke")) {
$process = Start-Process -FilePath $exe -ArgumentList $arg -Wait -PassThru
if ($process.ExitCode -ne 0) { throw "Onedir smoke $arg failed: $($process.ExitCode)" }
}
- name: Repeat Windows service start-stop smoke
shell: pwsh
run: |
1..3 | ForEach-Object { python tools/smoke_desktop_package.py .\dist\DroidWebDisplay.exe --timeout 60 }
1..3 | ForEach-Object { python tools/smoke_desktop_package.py .\dist\DroidWebDisplayWindowsOnedir\DroidWebDisplay.exe --timeout 60 }
- name: Build Windows stable onedir ZIP
shell: pwsh
run: Compress-Archive -Path ".\dist\DroidWebDisplayWindowsOnedir\*" -DestinationPath ".\dist\DroidWebDisplay-windows-x86_64.zip" -CompressionLevel Optimal
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: windows-package-smoke
path: dist/DroidWebDisplay.exe
path: |
dist/DroidWebDisplay.exe
dist/DroidWebDisplay-windows-x86_64.zip
if-no-files-found: error

linux-appimage-smoke:
Expand All @@ -136,7 +176,7 @@ jobs:
libxcb-render-util0 \
libxcb-shape0 \
libxcb-xkb1
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6
with:
python-version: '3.11.15'
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
Expand Down
4 changes: 4 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -171,11 +171,14 @@ jobs:
gh run download "$run_id" --repo "$GITHUB_REPOSITORY" --name linux-appimage-smoke --dir artifacts/linux

windows=$(find artifacts/windows -type f -name 'DroidWebDisplay.exe' -print -quit)
windows_zip=$(find artifacts/windows -type f -name 'DroidWebDisplay-windows-x86_64.zip' -print -quit)
linux=$(find artifacts/linux -type f -name '*.AppImage' -print -quit)
test -n "$windows"
test -n "$windows_zip"
test -n "$linux"

cp "$windows" "release-assets/DroidWebDisplay-v${RELEASE_VERSION}-windows-x86_64.exe"
cp "$windows_zip" "release-assets/DroidWebDisplay-v${RELEASE_VERSION}-windows-x86_64.zip"
cp "$linux" "release-assets/DroidWebDisplay-v${RELEASE_VERSION}-linux-x86_64.AppImage"
chmod +x "release-assets/DroidWebDisplay-v${RELEASE_VERSION}-linux-x86_64.AppImage"
(cd release-assets && sha256sum DroidWebDisplay-* > SHA256SUMS.txt)
Expand All @@ -195,5 +198,6 @@ jobs:
--title "DroidWebDisplay $tag" \
--notes-file ".github/releases/${tag}.md" \
release-assets/DroidWebDisplay-v${RELEASE_VERSION}-windows-x86_64.exe \
release-assets/DroidWebDisplay-v${RELEASE_VERSION}-windows-x86_64.zip \
release-assets/DroidWebDisplay-v${RELEASE_VERSION}-linux-x86_64.AppImage \
release-assets/SHA256SUMS.txt
21 changes: 20 additions & 1 deletion apps/web-client/dist/assets/browser-support.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

79 changes: 78 additions & 1 deletion apps/web-client/dist/assets/main.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

30 changes: 28 additions & 2 deletions apps/web-client/src/browser-support.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ export interface BrowserCapabilityReport {
readonly supported: boolean;
readonly missing: readonly string[];
readonly userAgent: string;
readonly browserName: string;
readonly platform: string;
readonly hardwareConcurrency: number | null;
readonly audioSupported: boolean;
readonly missingAudio: readonly string[];
}
Expand All @@ -15,18 +18,41 @@ export interface BrowserCapabilityScope {
readonly AudioDecoder?: unknown;
readonly EncodedAudioChunk?: unknown;
readonly AudioContext?: unknown;
readonly navigator?: { readonly userAgent?: string };
readonly navigator?: {
readonly userAgent?: string;
readonly platform?: string;
readonly hardwareConcurrency?: number;
};
}

export function browserName(userAgent: string): string {
const matchers: readonly [RegExp, string][] = [
[/Edg\/([0-9.]+)/, "Edge"],
[/Chrome\/([0-9.]+)/, "Chrome"],
[/Firefox\/([0-9.]+)/, "Firefox"],
[/Version\/([0-9.]+).*Safari\//, "Safari"],
];
for (const [pattern, name] of matchers) {
const match = pattern.exec(userAgent);
if (match) return `${name} ${match[1]}`;
}
return userAgent === "unknown" ? "unknown" : "Other browser";
}

export function inspectBrowserCapabilities(scope: BrowserCapabilityScope = globalThis): BrowserCapabilityReport {
const required = ["WebSocket", "ReadableStream", "WritableStream", "VideoDecoder", "EncodedVideoChunk"] as const;
const missing = required.filter((name) => typeof scope[name] === "undefined");
const audio = ["AudioDecoder", "EncodedAudioChunk", "AudioContext"] as const;
const missingAudio = audio.filter((name) => typeof scope[name] === "undefined");
const userAgent = scope.navigator?.userAgent ?? "unknown";
const concurrency = scope.navigator?.hardwareConcurrency;
return {
supported: missing.length === 0,
missing,
userAgent: scope.navigator?.userAgent ?? "unknown",
userAgent,
browserName: browserName(userAgent),
platform: scope.navigator?.platform || "unknown",
hardwareConcurrency: typeof concurrency === "number" && Number.isFinite(concurrency) ? concurrency : null,
audioSupported: missingAudio.length === 0,
missingAudio,
};
Expand Down
Loading
Loading