name: RustDesk Windows x64 on: workflow_dispatch: env: RUSTDESK_VERSION: "1.4.9" # Build target constants, not software versions. RUST_TARGET: x86_64-pc-windows-msvc VCPKG_TRIPLET: x64-windows-static jobs: resolve-config: name: Resolve upstream build config runs-on: ci-linux-builder env: NEXUS_CARGO_URL: ${{ secrets.NEXUS_CARGO_URL }} NEXUS_MAVEN_URL: ${{ secrets.NEXUS_MAVEN_URL }} steps: - name: Checkout CI repository uses: actions/checkout@v4 - name: Prepare RAM CI environment uses: ./.gitea/actions/prepare-ram-ci - name: Clone selected RustDesk tag shell: bash run: | set -euxo pipefail rm -rf rustdesk-config build-config git clone \ --branch "${RUSTDESK_VERSION}" \ --depth 1 \ https://github.com/rustdesk/rustdesk.git \ rustdesk-config - name: Extract upstream tool versions shell: bash run: | set -euxo pipefail mkdir -p build-config python3 - <<'PY' import re from pathlib import Path repo = Path("rustdesk-config") flutter_file = repo / ".github/workflows/flutter-build.yml" bridge_file = repo / ".github/workflows/bridge.yml" topmost_file = repo / ".github/workflows/third-party-RustDeskTempTopMostWindow.yml" flutter = flutter_file.read_text(encoding="utf-8") bridge = bridge_file.read_text(encoding="utf-8") topmost = topmost_file.read_text(encoding="utf-8") if topmost_file.exists() else "" def env_value(text, name, required=True): m = re.search( rf'(?m)^\s*{re.escape(name)}:\s*["\']?([^"\'#\s]+)', text, ) if m: return m.group(1).strip() if required: raise SystemExit(f"Unable to resolve {name}") return "" windows_rust = ( env_value(flutter, "SCITER_RUST_VERSION", False) or env_value(flutter, "RUST_VERSION") ) bridge_flutter_match = re.search( r'flutter-version:\s*["\']([^"\']+)["\']', bridge, ) if not bridge_flutter_match: raise SystemExit("Unable to resolve bridge Flutter version") topmost_match = re.search( r"git\s+checkout\s+([0-9a-fA-F]{40})", topmost, ) values = { "WINDOWS_RUST_VERSION": windows_rust, "LLVM_VERSION": env_value(flutter, "LLVM_VERSION"), "WINDOWS_FLUTTER_VERSION": env_value(flutter, "FLUTTER_VERSION"), "VCPKG_COMMIT_ID": env_value(flutter, "VCPKG_COMMIT_ID"), "BRIDGE_RUST_VERSION": env_value(bridge, "RUST_VERSION"), "BRIDGE_FLUTTER_VERSION": bridge_flutter_match.group(1), "FRB_VERSION": env_value(bridge, "FLUTTER_RUST_BRIDGE_VERSION"), "CARGO_EXPAND_VERSION": env_value(bridge, "CARGO_EXPAND_VERSION", False), "TOPMOST_COMMIT": topmost_match.group(1) if topmost_match else "HEAD", "USE_CUSTOM_ENGINE": "1" if "windows-x64-release.zip" in flutter else "0", } out = Path("build-config/build-config.env") out.write_text( "".join(f"{k}={v}\n" for k, v in values.items()), encoding="utf-8", ) for k, v in values.items(): print(f"{k}={v}") PY - name: Upload build config uses: https://github.com/actions/upload-artifact@v7 with: name: rustdesk-build-config path: build-config/build-config.env if-no-files-found: error retention-days: 1 generate-bridge: name: Generate Flutter Rust Bridge needs: [resolve-config] runs-on: ci-linux-builder env: NEXUS_CARGO_URL: ${{ secrets.NEXUS_CARGO_URL }} NEXUS_MAVEN_URL: ${{ secrets.NEXUS_MAVEN_URL }} steps: - name: Checkout CI repository uses: actions/checkout@v4 - name: Prepare RAM CI environment uses: ./.gitea/actions/prepare-ram-ci - name: Download build config uses: https://github.com/actions/download-artifact@v8 with: name: rustdesk-build-config path: build-config - name: Load build config shell: bash run: | set -euxo pipefail while IFS='=' read -r key value; do [ -z "${key}" ] && continue echo "${key}=${value}" >> "${GITHUB_ENV}" done < build-config/build-config.env - name: Clone selected RustDesk tag shell: bash run: | set -euxo pipefail rm -rf rustdesk bridge-artifact git clone \ --branch "${RUSTDESK_VERSION}" \ --depth 1 \ https://github.com/rustdesk/rustdesk.git \ rustdesk git -C rustdesk submodule update --init --recursive - name: Prepare bridge toolchain shell: bash run: | set -euxo pipefail rustup toolchain install \ "${BRIDGE_RUST_VERSION}" \ --profile minimal \ --component rustfmt export RUSTUP_TOOLCHAIN="${BRIDGE_RUST_VERSION}" echo "RUSTUP_TOOLCHAIN=${BRIDGE_RUST_VERSION}" >> "${GITHUB_ENV}" FLUTTER_DIR="${HOME}/.cache/rustdesk/flutter-${BRIDGE_FLUTTER_VERSION}" if [ ! -x "${FLUTTER_DIR}/bin/flutter" ]; then mkdir -p "$(dirname "${FLUTTER_DIR}")" if [ -x "/opt/flutter-${BRIDGE_FLUTTER_VERSION}/bin/flutter" ]; then cp -a "/opt/flutter-${BRIDGE_FLUTTER_VERSION}" "${FLUTTER_DIR}" else rm -rf "${FLUTTER_DIR}" mkdir -p "$(dirname "${FLUTTER_DIR}")" git clone \ --branch "${BRIDGE_FLUTTER_VERSION}" \ --depth 1 \ https://github.com/flutter/flutter.git \ "${FLUTTER_DIR}" fi fi git config --global --add safe.directory "${FLUTTER_DIR}" || true export PATH="${FLUTTER_DIR}/bin:${CARGO_HOME}/bin:${PATH}" echo "${FLUTTER_DIR}/bin" >> "${GITHUB_PATH}" flutter config --no-analytics flutter --version rustc --version if [ -n "${CARGO_EXPAND_VERSION}" ]; then current="$( cargo expand --version 2>/dev/null | awk '{print $2}' || true )" if [ "${current}" != "${CARGO_EXPAND_VERSION}" ]; then cargo install \ cargo-expand \ --version "${CARGO_EXPAND_VERSION}" \ --locked \ --force fi fi current="$( flutter_rust_bridge_codegen --version 2>/dev/null | awk '{print $2}' || true )" if [ "${current}" != "${FRB_VERSION}" ]; then cargo install \ flutter_rust_bridge_codegen \ --version "${FRB_VERSION}" \ --features uuid \ --locked \ --force fi - name: Generate bridge shell: bash run: | set -euxo pipefail cd rustdesk rm -rf flutter/.dart_tool if ! (cd flutter && flutter pub get); then if grep -q \ 'extended_text:[[:space:]]*14\.0\.0' \ flutter/pubspec.yaml; then sed -i \ 's/extended_text:[[:space:]]*14\.0\.0/extended_text: 13.0.0/g' \ flutter/pubspec.yaml (cd flutter && flutter pub get) else exit 1 fi fi flutter_rust_bridge_codegen \ --rust-input ./src/flutter_ffi.rs \ --dart-output ./flutter/lib/generated_bridge.dart \ --c-output ./flutter/macos/Runner/bridge_generated.h test -f ./src/bridge_generated.rs test -f ./src/bridge_generated.io.rs test -f ./flutter/lib/generated_bridge.dart test -f ./flutter/lib/generated_bridge.freezed.dart - name: Prepare bridge artifact shell: bash run: | set -euxo pipefail rm -rf bridge-artifact mkdir -p bridge-artifact/src mkdir -p bridge-artifact/flutter/lib cp rustdesk/src/bridge_generated.rs \ bridge-artifact/src/ cp rustdesk/src/bridge_generated.io.rs \ bridge-artifact/src/ cp rustdesk/flutter/lib/generated_bridge.dart \ bridge-artifact/flutter/lib/ cp rustdesk/flutter/lib/generated_bridge.freezed.dart \ bridge-artifact/flutter/lib/ - name: Upload bridge uses: https://github.com/actions/upload-artifact@v7 with: name: rustdesk-bridge path: bridge-artifact/ if-no-files-found: error retention-days: 1 build-windows: name: Build Windows x64 needs: [resolve-config, generate-bridge] runs-on: rustdesk-windows env: RUSTUP_HOME: C:\BuildTools\rustup CARGO_HOME: C:\BuildTools\cargo PUB_CACHE: C:\BuildTools\pub-cache VCPKG_ROOT: C:\vcpkg steps: - name: Download build config uses: https://github.com/actions/download-artifact@v8 with: name: rustdesk-build-config path: build-config - name: Load build config shell: pwsh run: | $ErrorActionPreference = "Stop" Get-Content ".\build-config\build-config.env" | ForEach-Object { if ([string]::IsNullOrWhiteSpace($_)) { return } $parts = $_ -split "=", 2 $name = $parts[0] $value = $parts[1] [Environment]::SetEnvironmentVariable( $name, $value, "Process" ) Add-Content ` -Path $env:GITHUB_ENV ` -Value "$name=$value" } - name: Clone selected RustDesk tag shell: pwsh run: | $ErrorActionPreference = "Stop" Remove-Item ".\rustdesk" ` -Recurse -Force -ErrorAction SilentlyContinue Remove-Item ".\bridge-download" ` -Recurse -Force -ErrorAction SilentlyContinue git -c core.longpaths=true clone ` --branch "$env:RUSTDESK_VERSION" ` --depth 1 ` https://github.com/rustdesk/rustdesk.git ` rustdesk if ($LASTEXITCODE -ne 0) { throw "RustDesk clone failed" } git -C rustdesk ` -c core.longpaths=true ` submodule update --init --recursive if ($LASTEXITCODE -ne 0) { throw "RustDesk submodule checkout failed" } - name: Prepare selected Windows toolchain shell: pwsh run: | $ErrorActionPreference = "Stop" Write-Host "RustDesk: $env:RUSTDESK_VERSION" Write-Host "Rust: $env:WINDOWS_RUST_VERSION" Write-Host "LLVM: $env:LLVM_VERSION" Write-Host "Flutter: $env:WINDOWS_FLUTTER_VERSION" Write-Host "vcpkg: $env:VCPKG_COMMIT_ID" # Rust rustup toolchain install ` "$env:WINDOWS_RUST_VERSION" ` --profile minimal ` --component rustfmt ` --target "$env:RUST_TARGET" if ($LASTEXITCODE -ne 0) { throw "Rust toolchain setup failed" } $env:RUSTUP_TOOLCHAIN = $env:WINDOWS_RUST_VERSION Add-Content $env:GITHUB_ENV ` "RUSTUP_TOOLCHAIN=$env:WINDOWS_RUST_VERSION" # LLVM: reuse installed version when possible, otherwise install # the version requested by the selected RustDesk tag. $clang = Get-Command clang -ErrorAction SilentlyContinue $llvmBin = $null if ($clang) { $firstLine = (& $clang.Source --version | Select-Object -First 1) if ($firstLine -match [regex]::Escape($env:LLVM_VERSION)) { $llvmBin = Split-Path $clang.Source -Parent } } if (-not $llvmBin) { $llvmRoot = "C:\BuildTools\llvm-$env:LLVM_VERSION" $llvmBin = "$llvmRoot\bin" if (-not (Test-Path "$llvmBin\clang.exe")) { $installer = ( "C:\BuildTools\downloads\" + "LLVM-$env:LLVM_VERSION-win64.exe" ) $url = ( "https://github.com/llvm/llvm-project/releases/download/" + "llvmorg-$env:LLVM_VERSION/" + "LLVM-$env:LLVM_VERSION-win64.exe" ) Invoke-WebRequest -Uri $url -OutFile $installer $p = Start-Process ` -FilePath $installer ` -ArgumentList @("/S", "/D=$llvmRoot") ` -Wait ` -PassThru if ($p.ExitCode -ne 0) { throw "LLVM installation failed" } } } if (-not (Test-Path "$llvmBin\libclang.dll")) { throw "libclang.dll not found" } $env:LIBCLANG_PATH = $llvmBin $env:Path = "$llvmBin;$env:Path" Add-Content $env:GITHUB_ENV ` "LIBCLANG_PATH=$llvmBin" Add-Content $env:GITHUB_PATH $llvmBin # Flutter $flutterRoot = ( "C:\BuildTools\flutter-" + $env:WINDOWS_FLUTTER_VERSION ) if (-not (Test-Path "$flutterRoot\bin\flutter.bat")) { Remove-Item $flutterRoot ` -Recurse -Force -ErrorAction SilentlyContinue git clone ` --branch "$env:WINDOWS_FLUTTER_VERSION" ` --depth 1 ` https://github.com/flutter/flutter.git ` $flutterRoot if ($LASTEXITCODE -ne 0) { throw "Flutter SDK setup failed" } } git config --system --add safe.directory ` ($flutterRoot -replace "\\", "/") git -C $flutterRoot reset --hard HEAD $env:FLUTTER_ROOT = $flutterRoot $env:Path = "$flutterRoot\bin;$env:Path" Add-Content $env:GITHUB_ENV ` "FLUTTER_ROOT=$flutterRoot" Add-Content $env:GITHUB_PATH ` "$flutterRoot\bin" & "$flutterRoot\bin\flutter.bat" config --no-analytics & "$flutterRoot\bin\flutter.bat" config --enable-windows-desktop & "$flutterRoot\bin\flutter.bat" precache --windows if ($LASTEXITCODE -ne 0) { throw "Flutter Windows precache failed" } # Apply every Flutter patch shipped by the selected RustDesk tag # that cleanly applies to the selected Flutter SDK. $patches = Get-ChildItem ` ".\rustdesk\.github\patches" ` -Filter "flutter_*.diff" ` -File ` -ErrorAction SilentlyContinue foreach ($patch in $patches) { git -C $flutterRoot apply --check $patch.FullName 2>$null if ($LASTEXITCODE -eq 0) { Write-Host "Applying $($patch.Name)" git -C $flutterRoot apply $patch.FullName if ($LASTEXITCODE -ne 0) { throw "Flutter patch failed: $($patch.Name)" } } } # Replace stock engine only when the selected upstream workflow # explicitly uses RustDesk's custom Windows x64 engine. if ($env:USE_CUSTOM_ENGINE -eq "1") { $zip = "C:\BuildTools\downloads\rustdesk-engine.zip" $extract = ( "C:\BuildTools\rustdesk-engine-" + $env:WINDOWS_FLUTTER_VERSION ) Invoke-WebRequest ` -Uri "https://github.com/rustdesk/engine/releases/download/main/windows-x64-release.zip" ` -OutFile $zip Remove-Item $extract ` -Recurse -Force -ErrorAction SilentlyContinue New-Item -ItemType Directory ` -Force -Path $extract | Out-Null Expand-Archive ` -Path $zip ` -DestinationPath $extract ` -Force $dest = Join-Path ` $flutterRoot ` "bin\cache\artifacts\engine\windows-x64-release" Remove-Item "$dest\*" ` -Recurse -Force -ErrorAction SilentlyContinue Copy-Item "$extract\*" "$dest\" ` -Recurse -Force } # vcpkg if (-not (Test-Path "$env:VCPKG_ROOT\.git")) { Remove-Item $env:VCPKG_ROOT ` -Recurse -Force -ErrorAction SilentlyContinue git clone ` https://github.com/microsoft/vcpkg.git ` $env:VCPKG_ROOT } $current = git -C $env:VCPKG_ROOT rev-parse HEAD if ($current -ne $env:VCPKG_COMMIT_ID) { git -C $env:VCPKG_ROOT fetch ` origin "$env:VCPKG_COMMIT_ID" git -C $env:VCPKG_ROOT checkout ` --force "$env:VCPKG_COMMIT_ID" foreach ($dir in @("installed", "buildtrees", "packages")) { Remove-Item ` (Join-Path $env:VCPKG_ROOT $dir) ` -Recurse -Force -ErrorAction SilentlyContinue } } & "$env:VCPKG_ROOT\bootstrap-vcpkg.bat" -disableMetrics if ($LASTEXITCODE -ne 0) { throw "vcpkg bootstrap failed" } - name: Restore generated bridge uses: https://github.com/actions/download-artifact@v8 with: name: rustdesk-bridge path: bridge-download - name: Copy generated bridge into RustDesk shell: pwsh run: | $ErrorActionPreference = "Stop" $files = @{ "bridge-download\src\bridge_generated.rs" = "rustdesk\src\bridge_generated.rs" "bridge-download\src\bridge_generated.io.rs" = "rustdesk\src\bridge_generated.io.rs" "bridge-download\flutter\lib\generated_bridge.dart" = "rustdesk\flutter\lib\generated_bridge.dart" "bridge-download\flutter\lib\generated_bridge.freezed.dart" = "rustdesk\flutter\lib\generated_bridge.freezed.dart" } foreach ($source in $files.Keys) { if (-not (Test-Path $source)) { throw "Missing bridge file: $source" } Copy-Item $source $files[$source] -Force } if (-not (Test-Path "rustdesk\flutter\windows\CMakeLists.txt")) { throw "Selected RustDesk tag has no Flutter Windows project" } - name: Patch RustDesk server configuration shell: pwsh env: ID_SERVER: ${{ secrets.RUSTDESK_ID_SERVER }} PUB_KEY: ${{ secrets.RUSTDESK_KEY }} run: | $ErrorActionPreference = "Stop" if ([string]::IsNullOrWhiteSpace($env:ID_SERVER)) { throw "RUSTDESK_ID_SERVER secret is empty" } if ([string]::IsNullOrWhiteSpace($env:PUB_KEY)) { throw "RUSTDESK_KEY secret is empty" } $pythonCode = @' import json import os import re from pathlib import Path path = Path(r"rustdesk/libs/hbb_common/src/config.rs") if not path.exists(): raise SystemExit(f"config.rs not found: {path}") server = os.environ["ID_SERVER"].strip() key = os.environ["PUB_KEY"].strip() if not server: raise SystemExit("RUSTDESK_ID_SERVER is empty") if not key: raise SystemExit("RUSTDESK_KEY is empty") text = path.read_text(encoding="utf-8") text, server_count = re.subn( r"pub const RENDEZVOUS_SERVERS\s*:\s*[^=]+?=" r"\s*&\[(?:.|\n)*?\];", "pub const RENDEZVOUS_SERVERS: &[&str] = &[" + json.dumps(server) + "];", text, count=1, ) text, key_count = re.subn( r"pub const RS_PUB_KEY\s*:\s*[^=]+?=\s*.*?;", "pub const RS_PUB_KEY: &str = " + json.dumps(key) + ";", text, count=1, flags=re.S, ) if server_count != 1: raise SystemExit( f"RENDEZVOUS_SERVERS replacement count: {server_count}" ) if key_count != 1: raise SystemExit( f"RS_PUB_KEY replacement count: {key_count}" ) path.write_text(text, encoding="utf-8") print(f"ID server configured: {server}") print("Server public key configured successfully") print("Relay configuration untouched") '@ $pythonCode | python - if ($LASTEXITCODE -ne 0) { throw "RustDesk configuration patch failed" } Write-Host "" Write-Host "Checking patched RustDesk configuration..." $config = Get-Content ` "rustdesk\libs\hbb_common\src\config.rs" ` -Raw if ($config -notmatch "RENDEZVOUS_SERVERS") { throw "RENDEZVOUS_SERVERS not found after patch" } if ($config -notmatch "RS_PUB_KEY") { throw "RS_PUB_KEY not found after patch" } Write-Host "Custom ID server: OK" Write-Host "Custom public key: OK" Write-Host "Relay untouched: OK" - name: Install RustDesk vcpkg dependencies shell: pwsh run: | $ErrorActionPreference = "Stop" Set-Location rustdesk $env:VCPKG_DEFAULT_HOST_TRIPLET = $env:VCPKG_TRIPLET & "$env:VCPKG_ROOT\vcpkg.exe" ` install ` --triplet "$env:VCPKG_TRIPLET" ` --x-install-root="$env:VCPKG_ROOT\installed" if ($LASTEXITCODE -ne 0) { throw "vcpkg install failed" } - name: Build RustDesk shell: pwsh run: | $ErrorActionPreference = "Stop" $repo = (Resolve-Path "rustdesk").Path $windowsCmake = Join-Path ` $repo ` "flutter\windows\CMakeLists.txt" if (-not (Test-Path $windowsCmake)) { git -C $repo restore ` --source=HEAD -- flutter/windows } if (-not (Test-Path $windowsCmake)) { throw "flutter/windows/CMakeLists.txt is missing" } Push-Location (Join-Path $repo "libs\virtual_display\dylib") try { cargo build --locked --release if ($LASTEXITCODE -ne 0) { throw "virtual display build failed" } } finally { Pop-Location } Push-Location $repo try { cargo build ` --locked ` --features "hwcodec,vram,flutter" ` --lib ` --release if ($LASTEXITCODE -ne 0) { throw "RustDesk Rust build failed" } } finally { Pop-Location } $flutterDir = Join-Path $repo "flutter" Push-Location $flutterDir try { flutter pub get if ($LASTEXITCODE -ne 0) { throw "flutter pub get failed" } } finally { Pop-Location } # Restore the Windows project after dependency resolution. git -C $repo restore ` --source=HEAD -- flutter/windows if (-not (Test-Path $windowsCmake)) { throw "flutter/windows disappeared after pub get" } Push-Location $flutterDir try { flutter build windows --release --no-pub if ($LASTEXITCODE -ne 0) { throw "Flutter Windows build failed" } } finally { Pop-Location } $release = Join-Path ` $repo ` "flutter\build\windows\x64\runner\Release" if (-not (Test-Path "$release\rustdesk.exe")) { throw "rustdesk.exe was not produced" } $virtualDisplayDll = Join-Path ` $repo ` "target\release\deps\dylib_virtual_display.dll" if (-not (Test-Path $virtualDisplayDll)) { throw "dylib_virtual_display.dll was not produced" } Copy-Item ` $virtualDisplayDll ` "$release\dylib_virtual_display.dll" ` -Force $packageDir = Join-Path $repo "dist\rustdesk" Remove-Item $packageDir ` -Recurse -Force -ErrorAction SilentlyContinue New-Item -ItemType Directory ` -Force -Path $packageDir | Out-Null Copy-Item "$release\*" "$packageDir\" ` -Recurse -Force - name: Build WindowInjection.dll shell: pwsh run: | $ErrorActionPreference = "Stop" Set-Location rustdesk $source = ".\third-party\RustDeskTempTopMostWindow" Remove-Item ` $source ` -Recurse ` -Force ` -ErrorAction SilentlyContinue git clone ` https://github.com/rustdesk-org/RustDeskTempTopMostWindow ` $source if ($LASTEXITCODE -ne 0) { throw "RustDeskTempTopMostWindow clone failed" } if ( -not [string]::IsNullOrWhiteSpace($env:TOPMOST_COMMIT) -and $env:TOPMOST_COMMIT -ne "HEAD" ) { git ` -C $source ` checkout ` "$env:TOPMOST_COMMIT" if ($LASTEXITCODE -ne 0) { throw "RustDeskTempTopMostWindow checkout failed" } } $vswhere = ` "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe" if (-not (Test-Path $vswhere)) { throw "vswhere.exe not found" } $vsInfo = & $vswhere ` -latest ` -products * ` -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 ` -format json | ConvertFrom-Json if (-not $vsInfo) { throw "Visual Studio C++ Build Tools not found" } $vsPath = $vsInfo[0].installationPath $vsVersion = $vsInfo[0].installationVersion Write-Host "Visual Studio:" Write-Host " Path: $vsPath" Write-Host " Version: $vsVersion" $vsMajor = [int]($vsVersion.Split(".")[0]) $platformToolset = switch ($vsMajor) { 17 { "v143" } 16 { "v142" } 15 { "v141" } default { throw "Unsupported Visual Studio major version: $vsMajor" } } Write-Host "PlatformToolset: $platformToolset" $msbuild = & $vswhere ` -latest ` -products * ` -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 ` -find "MSBuild\**\Bin\MSBuild.exe" | Select-Object -First 1 if (-not $msbuild) { throw "MSBuild.exe not found" } Write-Host "MSBuild: $msbuild" & $msbuild ` "$source\WindowInjection\WindowInjection.vcxproj" ` "-p:Configuration=Release" ` "-p:Platform=x64" ` "-p:PlatformToolset=$platformToolset" ` "/p:TargetVersion=Windows10" if ($LASTEXITCODE -ne 0) { throw "WindowInjection.dll build failed" } $dll = ` "$source\WindowInjection\x64\Release\WindowInjection.dll" if (-not (Test-Path $dll)) { throw "WindowInjection.dll not found: $dll" } Copy-Item ` $dll ` ".\dist\rustdesk\WindowInjection.dll" ` -Force Write-Host "" Write-Host "WindowInjection.dll built successfully" Write-Host "PlatformToolset: $platformToolset" - name: Add usbmmidd shell: pwsh run: | $ErrorActionPreference = "Stop" Set-Location rustdesk $zip = ".\third-party\usbmmidd_v2.zip" $extract = ".\third-party\usbmmidd" Remove-Item $extract ` -Recurse -Force -ErrorAction SilentlyContinue Invoke-WebRequest ` -Uri "https://github.com/rustdesk-org/rdev/releases/download/usbmmidd_v2/usbmmidd_v2.zip" ` -OutFile $zip Expand-Archive ` -Path $zip ` -DestinationPath $extract ` -Force $usb = Join-Path $extract "usbmmidd_v2" Remove-Item "$usb\Win32" ` -Recurse -Force -ErrorAction SilentlyContinue foreach ($file in @( "deviceinstaller64.exe", "deviceinstaller.exe", "usbmmidd.bat" )) { Remove-Item ` (Join-Path $usb $file) ` -Force ` -ErrorAction SilentlyContinue } Copy-Item $usb ` ".\dist\rustdesk\usbmmidd_v2" ` -Recurse -Force - name: Build portable executable shell: pwsh run: | $ErrorActionPreference = "Stop" Set-Location rustdesk $runnerRes = Get-ChildItem ` ".\flutter\build\windows\x64" ` -Filter "Runner.res" ` -Recurse ` -ErrorAction SilentlyContinue | Select-Object -First 1 if ($runnerRes) { Copy-Item ` $runnerRes.FullName ` ".\libs\portable\Runner.res" ` -Force } $manifest = ".\res\manifest.xml" $tempManifest = ".\res\manifest.xml.tmp" Get-Content $manifest | Where-Object { $_ -notmatch "dpiAware" } | Set-Content ` $tempManifest ` -Encoding utf8 Move-Item ` $tempManifest ` $manifest ` -Force Push-Location ".\libs\portable" try { python -m pip install ` --disable-pip-version-check ` -r requirements.txt if ($LASTEXITCODE -ne 0) { throw "portable requirements installation failed" } python ` .\generate.py ` -f ..\..\dist\rustdesk\ ` -o . ` -e ..\..\dist\rustdesk\rustdesk.exe if ($LASTEXITCODE -ne 0) { throw "portable packer generation failed" } } finally { Pop-Location } $packer = ".\target\release\rustdesk-portable-packer.exe" if (-not (Test-Path $packer)) { throw "rustdesk-portable-packer.exe not found" } $outputName = ( "rustdesk-" + $env:RUSTDESK_VERSION + "-custom-windows-x86_64.exe" ) $output = Join-Path ".\dist" $outputName Copy-Item ` $packer ` $output ` -Force $hash = Get-FileHash ` $output ` -Algorithm SHA256 ( $hash.Hash.ToLower() + " " + $outputName ) | Set-Content ` "$output.sha256" ` -Encoding ASCII Write-Host "" Write-Host "============================================" Write-Host "Windows portable build completed successfully" Write-Host "============================================" Write-Host $output - name: Upload Windows build uses: https://github.com/actions/upload-artifact@v7 with: name: rustdesk-windows-x86_64-${{ env.RUSTDESK_VERSION }} path: | rustdesk/dist/rustdesk-*-custom-windows-x86_64.exe rustdesk/dist/rustdesk-*-custom-windows-x86_64.exe.sha256 rustdesk/dist/rustdesk/ if-no-files-found: error retention-days: 14