mirror of
https://github.com/rustdesk/rustdesk.git
synced 2026-06-18 22:34:54 +03:00
Compare commits
1 Commits
master
...
fuse-file-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0a0625126e |
@@ -2,8 +2,6 @@
|
||||
rustflags = ["-Ctarget-feature=+crt-static"]
|
||||
[target.i686-pc-windows-msvc]
|
||||
rustflags = ["-C", "target-feature=+crt-static", "-C", "link-args=/NODEFAULTLIB:MSVCRT"]
|
||||
[target.aarch64-pc-windows-msvc]
|
||||
rustflags = ["-Ctarget-feature=+crt-static"]
|
||||
[target.'cfg(target_os="macos")']
|
||||
rustflags = [
|
||||
"-C", "link-args=-sectcreate __CGPreLoginApp __cgpreloginapp /dev/null",
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
# Applies the Flutter 3.44-only source/pubspec changes on the fly, in CI only.
|
||||
#
|
||||
# Windows arm64 needs Flutter >= 3.44 (the first stable release shipping an arm64 Dart SDK +
|
||||
# engine), which renamed DialogTheme/TabBarTheme -> *Data and needs newer extended_text/
|
||||
# google_fonts. Every other platform is still on Flutter 3.24.5, where the old names/versions
|
||||
# are required, so these changes are kept OUT of the committed sources and applied here instead.
|
||||
#
|
||||
# Used by BOTH the Windows arm64 build (flutter-build.yml) and its dedicated bridge artifact
|
||||
# (bridge.yml) so they share an identical 3.44 source state -- the generated *.freezed.dart must
|
||||
# compile against the same Flutter/freezed version the arm64 build resolves.
|
||||
#
|
||||
# Remove this script (and commit the changes) once upstream bumps Flutter across the board.
|
||||
#
|
||||
# Run from the repository root. sed is used (not a git-apply patch) because the checked-out
|
||||
# sources are CRLF on the windows-11-arm runner; the substitutions below are anchor-free and
|
||||
# therefore CRLF-safe.
|
||||
set -euo pipefail
|
||||
|
||||
# ThemeData API renames (Flutter 3.27+):
|
||||
sed -i 's/dialogTheme: DialogTheme(/dialogTheme: DialogThemeData(/g' flutter/lib/common.dart
|
||||
sed -i 's/tabBarTheme: const TabBarTheme(/tabBarTheme: const TabBarThemeData(/g' flutter/lib/common.dart
|
||||
sed -i '/static ThemeData lightTheme = ThemeData(/,/static ThemeData darkTheme = ThemeData(/s/dialogTheme: DialogThemeData(/dialogTheme: DialogThemeData(\
|
||||
backgroundColor: Colors.white,/' flutter/lib/common.dart
|
||||
sed -i '/static ThemeData darkTheme = ThemeData(/,/scrollbarTheme: scrollbarThemeDark,/s/dialogTheme: DialogThemeData(/dialogTheme: DialogThemeData(\
|
||||
backgroundColor: Color(0xFF18191E),/' flutter/lib/common.dart
|
||||
# Dependency bumps required by the newer Dart/Flutter:
|
||||
sed -i 's/extended_text: 14.0.0/extended_text: 15.0.2/' flutter/pubspec.yaml
|
||||
sed -i 's/google_fonts: \^6.2.1/google_fonts: ^8.1.0/' flutter/pubspec.yaml
|
||||
|
||||
# Fail loudly if any expected string drifted, so we never silently build unpatched:
|
||||
grep -qF 'dialogTheme: DialogThemeData(' flutter/lib/common.dart
|
||||
grep -qF 'tabBarTheme: const TabBarThemeData(' flutter/lib/common.dart
|
||||
grep -qF 'backgroundColor: Colors.white,' flutter/lib/common.dart
|
||||
grep -qF 'backgroundColor: Color(0xFF18191E),' flutter/lib/common.dart
|
||||
grep -qF 'extended_text: 15.0.2' flutter/pubspec.yaml
|
||||
grep -qF 'google_fonts: ^8.1.0' flutter/pubspec.yaml
|
||||
|
||||
git --no-pager diff -- flutter/lib/common.dart flutter/pubspec.yaml
|
||||
28
.github/workflows/bridge.yml
vendored
28
.github/workflows/bridge.yml
vendored
@@ -7,6 +7,7 @@ on:
|
||||
|
||||
env:
|
||||
CARGO_EXPAND_VERSION: "1.0.95"
|
||||
FLUTTER_VERSION: "3.22.3"
|
||||
FLUTTER_RUST_BRIDGE_VERSION: "1.80.1"
|
||||
RUST_VERSION: "1.75" # https://github.com/rustdesk/rustdesk/discussions/7503
|
||||
|
||||
@@ -17,21 +18,10 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
job:
|
||||
# Default bridge for every platform still on Flutter 3.24.5 (generated with 3.22.3).
|
||||
- {
|
||||
target: x86_64-unknown-linux-gnu,
|
||||
os: ubuntu-22.04,
|
||||
extra-build-args: "",
|
||||
flutter-version: "3.22.3",
|
||||
artifact-name: "bridge-artifact",
|
||||
}
|
||||
# Dedicated bridge for the Windows arm64 build (Flutter 3.44); runs in parallel.
|
||||
- {
|
||||
target: x86_64-unknown-linux-gnu,
|
||||
os: ubuntu-22.04,
|
||||
extra-build-args: "",
|
||||
flutter-version: "3.44.0",
|
||||
artifact-name: "bridge-artifact-flutter-3.44",
|
||||
}
|
||||
steps:
|
||||
- name: Checkout source code
|
||||
@@ -74,13 +64,13 @@ jobs:
|
||||
uses: actions/cache@6f8efc29b200d32929f49075959781ed54ec270c # v3
|
||||
with:
|
||||
path: /tmp/flutter_rust_bridge
|
||||
key: bridge-${{ matrix.job.flutter-version }}
|
||||
key: vcpkg-${{ matrix.job.arch }}
|
||||
|
||||
- name: Install flutter
|
||||
uses: subosito/flutter-action@1a449444c387b1966244ae4d4f8c696479add0b2 # v2
|
||||
with:
|
||||
channel: "stable"
|
||||
flutter-version: ${{ matrix.job.flutter-version }}
|
||||
flutter-version: ${{ env.FLUTTER_VERSION }}
|
||||
cache: true
|
||||
|
||||
- name: Install flutter rust bridge deps
|
||||
@@ -88,15 +78,7 @@ jobs:
|
||||
run: |
|
||||
cargo install cargo-expand --version ${{ env.CARGO_EXPAND_VERSION }} --locked
|
||||
cargo install flutter_rust_bridge_codegen --version ${{ env.FLUTTER_RUST_BRIDGE_VERSION }} --features "uuid" --locked
|
||||
if [[ "${{ matrix.job.flutter-version }}" == "3.22.3" ]]; then
|
||||
# Default Flutter 3.22.3: extended_text 14 needs a newer Dart, so downgrade for resolution.
|
||||
sed -i -e 's/extended_text: 14.0.0/extended_text: 13.0.0/g' flutter/pubspec.yaml
|
||||
else
|
||||
# Flutter 3.44 bridge for Windows arm64: match that build's source/pubspec state so the
|
||||
# generated *.freezed.dart compiles against the same Flutter/freezed it resolves.
|
||||
bash .github/patches/apply_flutter_3.44_source_patches.sh
|
||||
fi
|
||||
pushd flutter && flutter pub get && popd
|
||||
pushd flutter && sed -i -e 's/extended_text: 14.0.0/extended_text: 13.0.0/g' pubspec.yaml && flutter pub get && popd
|
||||
|
||||
- name: Run flutter rust bridge
|
||||
run: |
|
||||
@@ -106,7 +88,7 @@ jobs:
|
||||
- name: Upload Artifact
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: ${{ matrix.job.artifact-name }}
|
||||
name: bridge-artifact
|
||||
path: |
|
||||
./src/bridge_generated.rs
|
||||
./src/bridge_generated.io.rs
|
||||
|
||||
1
.github/workflows/ci.yml
vendored
1
.github/workflows/ci.yml
vendored
@@ -81,7 +81,6 @@ jobs:
|
||||
# - { target: x86_64-apple-darwin , os: macos-10.15 }
|
||||
# - { target: x86_64-pc-windows-gnu , os: windows-2022 }
|
||||
# - { target: x86_64-pc-windows-msvc , os: windows-2022 }
|
||||
# - { target: aarch64-pc-windows-msvc , os: windows-11-arm }
|
||||
- { target: x86_64-unknown-linux-gnu , os: ubuntu-24.04 }
|
||||
# - { target: x86_64-unknown-linux-musl , os: ubuntu-20.04, use-cross: true }
|
||||
steps:
|
||||
|
||||
122
.github/workflows/flutter-build.yml
vendored
122
.github/workflows/flutter-build.yml
vendored
@@ -27,11 +27,6 @@ env:
|
||||
LLVM_VERSION: "15.0.6"
|
||||
FLUTTER_VERSION: "3.24.5"
|
||||
ANDROID_FLUTTER_VERSION: "3.24.5"
|
||||
# Windows arm64 only: the first stable Flutter to ship a native arm64 Windows Dart SDK +
|
||||
# engine is 3.44. Every other platform stays on FLUTTER_VERSION (3.24.5) until Windows 7
|
||||
# support is restored after the upstream-wide Flutter bump. The arm64 job patches the few
|
||||
# 3.44-only source/pubspec changes on the fly (see "Patch RustDesk sources for Flutter 3.44").
|
||||
FLUTTER_WINDOWS_ARM_VERSION: "3.44.0"
|
||||
# for arm64 linux because official Dart SDK does not work
|
||||
FLUTTER_ELINUX_VERSION: "3.16.9"
|
||||
TAG_NAME: "${{ inputs.upload-tag }}"
|
||||
@@ -58,24 +53,14 @@ jobs:
|
||||
|
||||
build-RustDeskTempTopMostWindow:
|
||||
uses: ./.github/workflows/third-party-RustDeskTempTopMostWindow.yml
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
job:
|
||||
- {
|
||||
target: windows-2022,
|
||||
platform: x64,
|
||||
}
|
||||
- {
|
||||
target: windows-11-arm,
|
||||
platform: ARM64,
|
||||
}
|
||||
with:
|
||||
upload-artifact: ${{ inputs.upload-artifact }}
|
||||
target: ${{ matrix.job.target }}
|
||||
target: windows-2022
|
||||
configuration: Release
|
||||
platform: ${{ matrix.job.platform }}
|
||||
platform: x64
|
||||
target_version: Windows10
|
||||
strategy:
|
||||
fail-fast: false
|
||||
|
||||
build-for-windows-flutter:
|
||||
name: ${{ matrix.job.target }}
|
||||
@@ -91,20 +76,9 @@ jobs:
|
||||
target: x86_64-pc-windows-msvc,
|
||||
os: windows-2022,
|
||||
arch: x86_64,
|
||||
flutter-arch: x64,
|
||||
vcpkg-triplet: x64-windows-static,
|
||||
build-args: "--vram",
|
||||
}
|
||||
- {
|
||||
target: aarch64-pc-windows-msvc,
|
||||
os: windows-11-arm,
|
||||
arch: aarch64,
|
||||
flutter-arch: arm64,
|
||||
vcpkg-triplet: arm64-windows-static,
|
||||
# vram is x86/x64-only (NVENC needs CUDA, Intel MediaSDK needs __rdtsc);
|
||||
# no NV/Intel/AMD hardware exists on Windows-on-ARM, so vram stays disabled here.
|
||||
build-args: "",
|
||||
}
|
||||
# - { target: aarch64-pc-windows-msvc, os: windows-2022, arch: aarch64 }
|
||||
steps:
|
||||
- name: Export GitHub Actions cache environment variables
|
||||
uses: actions/github-script@d7906e4ad0b1822421a7e6a35d5ca353c962f410 # v6
|
||||
@@ -121,91 +95,36 @@ jobs:
|
||||
- name: Restore bridge files
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
# arm64 is on Flutter 3.44, so it needs the bridge generated with the same Flutter
|
||||
# (its *.freezed.dart must match the freezed the arm64 build resolves). x64 and every
|
||||
# other platform keep the default 3.22.3-generated bridge.
|
||||
name: ${{ matrix.job.arch == 'aarch64' && 'bridge-artifact-flutter-3.44' || 'bridge-artifact' }}
|
||||
name: bridge-artifact
|
||||
path: ./
|
||||
|
||||
- name: Install LLVM and Clang
|
||||
uses: KyleMayes/install-llvm-action@ebc0426251bc40c7cd31162802432c68818ab8f0 # v2.0.9
|
||||
uses: KyleMayes/install-llvm-action@1a3da29f56261a1e1f937ec88f0856a9b8321d7e # v1
|
||||
with:
|
||||
version: ${{ env.LLVM_VERSION }}
|
||||
|
||||
- name: Install flutter
|
||||
id: flutter
|
||||
# arm64 builds with FLUTTER_WINDOWS_ARM_VERSION (>=3.44); x64 stays on FLUTTER_VERSION.
|
||||
# subosito only ships an x64 Windows SDK (Flutter's release manifest lists x64 only),
|
||||
# so it installs x64 on both arches. The arm64 runner re-bootstraps Dart to arm64 in
|
||||
# the next step.
|
||||
uses: subosito/flutter-action@2783a3f08e1baf891508463f8c6653c258246225 # v2.12.0; https://github.com/subosito/flutter-action/issues/277
|
||||
with:
|
||||
channel: "stable"
|
||||
flutter-version: ${{ matrix.job.arch == 'aarch64' && env.FLUTTER_WINDOWS_ARM_VERSION || env.FLUTTER_VERSION }}
|
||||
architecture: x64
|
||||
|
||||
- name: Force arm64 Dart SDK + engine
|
||||
# The x64 SDK subosito installs bundles an x64 Dart with a matching engine-dart-sdk.stamp,
|
||||
# so update_dart_sdk.ps1 short-circuits (stamp matches -> return) and keeps x64 Dart;
|
||||
# `flutter build windows` then targets the Dart VM's arch = x64, even on this arm64 host.
|
||||
# On this native-arm64 runner (PROCESSOR_ARCHITECTURE=ARM64), deleting the stamp and
|
||||
# re-running update_dart_sdk.ps1 pulls the arm64 Dart (available since Flutter 3.44.0).
|
||||
# https://github.com/flutter/flutter/issues/186730#issuecomment-4573214964
|
||||
if: ${{ matrix.job.arch == 'aarch64' }}
|
||||
run: |
|
||||
$flutterRoot = "${{ steps.flutter.outputs['CACHE-PATH'] }}"
|
||||
Write-Host "PROCESSOR_ARCHITECTURE=$env:PROCESSOR_ARCHITECTURE"
|
||||
Write-Host "Flutter root: $flutterRoot"
|
||||
Remove-Item -Force "$flutterRoot\bin\cache\engine-dart-sdk.stamp" -ErrorAction SilentlyContinue
|
||||
& "$flutterRoot\bin\internal\update_dart_sdk.ps1"
|
||||
# Confirm the Dart we ended up with is arm64 ("on windows_arm64"); fail loudly if not.
|
||||
$dartVer = & "$flutterRoot\bin\dart.bat" --version 2>&1 | Out-String
|
||||
Write-Host $dartVer
|
||||
if ($dartVer -notmatch "windows_arm64") {
|
||||
Write-Error "Expected an arm64 Dart SDK but got: $dartVer"
|
||||
exit 1
|
||||
}
|
||||
& "$flutterRoot\bin\flutter.bat" precache --windows
|
||||
# Fail fast if precache pulled the wrong-arch Windows engine: an arm64 Dart should
|
||||
# fetch windows-arm64 engine artifacts. Bailing here saves the ~25min Rust build.
|
||||
$engineDir = "$flutterRoot\bin\cache\artifacts\engine"
|
||||
Write-Host "Engine artifacts present:"
|
||||
Get-ChildItem $engineDir -Directory | Select-Object -ExpandProperty Name | Write-Host
|
||||
if (-not (Test-Path "$engineDir\windows-arm64-release")) {
|
||||
Write-Error "Expected windows-arm64-release engine artifacts but they are missing (wrong-arch SDK)."
|
||||
exit 1
|
||||
}
|
||||
flutter-version: ${{ env.FLUTTER_VERSION }}
|
||||
|
||||
# https://github.com/flutter/flutter/issues/155685
|
||||
# x64 only: arm64 uses the stock native arm64 Windows engine, and the rustdesk/engine
|
||||
# windows-x64-release.zip is built for the 3.24-era x64 engine (matches FLUTTER_VERSION).
|
||||
- name: Replace engine with rustdesk custom flutter engine
|
||||
if: ${{ matrix.job.arch == 'x86_64' }}
|
||||
run: |
|
||||
flutter doctor -v
|
||||
flutter precache --windows
|
||||
Invoke-WebRequest -Uri https://github.com/rustdesk/engine/releases/download/main/windows-x64-release.zip -OutFile windows-x64-release.zip
|
||||
Expand-Archive -Path windows-x64-release.zip -DestinationPath windows-x64-release
|
||||
mv -Force windows-x64-release/* C:/hostedtoolcache/windows/flutter/stable-${{ env.FLUTTER_VERSION }}-x64/bin/cache/artifacts/engine/windows-x64-release/
|
||||
mv -Force windows-x64-release/* C:/hostedtoolcache/windows/flutter/stable-${{ env.FLUTTER_VERSION }}-x64/bin/cache/artifacts/engine/windows-x64-release/
|
||||
|
||||
- name: Patch flutter
|
||||
# x64 stays on Flutter 3.24.5, which needs the dropdown filter patch.
|
||||
# arm64 is on Flutter 3.44 (patched separately below) and does not use this patch.
|
||||
if: ${{ matrix.job.arch == 'x86_64' }}
|
||||
shell: bash
|
||||
run: |
|
||||
cp .github/patches/flutter_3.24.4_dropdown_menu_enableFilter.diff $(dirname $(dirname $(which flutter)))
|
||||
cd $(dirname $(dirname $(which flutter)))
|
||||
[[ "3.24.5" == ${{env.FLUTTER_VERSION}} ]] && git apply flutter_3.24.4_dropdown_menu_enableFilter.diff
|
||||
|
||||
- name: Patch RustDesk sources for Flutter 3.44 (arm64)
|
||||
# arm64 is the only target on Flutter 3.44; apply its source/pubspec deltas on the fly
|
||||
# (shared with the 3.44 bridge job) so the committed sources stay on Flutter 3.24.5.
|
||||
# `flutter build` then runs `pub get`, regenerating pubspec.lock for the bumped deps.
|
||||
if: ${{ matrix.job.arch == 'aarch64' }}
|
||||
shell: bash
|
||||
run: bash .github/patches/apply_flutter_3.44_source_patches.sh
|
||||
|
||||
- name: Install Rust toolchain
|
||||
uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 # v1
|
||||
with:
|
||||
@@ -244,19 +163,11 @@ jobs:
|
||||
head -n 100 "${VCPKG_ROOT}/buildtrees/ffmpeg/build-${{ matrix.job.vcpkg-triplet }}-rel-out.log" || true
|
||||
shell: bash
|
||||
|
||||
- name: Set SODIUM_LIB_DIR (arm64)
|
||||
# libsodium-sys ships no arm64 Windows prebuilt lib; point it at the vcpkg-built one
|
||||
# (only for arm64 — leaving it unset lets x64 use the crate's bundled lib).
|
||||
if: ${{ matrix.job.arch == 'aarch64' }}
|
||||
shell: bash
|
||||
run: echo "SODIUM_LIB_DIR=$VCPKG_ROOT/installed/${{ matrix.job.vcpkg-triplet }}/lib" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Build rustdesk
|
||||
run: |
|
||||
# Windows: build RustDesk
|
||||
# --hwcodec is shared by all Windows targets; per-target extras (e.g. --vram) come from the matrix
|
||||
python3 .\build.py --portable --flutter --skip-portable-pack --hwcodec ${{ matrix.job.build-args }}
|
||||
mv ./flutter/build/windows/${{ matrix.job.flutter-arch }}/runner/Release ./rustdesk
|
||||
python3 .\build.py --portable --hwcodec --flutter --vram --skip-portable-pack
|
||||
mv ./flutter/build/windows/x64/runner/Release ./rustdesk
|
||||
|
||||
# Download usbmmidd_v2.zip and extract it to ./rustdesk
|
||||
Invoke-WebRequest -Uri https://github.com/rustdesk-org/rdev/releases/download/usbmmidd_v2/usbmmidd_v2.zip -OutFile usbmmidd_v2.zip
|
||||
@@ -312,7 +223,7 @@ jobs:
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
if: ${{ inputs.upload-artifact }}
|
||||
with:
|
||||
name: ${{ matrix.job.arch == 'aarch64' && 'topmostwindow-artifacts-ARM64' || 'topmostwindow-artifacts-x64' }}
|
||||
name: topmostwindow-artifacts
|
||||
path: "./rustdesk"
|
||||
|
||||
- name: Upload unsigned
|
||||
@@ -345,18 +256,13 @@ jobs:
|
||||
uses: microsoft/setup-msbuild@6fb02220983dee41ce7ae257b6f4d8f9bf5ed4ce # v2
|
||||
|
||||
- name: Build msi
|
||||
# Builds the MSI for the matrix arch. res/msi (WiX v4 + native CustomActions) carries
|
||||
# both x64 and ARM64 platform configs; WcaUtil/DUtil ship arm64 libs. msbuild platform
|
||||
# is x64 / ARM64; the produced Package.msi is globbed since its bin/<platform>/ dir varies.
|
||||
if: env.UPLOAD_ARTIFACT == 'true'
|
||||
run: |
|
||||
pushd ./res/msi
|
||||
python preprocess.py --arp -d ../../rustdesk
|
||||
nuget restore msi.sln
|
||||
$msiPlatform = if ('${{ matrix.job.arch }}' -eq 'aarch64') { 'ARM64' } else { 'x64' }
|
||||
msbuild msi.sln -p:Configuration=Release -p:Platform=$msiPlatform /p:TargetVersion=Windows10
|
||||
$msi = Get-ChildItem ./Package/bin/*/Release/en-us/Package.msi | Select-Object -First 1
|
||||
mv $msi.FullName ../../SignOutput/rustdesk-${{ env.VERSION }}-${{ matrix.job.arch }}.msi
|
||||
msbuild msi.sln -p:Configuration=Release -p:Platform=x64 /p:TargetVersion=Windows10
|
||||
mv ./Package/bin/x64/Release/en-us/Package.msi ../../SignOutput/rustdesk-${{ env.VERSION }}-${{ matrix.job.arch }}.msi
|
||||
sha256sum ../../SignOutput/rustdesk-*.msi
|
||||
|
||||
- name: Sign rustdesk self-extracted file
|
||||
|
||||
@@ -45,15 +45,16 @@ jobs:
|
||||
run: |
|
||||
git clone https://github.com/rustdesk-org/RustDeskTempTopMostWindow RustDeskTempTopMostWindow
|
||||
|
||||
# Build. commit 53b548a5398624f7149a382000397993542ad796 is tag v0.3
|
||||
- name: Build the project
|
||||
run: |
|
||||
cd RustDeskTempTopMostWindow && git checkout ecd8d6a139eee76845ea66423fb739af450fda90
|
||||
cd RustDeskTempTopMostWindow && git checkout 53b548a5398624f7149a382000397993542ad796
|
||||
msbuild ${{ env.project_path }} -p:Configuration=${{ inputs.configuration }} -p:Platform=${{ inputs.platform }} /p:TargetVersion=${{ inputs.target_version }}
|
||||
|
||||
- name: Archive build artifacts
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
if: ${{ inputs.upload-artifact }}
|
||||
with:
|
||||
name: topmostwindow-artifacts-${{ inputs.platform }}
|
||||
name: topmostwindow-artifacts
|
||||
path: |
|
||||
./${{ env.build_output_dir }}/WindowInjection.dll
|
||||
|
||||
14
Cargo.lock
generated
14
Cargo.lock
generated
@@ -2329,7 +2329,7 @@ version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412"
|
||||
dependencies = [
|
||||
"libloading 0.7.4",
|
||||
"libloading 0.8.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2694,7 +2694,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.60.2",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4494,7 +4494,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e310b3a6b5907f99202fcdb4960ff45b93735d7c7d96b760fcff8db2dc0e103d"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"windows-targets 0.48.5",
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4695,7 +4695,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "magnum-opus"
|
||||
version = "0.4.0"
|
||||
source = "git+https://github.com/rustdesk-org/magnum-opus#588c6e1f9ed50c3a01fa64f3bd3e7cdb0378a114"
|
||||
source = "git+https://github.com/rustdesk-org/magnum-opus#5cd2bf989c148662fa3a2d9d539a71d71fd1d256"
|
||||
dependencies = [
|
||||
"bindgen 0.59.2",
|
||||
"pkg-config",
|
||||
@@ -6673,7 +6673,7 @@ dependencies = [
|
||||
"once_cell",
|
||||
"socket2 0.5.10",
|
||||
"tracing",
|
||||
"windows-sys 0.60.2",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -7457,7 +7457,7 @@ dependencies = [
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys 0.11.0",
|
||||
"windows-sys 0.60.2",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -7514,7 +7514,7 @@ dependencies = [
|
||||
"security-framework 3.5.1",
|
||||
"security-framework-sys",
|
||||
"webpki-root-certs",
|
||||
"windows-sys 0.60.2",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
11
build.py
11
build.py
@@ -17,8 +17,7 @@ osx = platform.platform().startswith(
|
||||
hbb_name = 'rustdesk' + ('.exe' if windows else '')
|
||||
exe_path = 'target/release/' + hbb_name
|
||||
if windows:
|
||||
win_arch = 'arm64' if platform.machine().lower() in ('arm64', 'aarch64') else 'x64'
|
||||
flutter_build_dir = f'build/windows/{win_arch}/runner/Release/'
|
||||
flutter_build_dir = 'build/windows/x64/runner/Release/'
|
||||
elif osx:
|
||||
flutter_build_dir = 'build/macos/Build/Products/Release/'
|
||||
else:
|
||||
@@ -411,12 +410,7 @@ def build_flutter_dmg(version, features):
|
||||
system2(
|
||||
"cp target/release/liblibrustdesk.dylib target/release/librustdesk.dylib")
|
||||
os.chdir('flutter')
|
||||
# cargo builds a single-arch dylib for the host; restrict Xcode to the same arch
|
||||
# so the universal-by-default ARCHS_STANDARD doesn't try to link a missing slice.
|
||||
# FLUTTER_XCODE_* env vars are forwarded to xcodebuild as build settings.
|
||||
mac_arch = 'arm64' if platform.machine().lower() in ('arm64', 'aarch64') else 'x86_64'
|
||||
system2(
|
||||
f'FLUTTER_XCODE_ARCHS={mac_arch} FLUTTER_XCODE_ONLY_ACTIVE_ARCH=YES flutter build macos --release')
|
||||
system2('flutter build macos --release')
|
||||
system2('cp -rf ../target/release/service ./build/macos/Build/Products/Release/RustDesk.app/Contents/MacOS/')
|
||||
'''
|
||||
system2(
|
||||
@@ -512,7 +506,6 @@ def main():
|
||||
'target\\release\\rustdesk.exe')
|
||||
else:
|
||||
print('Not signed')
|
||||
os.makedirs(res_dir, exist_ok=True)
|
||||
system2(
|
||||
f'cp -rf target/release/RustDesk.exe {res_dir}')
|
||||
os.chdir('libs/portable')
|
||||
|
||||
@@ -598,22 +598,6 @@ class MyTheme {
|
||||
}
|
||||
}
|
||||
|
||||
/// Applies [fallbacks] as fontFamilyFallback to every text style in both
|
||||
/// themes. Called once at startup on ARM64 Linux after a CJK font has been
|
||||
/// loaded via FontLoader (see flutter/flutter#139293).
|
||||
static void applyFontFallback(List<String> fallbacks) {
|
||||
lightTheme = lightTheme.copyWith(
|
||||
textTheme: lightTheme.textTheme.apply(fontFamilyFallback: fallbacks),
|
||||
primaryTextTheme:
|
||||
lightTheme.primaryTextTheme.apply(fontFamilyFallback: fallbacks),
|
||||
);
|
||||
darkTheme = darkTheme.copyWith(
|
||||
textTheme: darkTheme.textTheme.apply(fontFamilyFallback: fallbacks),
|
||||
primaryTextTheme:
|
||||
darkTheme.primaryTextTheme.apply(fontFamilyFallback: fallbacks),
|
||||
);
|
||||
}
|
||||
|
||||
static ThemeMode currentThemeMode() {
|
||||
final preference = getThemeModePreference();
|
||||
if (preference == ThemeMode.system) {
|
||||
|
||||
@@ -72,24 +72,10 @@ Widget waylandKeyboardScopeChip(BuildContext context, String text) {
|
||||
);
|
||||
}
|
||||
|
||||
bool _isWindowsMode1PrivacyImpl(String privacyModeImpl) {
|
||||
return privacyModeImpl == kPrivacyModeImplMag ||
|
||||
privacyModeImpl == kPrivacyModeImplExcludeFromCapture;
|
||||
}
|
||||
|
||||
// macOS privacy mode blacks out all online displays. Windows Mode 1 also
|
||||
// covers every local monitor with privacy overlay windows, so remote display
|
||||
// switching does not weaken local privacy protection.
|
||||
//
|
||||
// Keep this separate from the capture backend capability. The legacy Windows
|
||||
// magnifier capturer is not reliable for multi-monitor capture; WebRTC's
|
||||
// screen_capturer_win_magnifier also disables it when SM_CMONITORS != 1:
|
||||
// https://webrtc.googlesource.com/src/+/1845922d5a1bf9c27deeffb4a8c8daea124434c1/modules/desktop_capture/win/screen_capturer_win_magnifier.cc
|
||||
bool allowDisplaySwitchInPrivacyMode(PeerInfo pi, String privacyModeImpl) {
|
||||
return pi.platform == kPeerPlatformMacOS ||
|
||||
(pi.platform == kPeerPlatformWindows &&
|
||||
_isWindowsMode1PrivacyImpl(privacyModeImpl) &&
|
||||
versionCmp(pi.version, '1.4.8') >= 0);
|
||||
// macOS privacy mode blacks out all online displays, so switching the remote
|
||||
// display does not weaken the local privacy protection.
|
||||
bool allowDisplaySwitchInPrivacyMode(PeerInfo pi) {
|
||||
return pi.platform == kPeerPlatformMacOS;
|
||||
}
|
||||
|
||||
class TTextMenu {
|
||||
@@ -978,8 +964,7 @@ Future<List<TToggleMenu>> toolbarDisplayToggle(
|
||||
|
||||
final privacyModeState = PrivacyModeState.find(id);
|
||||
if (pi.isSupportMultiDisplay &&
|
||||
(privacyModeState.isEmpty ||
|
||||
allowDisplaySwitchInPrivacyMode(pi, privacyModeState.value)) &&
|
||||
(privacyModeState.isEmpty || allowDisplaySwitchInPrivacyMode(pi)) &&
|
||||
pi.displaysCount.value > 1 &&
|
||||
bind.mainGetUserDefaultOption(key: kKeyShowMonitorsToolbar) == 'Y') {
|
||||
final value =
|
||||
@@ -1063,20 +1048,7 @@ List<TToggleMenu> toolbarPrivacyMode(
|
||||
return []; // No permission and not active, hide options.
|
||||
}
|
||||
|
||||
bool checkDisplayAllowedForPrivacyMode(String targetImplKey, bool turnOn) {
|
||||
if (!turnOn ||
|
||||
allowDisplaySwitchInPrivacyMode(pi, targetImplKey) ||
|
||||
(ffiModel.pi.currentDisplay == 0 &&
|
||||
!bind.sessionIsMultiUiSession(sessionId: sessionId))) {
|
||||
return true;
|
||||
}
|
||||
msgBox(sessionId, 'custom-nook-nocancel-hasclose', 'info',
|
||||
'Please switch to Display 1 first', '', ffi.dialogManager);
|
||||
return false;
|
||||
}
|
||||
|
||||
getDefaultMenu(Future<void> Function(SessionID sid, String opt) toggleFunc,
|
||||
String targetImplKey) {
|
||||
getDefaultMenu(Future<void> Function(SessionID sid, String opt) toggleFunc) {
|
||||
final enabled = !ffiModel.viewOnly &&
|
||||
(hasPrivacyModePermission || privacyModeState.isNotEmpty);
|
||||
return TToggleMenu(
|
||||
@@ -1084,7 +1056,16 @@ List<TToggleMenu> toolbarPrivacyMode(
|
||||
onChanged: enabled
|
||||
? (value) {
|
||||
if (value == null) return;
|
||||
if (!checkDisplayAllowedForPrivacyMode(targetImplKey, value)) {
|
||||
if (!allowDisplaySwitchInPrivacyMode(pi) &&
|
||||
ffiModel.pi.currentDisplay != 0 &&
|
||||
ffiModel.pi.currentDisplay != kAllDisplayValue) {
|
||||
msgBox(
|
||||
sessionId,
|
||||
'custom-nook-nocancel-hasclose',
|
||||
'info',
|
||||
'Please switch to Display 1 first',
|
||||
'',
|
||||
ffi.dialogManager);
|
||||
return;
|
||||
}
|
||||
final option = 'privacy-mode';
|
||||
@@ -1102,7 +1083,7 @@ List<TToggleMenu> toolbarPrivacyMode(
|
||||
getDefaultMenu((sid, opt) async {
|
||||
bind.sessionToggleOption(sessionId: sid, value: opt);
|
||||
togglePrivacyModeTime = DateTime.now();
|
||||
}, kPrivacyModeImplMag)
|
||||
})
|
||||
];
|
||||
}
|
||||
if (privacyModeImpls.isEmpty) {
|
||||
@@ -1116,7 +1097,7 @@ List<TToggleMenu> toolbarPrivacyMode(
|
||||
bind.sessionTogglePrivacyMode(
|
||||
sessionId: sid, implKey: implKey, on: privacyModeState.isEmpty);
|
||||
togglePrivacyModeTime = DateTime.now();
|
||||
}, implKey)
|
||||
})
|
||||
];
|
||||
} else {
|
||||
final visibleImpls = hasPrivacyModePermission
|
||||
@@ -1137,9 +1118,6 @@ List<TToggleMenu> toolbarPrivacyMode(
|
||||
? (value) {
|
||||
if (value == null) return;
|
||||
if (value && !hasPrivacyModePermission) return;
|
||||
if (!checkDisplayAllowedForPrivacyMode(implKey, value)) {
|
||||
return;
|
||||
}
|
||||
togglePrivacyModeTime = DateTime.now();
|
||||
bind.sessionTogglePrivacyMode(
|
||||
sessionId: sessionId, implKey: implKey, on: value);
|
||||
|
||||
@@ -29,10 +29,6 @@ const String kPlatformAdditionsHasFileClipboard = "has_file_clipboard";
|
||||
const String kPlatformAdditionsSupportedPrivacyModeImpl =
|
||||
"supported_privacy_mode_impl";
|
||||
|
||||
const String kPrivacyModeImplMag = 'privacy_mode_impl_mag';
|
||||
const String kPrivacyModeImplExcludeFromCapture =
|
||||
'privacy_mode_impl_exclude_from_capture';
|
||||
|
||||
const String kPeerPlatformWindows = "Windows";
|
||||
const String kPeerPlatformLinux = "Linux";
|
||||
const String kPeerPlatformMacOS = "Mac OS";
|
||||
|
||||
@@ -810,9 +810,8 @@ class _RemoteToolbarState extends State<RemoteToolbar> {
|
||||
}
|
||||
|
||||
toolbarItems.add(Obx(() {
|
||||
final privacyModeState = PrivacyModeState.find(widget.id);
|
||||
if ((privacyModeState.isEmpty ||
|
||||
allowDisplaySwitchInPrivacyMode(pi, privacyModeState.value)) &&
|
||||
if ((PrivacyModeState.find(widget.id).isEmpty ||
|
||||
allowDisplaySwitchInPrivacyMode(pi)) &&
|
||||
pi.displaysCount.value > 1) {
|
||||
return _MonitorMenu(
|
||||
id: widget.id,
|
||||
|
||||
@@ -29,8 +29,6 @@ import 'mobile/pages/home_page.dart';
|
||||
import 'mobile/pages/server_page.dart';
|
||||
import 'mobile/widgets/deploy_dialog.dart';
|
||||
import 'models/platform_model.dart';
|
||||
import 'native/font_manager.dart'
|
||||
if (dart.library.html) 'web/font_manager.dart';
|
||||
|
||||
import 'package:flutter_hbb/plugin/handlers.dart'
|
||||
if (dart.library.html) 'package:flutter_hbb/web/plugin/handlers.dart';
|
||||
@@ -39,15 +37,10 @@ import 'package:flutter_hbb/plugin/handlers.dart'
|
||||
int? kWindowId;
|
||||
WindowType? kWindowType;
|
||||
late List<String> kBootArgs;
|
||||
bool _cjkFontLoaded = false;
|
||||
|
||||
Future<void> main(List<String> args) async {
|
||||
earlyAssert();
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
_cjkFontLoaded = await loadSystemCJKFonts();
|
||||
if (_cjkFontLoaded) {
|
||||
MyTheme.applyFontFallback([kLinuxCjkFontFamily]);
|
||||
}
|
||||
|
||||
debugPrint("launch args: $args");
|
||||
kBootArgs = List.from(args);
|
||||
@@ -390,7 +383,6 @@ void _runApp(
|
||||
builder: (context, child) {
|
||||
child = _keepScaleBuilder(context, child);
|
||||
child = botToastBuilder(context, child);
|
||||
if (_cjkFontLoaded) child = _mergeCjkFallback(context, child);
|
||||
return child;
|
||||
},
|
||||
),
|
||||
@@ -541,7 +533,6 @@ class _AppState extends State<App> with WidgetsBindingObserver {
|
||||
: (context, child) {
|
||||
child = _keepScaleBuilder(context, child);
|
||||
child = botToastBuilder(context, child);
|
||||
if (_cjkFontLoaded) child = _mergeCjkFallback(context, child);
|
||||
if ((isDesktop && desktopType == DesktopType.main) ||
|
||||
isWebDesktop) {
|
||||
child = keyListenerBuilder(context, child);
|
||||
@@ -595,19 +586,6 @@ _registerEventHandler() {
|
||||
}
|
||||
}
|
||||
|
||||
/// Merges the theme's fontFamilyFallback into [DefaultTextStyle] so that
|
||||
/// bare [Text] widgets (and those with inherit:true styles) also pick up the
|
||||
/// CJK fallback font loaded on ARM64 Linux.
|
||||
Widget _mergeCjkFallback(BuildContext context, Widget? child) {
|
||||
final result = child ?? Container();
|
||||
final fallback = Theme.of(context).textTheme.bodyMedium?.fontFamilyFallback;
|
||||
if (fallback == null || fallback.isEmpty) return result;
|
||||
return DefaultTextStyle.merge(
|
||||
style: TextStyle(fontFamilyFallback: fallback),
|
||||
child: result,
|
||||
);
|
||||
}
|
||||
|
||||
Widget keyListenerBuilder(BuildContext context, Widget? child) {
|
||||
return RawKeyboardListener(
|
||||
focusNode: FocusNode(),
|
||||
|
||||
@@ -1220,11 +1220,7 @@ void showOptions(
|
||||
if (image != null) {
|
||||
displays.add(Padding(padding: const EdgeInsets.only(top: 8), child: image));
|
||||
}
|
||||
final privacyModeState = PrivacyModeState.find(id);
|
||||
if (pi.displays.length > 1 &&
|
||||
pi.currentDisplay != kAllDisplayValue &&
|
||||
(privacyModeState.isEmpty ||
|
||||
allowDisplaySwitchInPrivacyMode(pi, privacyModeState.value))) {
|
||||
if (pi.displays.length > 1 && pi.currentDisplay != kAllDisplayValue) {
|
||||
final cur = pi.currentDisplay;
|
||||
final children = <Widget>[];
|
||||
final isDarkTheme = MyTheme.currentThemeMode() == ThemeMode.dark;
|
||||
@@ -1278,6 +1274,8 @@ void showOptions(
|
||||
await toolbarDisplayToggle(context, id, gFFI);
|
||||
|
||||
List<TToggleMenu> privacyModeList = [];
|
||||
// privacy mode
|
||||
final privacyModeState = PrivacyModeState.find(id);
|
||||
if ((gFFI.ffiModel.pi.features.privacyMode && gFFI.ffiModel.keyboard) ||
|
||||
privacyModeState.isNotEmpty) {
|
||||
privacyModeList = toolbarPrivacyMode(privacyModeState, context, id, gFFI);
|
||||
|
||||
@@ -1,109 +0,0 @@
|
||||
import 'dart:ffi' show Abi;
|
||||
import 'dart:io';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
/// Font family name registered with [FontLoader] when a system CJK font is
|
||||
/// successfully loaded on ARM64 Linux.
|
||||
const kLinuxCjkFontFamily = 'SystemCJK';
|
||||
|
||||
const _kFontSearchPaths = [
|
||||
// Debian / Ubuntu (noto-fonts / fonts-noto-cjk)
|
||||
'/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc',
|
||||
'/usr/share/fonts/truetype/noto/NotoSansCJK-Regular.ttc',
|
||||
'/usr/share/fonts/opentype/noto/NotoSansCJKsc-Regular.otf',
|
||||
// Fedora / RHEL / Rocky (google-noto-sans-cjk-fonts)
|
||||
'/usr/share/fonts/google-noto-cjk/NotoSansCJK-Regular.ttc',
|
||||
'/usr/share/fonts/google-noto-sans-cjk-fonts/NotoSansCJK-Regular.ttc',
|
||||
// Arch Linux (noto-fonts-cjk)
|
||||
'/usr/share/fonts/noto-cjk/NotoSansCJK-Regular.ttc',
|
||||
'/usr/share/fonts/noto-cjk/NotoSansCJKsc-Regular.otf',
|
||||
// Generic fallback paths
|
||||
'/usr/share/fonts/noto/NotoSansCJK-Regular.ttc',
|
||||
'/usr/share/fonts/noto/NotoSansCJKsc-Regular.otf',
|
||||
// WenQuanYi — commonly pre-installed on CJK-locale systems
|
||||
'/usr/share/fonts/truetype/wqy/wqy-microhei.ttc',
|
||||
'/usr/share/fonts/truetype/wqy/wqy-zenhei.ttc',
|
||||
'/usr/share/fonts/wqy-microhei/wqy-microhei.ttc',
|
||||
'/usr/share/fonts/wqy-zenhei/wqy-zenhei.ttc',
|
||||
];
|
||||
|
||||
/// Loads a system CJK font on ARM64 Linux into Flutter's font registry via
|
||||
/// [FontLoader], working around the missing fontconfig support in the
|
||||
/// flutter-elinux engine (https://github.com/flutter/flutter/issues/139293).
|
||||
///
|
||||
/// Returns true if a CJK font was successfully loaded; false otherwise.
|
||||
/// On all other platforms this is a no-op and returns false immediately.
|
||||
Future<bool> loadSystemCJKFonts() async {
|
||||
if (Abi.current() != Abi.linuxArm64) return false;
|
||||
|
||||
final path = await _findCjkFontPath();
|
||||
if (path == null) {
|
||||
debugPrint('ARM64 Linux: no CJK font found; CJK text may not render');
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
final loader = FontLoader(kLinuxCjkFontFamily);
|
||||
final bytes = await File(path).readAsBytes();
|
||||
loader.addFont(Future.value(ByteData.view(bytes.buffer, bytes.offsetInBytes, bytes.lengthInBytes)));
|
||||
await loader.load();
|
||||
debugPrint('ARM64 Linux: loaded CJK font from $path');
|
||||
return true;
|
||||
} catch (e) {
|
||||
debugPrint('ARM64 Linux: failed to load CJK font: $e');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Future<String?> _findCjkFontPath() async {
|
||||
// Query fc-list for each CJK script separately. Fonts present in all three
|
||||
// sets (zh ∩ ja ∩ ko) are true pan-CJK fonts; prefer them so we don't
|
||||
// accidentally pick a Chinese-only font that lacks Japanese kana or Korean
|
||||
// hangul glyphs. fc-list is a fontconfig CLI tool available on most Linux
|
||||
// systems independent of whether the Flutter engine was built with fontconfig.
|
||||
final byLang = <String, Set<String>>{};
|
||||
for (final lang in const ['zh', 'ja', 'ko']) {
|
||||
final paths = <String>{};
|
||||
try {
|
||||
final r =
|
||||
await Process.run('fc-list', [':lang=$lang', '--format=%{file}\n']);
|
||||
if (r.exitCode == 0) {
|
||||
for (final line in r.stdout.toString().split('\n')) {
|
||||
final p = line.trim();
|
||||
if (p.isNotEmpty && File(p).existsSync()) paths.add(p);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint('ARM64 Linux: fc-list failed for lang=$lang: $e');
|
||||
}
|
||||
byLang[lang] = paths;
|
||||
}
|
||||
|
||||
final panCjk = byLang['zh']!
|
||||
.intersection(byLang['ja']!)
|
||||
.intersection(byLang['ko']!);
|
||||
final anyCjk =
|
||||
byLang.values.fold(<String>{}, (acc, s) => acc..addAll(s));
|
||||
|
||||
// Among candidates, prefer well-known pan-CJK font families.
|
||||
String? pick(Iterable<String> pool) {
|
||||
const preferred = ['notosanscjk', 'sourcehansans', 'sourcehanserif'];
|
||||
for (final name in preferred) {
|
||||
for (final p in pool) {
|
||||
if (p.toLowerCase().contains(name)) return p;
|
||||
}
|
||||
}
|
||||
return pool.isNotEmpty ? pool.first : null;
|
||||
}
|
||||
|
||||
final found = pick(panCjk) ?? pick(anyCjk);
|
||||
if (found != null) return found;
|
||||
|
||||
for (final p in _kFontSearchPaths) {
|
||||
if (File(p).existsSync()) return p;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
/// Web stub for `native/font_manager.dart`.
|
||||
///
|
||||
/// The native implementation depends on `dart:io` (Process/File/Platform) to
|
||||
/// load a system CJK font on ARM64 Linux, which cannot compile for the web
|
||||
/// target. The web build has no such fontconfig limitation, so this is a no-op.
|
||||
const kLinuxCjkFontFamily = 'SystemCJK';
|
||||
|
||||
Future<bool> loadSystemCJKFonts() async => false;
|
||||
@@ -7,7 +7,8 @@ use fuser::MountOption;
|
||||
use hbb_common::{config::APP_NAME, log};
|
||||
use parking_lot::Mutex;
|
||||
use std::{
|
||||
path::PathBuf,
|
||||
io,
|
||||
path::{Path, PathBuf},
|
||||
sync::{mpsc::Sender, Arc},
|
||||
time::Duration,
|
||||
};
|
||||
@@ -53,8 +54,16 @@ pub fn init_fuse_context(is_client: bool) -> Result<(), CliprdrError> {
|
||||
} else {
|
||||
FUSE_CONTEXT_SERVER.lock()
|
||||
};
|
||||
if fuse_context_lock.is_some() {
|
||||
return Ok(());
|
||||
if let Some(ctx) = fuse_context_lock.as_ref() {
|
||||
if is_mount_point_healthy(&ctx.mount_point) {
|
||||
return Ok(());
|
||||
}
|
||||
log::warn!(
|
||||
"clipboard FUSE mount {} is disconnected, remounting",
|
||||
ctx.mount_point.display()
|
||||
);
|
||||
let stale_context = fuse_context_lock.take();
|
||||
drop(stale_context);
|
||||
}
|
||||
let mount_point = if is_client {
|
||||
FUSE_MOUNT_POINT_CLIENT.clone()
|
||||
@@ -159,35 +168,100 @@ struct FuseContext {
|
||||
}
|
||||
|
||||
// this function must be called after the main IPC is up
|
||||
fn prepare_fuse_mount_point(mount_point: &PathBuf) {
|
||||
fn prepare_fuse_mount_point(mount_point: &Path) {
|
||||
use std::{
|
||||
fs::{self, Permissions},
|
||||
os::unix::prelude::PermissionsExt,
|
||||
};
|
||||
|
||||
fs::create_dir(mount_point).ok();
|
||||
fs::set_permissions(mount_point, Permissions::from_mode(0o777)).ok();
|
||||
if let Some(parent) = mount_point.parent() {
|
||||
if let Err(e) = fs::create_dir_all(parent) {
|
||||
log::warn!("failed to create FUSE mount parent {:?}: {:?}", parent, e);
|
||||
}
|
||||
}
|
||||
|
||||
if let Err(e) = std::process::Command::new("umount")
|
||||
.arg(mount_point)
|
||||
.status()
|
||||
{
|
||||
log::warn!("umount {:?} may fail: {:?}", mount_point, e);
|
||||
unmount_fuse_mount_point(mount_point);
|
||||
|
||||
if let Err(e) = fs::create_dir_all(mount_point) {
|
||||
log::warn!(
|
||||
"failed to create FUSE mount point {:?}: {:?}",
|
||||
mount_point,
|
||||
e
|
||||
);
|
||||
}
|
||||
if let Err(e) = fs::set_permissions(mount_point, Permissions::from_mode(0o777)) {
|
||||
log::warn!(
|
||||
"failed to set FUSE mount point permissions {:?}: {:?}",
|
||||
mount_point,
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn uninit_fuse_context_(is_client: bool) {
|
||||
if is_client {
|
||||
let _ = FUSE_CONTEXT_CLIENT.lock().take();
|
||||
} else {
|
||||
let _ = FUSE_CONTEXT_SERVER.lock().take();
|
||||
fn is_mount_point_healthy(mount_point: &Path) -> bool {
|
||||
is_mount_point_healthy_result(std::fs::metadata(mount_point))
|
||||
}
|
||||
|
||||
fn is_mount_point_healthy_result<T>(result: io::Result<T>) -> bool {
|
||||
match result {
|
||||
Ok(_) => true,
|
||||
Err(e) => {
|
||||
e.raw_os_error() != Some(libc::ENOTCONN) && e.kind() != io::ErrorKind::NotFound
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn unmount_fuse_mount_point(mount_point: &Path) {
|
||||
if run_unmount_command("umount", &["-l"], mount_point) {
|
||||
return;
|
||||
}
|
||||
if run_unmount_command("fusermount3", &["-uz"], mount_point) {
|
||||
return;
|
||||
}
|
||||
run_unmount_command("fusermount", &["-uz"], mount_point);
|
||||
}
|
||||
|
||||
fn run_unmount_command(program: &str, args: &[&str], mount_point: &Path) -> bool {
|
||||
match std::process::Command::new(program)
|
||||
.args(args)
|
||||
.arg(mount_point)
|
||||
.status()
|
||||
{
|
||||
Ok(status) if status.success() => {}
|
||||
Ok(status) => {
|
||||
log::debug!(
|
||||
"{} {:?} exited with status {:?}",
|
||||
program,
|
||||
mount_point,
|
||||
status.code()
|
||||
);
|
||||
return false;
|
||||
}
|
||||
Err(e) => {
|
||||
log::debug!("failed to run {} for {:?}: {:?}", program, mount_point, e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
fn uninit_fuse_context_(is_client: bool) {
|
||||
let mut fuse_context_lock = if is_client {
|
||||
FUSE_CONTEXT_CLIENT.lock()
|
||||
} else {
|
||||
FUSE_CONTEXT_SERVER.lock()
|
||||
};
|
||||
let ctx = fuse_context_lock.take();
|
||||
drop(ctx);
|
||||
}
|
||||
|
||||
impl Drop for FuseContext {
|
||||
fn drop(&mut self) {
|
||||
self.session.lock().take().map(|s| s.join());
|
||||
log::info!("unmounting clipboard FUSE from {}", self.mount_point.display());
|
||||
unmount_fuse_mount_point(&self.mount_point);
|
||||
if let Some(session) = self.session.lock().take() {
|
||||
session.join();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -223,3 +297,30 @@ impl FuseContext {
|
||||
.collect())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::{fs, io};
|
||||
|
||||
#[test]
|
||||
fn reports_disconnected_fuse_mount_as_unhealthy() {
|
||||
let err = io::Error::from_raw_os_error(libc::ENOTCONN);
|
||||
|
||||
assert!(!is_mount_point_healthy_result::<()>(Err(err)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reports_existing_directory_mount_point_as_healthy() {
|
||||
let mount_point = std::env::temp_dir().join(format!(
|
||||
"rustdesk-fuse-mount-health-test-{}",
|
||||
std::process::id()
|
||||
));
|
||||
let _ = fs::remove_dir_all(&mount_point);
|
||||
fs::create_dir(&mount_point).unwrap();
|
||||
|
||||
assert!(is_mount_point_healthy(&mount_point));
|
||||
|
||||
let _ = fs::remove_dir_all(&mount_point);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ fn link_vcpkg(mut path: PathBuf, name: &str) -> PathBuf {
|
||||
format!("{}-{}", target_arch, target_os)
|
||||
}
|
||||
} else if target_os == "windows" {
|
||||
format!("{}-windows-static", target_arch)
|
||||
"x64-windows-static".to_owned()
|
||||
} else {
|
||||
format!("{}-{}", target_arch, target_os)
|
||||
};
|
||||
|
||||
@@ -52,33 +52,6 @@ lazy_static::lazy_static! {
|
||||
static ref MAG_BUFFER: Mutex<(bool, Vec<u8>)> = Default::default();
|
||||
}
|
||||
|
||||
fn find_windows(cls: &str, name: &str) -> Result<Vec<HWND>> {
|
||||
let name_c = CString::new(name)?;
|
||||
let cls_c = if cls.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(CString::new(cls)?)
|
||||
};
|
||||
let mut hwnds = Vec::new();
|
||||
unsafe {
|
||||
let mut after = NULL as _;
|
||||
loop {
|
||||
let hwnd = FindWindowExA(
|
||||
NULL as _,
|
||||
after,
|
||||
cls_c.as_ref().map_or(NULL as _, |c| c.as_ptr()),
|
||||
name_c.as_ptr(),
|
||||
);
|
||||
if hwnd.is_null() {
|
||||
break;
|
||||
}
|
||||
hwnds.push(hwnd);
|
||||
after = hwnd;
|
||||
}
|
||||
}
|
||||
Ok(hwnds)
|
||||
}
|
||||
|
||||
pub type REFWICPixelFormatGUID = *const GUID;
|
||||
pub type WICPixelFormatGUID = GUID;
|
||||
|
||||
@@ -274,8 +247,6 @@ pub struct CapturerMag {
|
||||
rect: RECT,
|
||||
width: usize,
|
||||
height: usize,
|
||||
excluded_window_target: Option<(String, String)>,
|
||||
excluded_windows: Vec<HWND>,
|
||||
}
|
||||
|
||||
impl Drop for CapturerMag {
|
||||
@@ -290,10 +261,6 @@ impl CapturerMag {
|
||||
MagInterface::new().is_ok()
|
||||
}
|
||||
|
||||
// This captures through the legacy Windows Magnification API. Do not infer
|
||||
// multi-monitor capture support from privacy overlay coverage: WebRTC also
|
||||
// disables its magnifier capturer when SM_CMONITORS != 1.
|
||||
// https://webrtc.googlesource.com/src/+/1845922d5a1bf9c27deeffb4a8c8daea124434c1/modules/desktop_capture/win/screen_capturer_win_magnifier.cc
|
||||
pub(crate) fn new(origin: (i32, i32), width: usize, height: usize) -> Result<Self> {
|
||||
unsafe {
|
||||
let x = GetSystemMetrics(SM_XVIRTUALSCREEN);
|
||||
@@ -338,8 +305,6 @@ impl CapturerMag {
|
||||
},
|
||||
width,
|
||||
height,
|
||||
excluded_window_target: None,
|
||||
excluded_windows: Vec::new(),
|
||||
};
|
||||
|
||||
unsafe {
|
||||
@@ -471,41 +436,19 @@ impl CapturerMag {
|
||||
}
|
||||
|
||||
pub(crate) fn exclude(&mut self, cls: &str, name: &str) -> Result<bool> {
|
||||
let mut hwnds = find_windows(cls, name)?;
|
||||
hwnds.sort_unstable_by_key(|hwnd| *hwnd as usize);
|
||||
self.excluded_window_target = Some((cls.to_owned(), name.to_owned()));
|
||||
if hwnds.is_empty() {
|
||||
self.excluded_windows.clear();
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
self.exclude_windows(&mut hwnds)?;
|
||||
self.excluded_windows = hwnds;
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
fn refresh_excluded_windows(&mut self) -> Result<()> {
|
||||
let Some((cls, name)) = self.excluded_window_target.as_ref() else {
|
||||
return Ok(());
|
||||
};
|
||||
let mut hwnds = find_windows(cls, name)?;
|
||||
hwnds.sort_unstable_by_key(|hwnd| *hwnd as usize);
|
||||
// This runs from frame() because refreshed privacy overlays get new
|
||||
// HWNDs. It is only used on the legacy magnifier backend while privacy
|
||||
// mode is active; if it shows up as hot-path cost, throttle this check.
|
||||
// Keep the previous filter list while privacy windows are being recreated.
|
||||
if hwnds.is_empty() || hwnds == self.excluded_windows {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
self.exclude_windows(&mut hwnds)?;
|
||||
self.excluded_windows = hwnds;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn exclude_windows(&mut self, hwnds: &mut [HWND]) -> Result<bool> {
|
||||
let count = hwnds.len() as _;
|
||||
let name_c = CString::new(name)?;
|
||||
unsafe {
|
||||
let mut hwnd = if cls.len() == 0 {
|
||||
FindWindowExA(NULL as _, NULL as _, NULL as _, name_c.as_ptr())
|
||||
} else {
|
||||
let cls_c = CString::new(cls).unwrap();
|
||||
FindWindowExA(NULL as _, NULL as _, cls_c.as_ptr(), name_c.as_ptr())
|
||||
};
|
||||
|
||||
if hwnd.is_null() {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
if let Some(set_window_filter_list_func) =
|
||||
self.mag_interface.set_window_filter_list_func
|
||||
{
|
||||
@@ -513,15 +456,16 @@ impl CapturerMag {
|
||||
== set_window_filter_list_func(
|
||||
self.magnifier_window,
|
||||
MW_FILTERMODE_EXCLUDE,
|
||||
count,
|
||||
hwnds.as_mut_ptr(),
|
||||
1,
|
||||
&mut hwnd,
|
||||
)
|
||||
{
|
||||
return Err(Error::new(
|
||||
ErrorKind::Other,
|
||||
format!(
|
||||
"Failed MagSetWindowFilterList for {} windows, error {}",
|
||||
count,
|
||||
"Failed MagSetWindowFilterList for cls {} name {}, error {}",
|
||||
cls,
|
||||
name,
|
||||
Error::last_os_error()
|
||||
),
|
||||
));
|
||||
@@ -552,7 +496,6 @@ impl CapturerMag {
|
||||
}
|
||||
|
||||
pub(crate) fn frame(&mut self, data: &mut Vec<u8>) -> Result<()> {
|
||||
self.refresh_excluded_windows()?;
|
||||
Self::clear_data();
|
||||
|
||||
unsafe {
|
||||
|
||||
@@ -7,10 +7,6 @@
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|ARM64">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>ARM64</Platform>
|
||||
</ProjectConfiguration>
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Globals">
|
||||
<Keyword>Win32Proj</Keyword>
|
||||
@@ -26,12 +22,6 @@
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'" Label="Configuration">
|
||||
<ConfigurationType>DynamicLibrary</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||
<ImportGroup Label="ExtensionSettings">
|
||||
</ImportGroup>
|
||||
@@ -40,9 +30,6 @@
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<PropertyGroup Label="UserMacros" />
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<ClCompile>
|
||||
@@ -66,28 +53,6 @@
|
||||
<ModuleDefinitionFile>CustomActions.def</ModuleDefinitionFile>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>NDEBUG;EXAMPLECADLL_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<PrecompiledHeader>Use</PrecompiledHeader>
|
||||
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
|
||||
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<AdditionalDependencies>msi.lib;version.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<EnableUAC>false</EnableUAC>
|
||||
<ModuleDefinitionFile>CustomActions.def</ModuleDefinitionFile>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="Common.h" />
|
||||
<ClInclude Include="framework.h" />
|
||||
@@ -100,7 +65,6 @@
|
||||
<ClCompile Include="FirewallRules.cpp" />
|
||||
<ClCompile Include="pch.cpp">
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Create</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">Create</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
<ClCompile Include="ReadConfig.cpp" />
|
||||
<ClCompile Include="RemotePrinter.cpp" />
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<IncludeSearchPaths>
|
||||
</IncludeSearchPaths>
|
||||
<Configurations>Release</Configurations>
|
||||
<Platforms>x64;ARM64</Platforms>
|
||||
<Platforms>x64</Platforms>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Content Include="Includes.wxi" />
|
||||
|
||||
@@ -10,17 +10,12 @@ EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Release|x64 = Release|x64
|
||||
Release|ARM64 = Release|ARM64
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{F403A403-CEFF-4399-B51C-CC646C8E98CF}.Release|x64.ActiveCfg = Release|x64
|
||||
{F403A403-CEFF-4399-B51C-CC646C8E98CF}.Release|x64.Build.0 = Release|x64
|
||||
{F403A403-CEFF-4399-B51C-CC646C8E98CF}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||
{F403A403-CEFF-4399-B51C-CC646C8E98CF}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{6B3647E0-B4A3-46AE-8757-A22EE51C1DAC}.Release|x64.ActiveCfg = Release|x64
|
||||
{6B3647E0-B4A3-46AE-8757-A22EE51C1DAC}.Release|x64.Build.0 = Release|x64
|
||||
{6B3647E0-B4A3-46AE-8757-A22EE51C1DAC}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||
{6B3647E0-B4A3-46AE-8757-A22EE51C1DAC}.Release|ARM64.Build.0 = Release|ARM64
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
||||
@@ -130,18 +130,14 @@ elseif(VCPKG_TARGET_IS_WINDOWS)
|
||||
--cc=cl \
|
||||
--enable-gpl \
|
||||
--enable-d3d11va \
|
||||
--enable-hwaccel=h264_d3d11va \
|
||||
--enable-hwaccel=hevc_d3d11va \
|
||||
--enable-hwaccel=h264_d3d11va2 \
|
||||
--enable-hwaccel=hevc_d3d11va2 \
|
||||
")
|
||||
|
||||
if(VCPKG_TARGET_ARCHITECTURE STREQUAL "x86" OR VCPKG_TARGET_ARCHITECTURE STREQUAL "x64")
|
||||
string(APPEND OPTIONS "\
|
||||
--enable-cuda \
|
||||
--enable-ffnvcodec \
|
||||
--enable-hwaccel=h264_nvdec \
|
||||
--enable-hwaccel=hevc_nvdec \
|
||||
--enable-hwaccel=h264_d3d11va \
|
||||
--enable-hwaccel=hevc_d3d11va \
|
||||
--enable-hwaccel=h264_d3d11va2 \
|
||||
--enable-hwaccel=hevc_d3d11va2 \
|
||||
--enable-amf \
|
||||
--enable-encoder=h264_amf \
|
||||
--enable-encoder=hevc_amf \
|
||||
@@ -151,7 +147,6 @@ elseif(VCPKG_TARGET_IS_WINDOWS)
|
||||
--enable-encoder=h264_qsv \
|
||||
--enable-encoder=hevc_qsv \
|
||||
")
|
||||
endif()
|
||||
|
||||
if(VCPKG_TARGET_ARCHITECTURE STREQUAL "x86")
|
||||
set(LIB_MACHINE_ARG /machine:x86)
|
||||
@@ -159,9 +154,6 @@ elseif(VCPKG_TARGET_IS_WINDOWS)
|
||||
elseif(VCPKG_TARGET_ARCHITECTURE STREQUAL "x64")
|
||||
set(LIB_MACHINE_ARG /machine:x64)
|
||||
string(APPEND OPTIONS " --arch=x86_64")
|
||||
elseif(VCPKG_TARGET_ARCHITECTURE STREQUAL "arm64")
|
||||
set(LIB_MACHINE_ARG /machine:arm64)
|
||||
string(APPEND OPTIONS " --arch=aarch64 --enable-cross-compile")
|
||||
else()
|
||||
message(FATAL_ERROR "Unsupported target architecture")
|
||||
endif()
|
||||
|
||||
@@ -4,14 +4,13 @@ use hbb_common::{allow_err, bail, log, ResultType};
|
||||
use std::{
|
||||
ffi::CString,
|
||||
io::Error,
|
||||
mem::size_of,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
use winapi::{
|
||||
shared::{
|
||||
minwindef::{BOOL, FALSE, LPARAM, TRUE},
|
||||
minwindef::FALSE,
|
||||
ntdef::{HANDLE, NULL},
|
||||
windef::{HDC, HMONITOR, HWND, RECT},
|
||||
windef::HWND,
|
||||
},
|
||||
um::{
|
||||
handleapi::CloseHandle,
|
||||
@@ -32,13 +31,7 @@ pub(super) const PRIVACY_MODE_IMPL: &str = "privacy_mode_impl_mag";
|
||||
pub const ORIGIN_PROCESS_EXE: &'static str = "C:\\Windows\\System32\\RuntimeBroker.exe";
|
||||
pub const WIN_TOPMOST_INJECTED_PROCESS_EXE: &'static str = "RuntimeBroker_rustdesk.exe";
|
||||
pub const INJECTED_PROCESS_EXE: &'static str = WIN_TOPMOST_INJECTED_PROCESS_EXE;
|
||||
pub(super) const PRIVACY_WINDOW_CLASS: &'static str = "RustDeskPrivacyWindowClass";
|
||||
pub(super) const PRIVACY_WINDOW_NAME: &'static str = "RustDeskPrivacyWindow";
|
||||
const PRIVACY_WINDOW_WAIT_MILLIS: u128 = 1_000;
|
||||
const PRIVACY_WINDOW_WAIT_EXTRA_MONITOR_MILLIS: u128 = 500;
|
||||
const PRIVACY_WINDOW_POLL_INTERVAL_MILLIS: u64 = 100;
|
||||
const WM_RUSTDESK_SHOW_WINDOWS: u32 = WM_APP + 3;
|
||||
const WM_RUSTDESK_HIDE_WINDOWS: u32 = WM_APP + 4;
|
||||
|
||||
struct WindowHandlers {
|
||||
hthread: u64,
|
||||
@@ -109,17 +102,22 @@ impl PrivacyMode for PrivacyModeImpl {
|
||||
);
|
||||
}
|
||||
|
||||
let should_start_broker = self.handlers.is_default();
|
||||
if should_start_broker {
|
||||
log::info!("turn_on_privacy, broker not running, try start");
|
||||
if self.handlers.is_default() {
|
||||
log::info!("turn_on_privacy, dll not found when started, try start");
|
||||
self.start()?;
|
||||
std::thread::sleep(std::time::Duration::from_millis(1_000));
|
||||
}
|
||||
|
||||
if let Err(e) = self.show_privacy_windows(conn_id, true) {
|
||||
self.stop();
|
||||
return Err(e);
|
||||
let hwnd = wait_find_privacy_hwnd(0)?;
|
||||
if hwnd.is_null() {
|
||||
bail!("No privacy window created");
|
||||
}
|
||||
super::win_input::hook()?;
|
||||
unsafe {
|
||||
ShowWindow(hwnd as _, SW_SHOW);
|
||||
}
|
||||
self.conn_id = conn_id;
|
||||
self.hwnd = hwnd as _;
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
@@ -130,33 +128,27 @@ impl PrivacyMode for PrivacyModeImpl {
|
||||
) -> ResultType<()> {
|
||||
self.check_off_conn_id(conn_id)?;
|
||||
super::win_input::unhook()?;
|
||||
let hwnds = find_privacy_hwnds()?;
|
||||
let hide_result = set_privacy_windows_visible(&hwnds, false);
|
||||
if hide_result.is_err() {
|
||||
self.stop();
|
||||
|
||||
unsafe {
|
||||
let hwnd = wait_find_privacy_hwnd(0)?;
|
||||
if !hwnd.is_null() {
|
||||
ShowWindow(hwnd, SW_HIDE);
|
||||
}
|
||||
}
|
||||
|
||||
// Continue local state cleanup even after stop(); the broker has
|
||||
// been torn down, so keeping conn_id/hwnd would leave stale state.
|
||||
if self.conn_id != INVALID_PRIVACY_MODE_CONN_ID {
|
||||
// Only publish the off state after the hide message was posted.
|
||||
// Otherwise the peer may receive a success-like state and then a
|
||||
// failed turn-off response for the same request.
|
||||
if hide_result.is_ok() {
|
||||
if let Some(state) = state {
|
||||
allow_err!(super::set_privacy_mode_state(
|
||||
conn_id,
|
||||
state,
|
||||
PRIVACY_MODE_IMPL.to_string(),
|
||||
1_000
|
||||
));
|
||||
}
|
||||
if let Some(state) = state {
|
||||
allow_err!(super::set_privacy_mode_state(
|
||||
conn_id,
|
||||
state,
|
||||
PRIVACY_MODE_IMPL.to_string(),
|
||||
1_000
|
||||
));
|
||||
}
|
||||
self.conn_id = INVALID_PRIVACY_MODE_CONN_ID.to_owned();
|
||||
self.hwnd = 0;
|
||||
}
|
||||
|
||||
hide_result.map(|_| ())
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -214,7 +206,8 @@ impl PrivacyModeImpl {
|
||||
);
|
||||
}
|
||||
|
||||
if wait_find_privacy_hwnds(PRIVACY_WINDOW_WAIT_MILLIS).is_ok() {
|
||||
let hwnd = wait_find_privacy_hwnd(1_000)?;
|
||||
if !hwnd.is_null() {
|
||||
log::info!("Privacy window is ready");
|
||||
return Ok(());
|
||||
}
|
||||
@@ -283,19 +276,14 @@ impl PrivacyModeImpl {
|
||||
);
|
||||
};
|
||||
|
||||
if let Err(e) = inject_dll(
|
||||
inject_dll(
|
||||
proc_info.hProcess,
|
||||
proc_info.hThread,
|
||||
dll_file.to_string_lossy().as_ref(),
|
||||
) {
|
||||
TerminateProcess(proc_info.hProcess, 0);
|
||||
CloseHandle(proc_info.hThread);
|
||||
CloseHandle(proc_info.hProcess);
|
||||
return Err(e);
|
||||
}
|
||||
)?;
|
||||
|
||||
if 0xffffffff == ResumeThread(proc_info.hThread) {
|
||||
TerminateProcess(proc_info.hProcess, 0);
|
||||
// CloseHandle
|
||||
CloseHandle(proc_info.hThread);
|
||||
CloseHandle(proc_info.hProcess);
|
||||
|
||||
@@ -308,9 +296,9 @@ impl PrivacyModeImpl {
|
||||
self.handlers.hthread = proc_info.hThread as _;
|
||||
self.handlers.hprocess = proc_info.hProcess as _;
|
||||
|
||||
if let Err(e) = wait_find_privacy_hwnds(PRIVACY_WINDOW_WAIT_MILLIS) {
|
||||
self.handlers.reset();
|
||||
return Err(e);
|
||||
let hwnd = wait_find_privacy_hwnd(1_000)?;
|
||||
if hwnd.is_null() {
|
||||
bail!("Failed to get hwnd after started");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -321,49 +309,6 @@ impl PrivacyModeImpl {
|
||||
pub fn stop(&mut self) {
|
||||
self.handlers.reset();
|
||||
}
|
||||
|
||||
fn show_privacy_windows(&mut self, conn_id: i32, hook_input: bool) -> ResultType<()> {
|
||||
let hwnds = wait_find_privacy_hwnds(PRIVACY_WINDOW_WAIT_MILLIS)?;
|
||||
if hwnds.is_empty() {
|
||||
bail!("No privacy window created");
|
||||
}
|
||||
|
||||
if hook_input {
|
||||
super::win_input::hook()?;
|
||||
}
|
||||
match set_privacy_windows_visible(&hwnds, true) {
|
||||
Ok(_) => {
|
||||
let visible_hwnds =
|
||||
match wait_find_visible_privacy_hwnds(PRIVACY_WINDOW_WAIT_MILLIS) {
|
||||
Ok(hwnds) => hwnds,
|
||||
Err(e) => {
|
||||
allow_err!(set_privacy_windows_visible(&hwnds, false));
|
||||
if hook_input {
|
||||
allow_err!(super::win_input::unhook());
|
||||
}
|
||||
return Err(e);
|
||||
}
|
||||
};
|
||||
let Some(hwnd) = visible_hwnds.first() else {
|
||||
allow_err!(set_privacy_windows_visible(&hwnds, false));
|
||||
if hook_input {
|
||||
allow_err!(super::win_input::unhook());
|
||||
}
|
||||
bail!("No visible privacy window created");
|
||||
};
|
||||
self.conn_id = conn_id;
|
||||
self.hwnd = *hwnd as _;
|
||||
Ok(())
|
||||
}
|
||||
Err(e) => {
|
||||
allow_err!(set_privacy_windows_visible(&hwnds, false));
|
||||
if hook_input {
|
||||
allow_err!(super::win_input::unhook());
|
||||
}
|
||||
Err(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for PrivacyModeImpl {
|
||||
@@ -418,217 +363,21 @@ unsafe fn inject_dll<'a>(hproc: HANDLE, hthread: HANDLE, dll_file: &'a str) -> R
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn wait_find_privacy_hwnds(msecs: u128) -> ResultType<Vec<HWND>> {
|
||||
wait_find_privacy_hwnds_impl(msecs, false)
|
||||
}
|
||||
|
||||
fn wait_find_visible_privacy_hwnds(msecs: u128) -> ResultType<Vec<HWND>> {
|
||||
wait_find_privacy_hwnds_impl(msecs, true)
|
||||
}
|
||||
|
||||
fn privacy_window_wait_millis(base_millis: u128, monitor_count: usize) -> u128 {
|
||||
if base_millis == 0 {
|
||||
return 0;
|
||||
}
|
||||
// Privacy Mode 1 creates one overlay per monitor. Keep the single-monitor
|
||||
// wait as the base and add time for each extra overlay before coverage
|
||||
// verification times out.
|
||||
base_millis
|
||||
+ (monitor_count.saturating_sub(1) as u128) * PRIVACY_WINDOW_WAIT_EXTRA_MONITOR_MILLIS
|
||||
}
|
||||
|
||||
fn wait_find_privacy_hwnds_impl(msecs: u128, require_visible: bool) -> ResultType<Vec<HWND>> {
|
||||
// This verifies initial turn-on coverage. If displays change during this
|
||||
// short poll window, the DLL refreshes overlays asynchronously, while this
|
||||
// check may still time out against the geometry sampled here.
|
||||
let monitor_rects = get_monitor_rects()?;
|
||||
if monitor_rects.is_empty() {
|
||||
bail!("No privacy monitor found");
|
||||
}
|
||||
let msecs = privacy_window_wait_millis(msecs, monitor_rects.len());
|
||||
|
||||
pub(super) fn wait_find_privacy_hwnd(msecs: u128) -> ResultType<HWND> {
|
||||
let tm_begin = Instant::now();
|
||||
let wndname = CString::new(PRIVACY_WINDOW_NAME)?;
|
||||
loop {
|
||||
let hwnds = find_privacy_hwnds()?;
|
||||
let visible_hwnds = if require_visible {
|
||||
filter_visible_hwnds(&hwnds)
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
let covered_hwnds = if require_visible {
|
||||
visible_hwnds.as_slice()
|
||||
} else {
|
||||
hwnds.as_slice()
|
||||
};
|
||||
let covered = count_covered_monitors(covered_hwnds, &monitor_rects);
|
||||
if covered == monitor_rects.len() {
|
||||
return Ok(if require_visible {
|
||||
visible_hwnds
|
||||
} else {
|
||||
hwnds
|
||||
});
|
||||
unsafe {
|
||||
let hwnd = FindWindowA(NULL as _, wndname.as_ptr() as _);
|
||||
if !hwnd.is_null() {
|
||||
return Ok(hwnd);
|
||||
}
|
||||
}
|
||||
|
||||
if msecs == 0 || tm_begin.elapsed().as_millis() > msecs {
|
||||
let visible = if require_visible { "visible " } else { "" };
|
||||
bail!(
|
||||
"Expected {}privacy windows to cover {} monitors, covered {}, found {}",
|
||||
visible,
|
||||
monitor_rects.len(),
|
||||
covered,
|
||||
hwnds.len(),
|
||||
);
|
||||
return Ok(NULL as _);
|
||||
}
|
||||
|
||||
std::thread::sleep(Duration::from_millis(PRIVACY_WINDOW_POLL_INTERVAL_MILLIS));
|
||||
std::thread::sleep(Duration::from_millis(100));
|
||||
}
|
||||
}
|
||||
|
||||
fn find_privacy_hwnds() -> ResultType<Vec<HWND>> {
|
||||
let class_name = CString::new(PRIVACY_WINDOW_CLASS)?;
|
||||
let wndname = CString::new(PRIVACY_WINDOW_NAME)?;
|
||||
let mut hwnds = Vec::new();
|
||||
unsafe {
|
||||
let mut after = NULL as _;
|
||||
loop {
|
||||
let hwnd = FindWindowExA(
|
||||
NULL as _,
|
||||
after,
|
||||
class_name.as_ptr() as _,
|
||||
wndname.as_ptr() as _,
|
||||
);
|
||||
if hwnd.is_null() {
|
||||
break;
|
||||
}
|
||||
hwnds.push(hwnd);
|
||||
after = hwnd;
|
||||
}
|
||||
}
|
||||
Ok(hwnds)
|
||||
}
|
||||
|
||||
fn filter_visible_hwnds(hwnds: &[HWND]) -> Vec<HWND> {
|
||||
hwnds
|
||||
.iter()
|
||||
.copied()
|
||||
.filter(|hwnd| unsafe { FALSE != IsWindowVisible(*hwnd) })
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn set_privacy_windows_visible(hwnds: &[HWND], show: bool) -> ResultType<usize> {
|
||||
if hwnds.is_empty() {
|
||||
return Ok(0);
|
||||
};
|
||||
let message = if show {
|
||||
WM_RUSTDESK_SHOW_WINDOWS
|
||||
} else {
|
||||
WM_RUSTDESK_HIDE_WINDOWS
|
||||
};
|
||||
let mut posted = 0;
|
||||
let mut first_error = None;
|
||||
for &hwnd in hwnds {
|
||||
unsafe {
|
||||
if FALSE == PostMessageA(hwnd, message, 0, 0) {
|
||||
if first_error.is_none() {
|
||||
first_error = Some(Error::last_os_error());
|
||||
}
|
||||
} else {
|
||||
posted += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(error) = first_error {
|
||||
bail!(
|
||||
"Failed to post privacy window visibility message to all privacy windows, posted {}/{}, first error {}",
|
||||
posted,
|
||||
hwnds.len(),
|
||||
error,
|
||||
);
|
||||
}
|
||||
Ok(posted)
|
||||
}
|
||||
|
||||
fn get_monitor_rects() -> ResultType<Vec<RECT>> {
|
||||
let mut rects = Vec::new();
|
||||
unsafe {
|
||||
if FALSE
|
||||
== EnumDisplayMonitors(
|
||||
NULL as _,
|
||||
NULL as _,
|
||||
Some(enum_monitor_rect_proc),
|
||||
&mut rects as *mut Vec<RECT> as LPARAM,
|
||||
)
|
||||
{
|
||||
bail!(
|
||||
"Failed EnumDisplayMonitors, error {}",
|
||||
Error::last_os_error()
|
||||
);
|
||||
}
|
||||
}
|
||||
Ok(rects)
|
||||
}
|
||||
|
||||
unsafe extern "system" fn enum_monitor_rect_proc(
|
||||
hmon: HMONITOR,
|
||||
_hdc: HDC,
|
||||
_rect: *mut RECT,
|
||||
lparam: LPARAM,
|
||||
) -> BOOL {
|
||||
let rects = &mut *(lparam as *mut Vec<RECT>);
|
||||
let mut monitor_info = MONITORINFO {
|
||||
cbSize: size_of::<MONITORINFO>() as _,
|
||||
rcMonitor: RECT {
|
||||
left: 0,
|
||||
top: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
},
|
||||
rcWork: RECT {
|
||||
left: 0,
|
||||
top: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
},
|
||||
dwFlags: 0,
|
||||
};
|
||||
if FALSE == GetMonitorInfoA(hmon, &mut monitor_info) {
|
||||
return FALSE;
|
||||
}
|
||||
rects.push(monitor_info.rcMonitor);
|
||||
TRUE
|
||||
}
|
||||
|
||||
fn count_covered_monitors(hwnds: &[HWND], monitor_rects: &[RECT]) -> usize {
|
||||
let mut covered = 0;
|
||||
for monitor_rect in monitor_rects {
|
||||
for hwnd in hwnds {
|
||||
let mut window_rect = RECT {
|
||||
left: 0,
|
||||
top: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
};
|
||||
unsafe {
|
||||
if FALSE == GetWindowRect(*hwnd, &mut window_rect) {
|
||||
log::warn!(
|
||||
"Failed GetWindowRect for privacy window, error {}",
|
||||
Error::last_os_error()
|
||||
);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if rect_covers(&window_rect, monitor_rect) {
|
||||
covered += 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
covered
|
||||
}
|
||||
|
||||
fn rect_covers(window_rect: &RECT, monitor_rect: &RECT) -> bool {
|
||||
window_rect.left <= monitor_rect.left
|
||||
&& window_rect.top <= monitor_rect.top
|
||||
&& window_rect.right >= monitor_rect.right
|
||||
&& window_rect.bottom >= monitor_rect.bottom
|
||||
}
|
||||
|
||||
@@ -272,10 +272,6 @@ fn create_capturer(
|
||||
if privacy_mode_id > 0 {
|
||||
#[cfg(windows)]
|
||||
{
|
||||
// Windows Mode 1 can cover every local monitor with overlay windows,
|
||||
// but the legacy magnifier capture backend is still single-monitor
|
||||
// constrained. Keep display-switch gating aligned with that backend
|
||||
// limit, not just the overlay coverage.
|
||||
if let Some(c1) = crate::privacy_mode::win_mag::create_capturer(
|
||||
privacy_mode_id,
|
||||
display.origin(),
|
||||
|
||||
11
vcpkg.json
11
vcpkg.json
@@ -20,11 +20,6 @@
|
||||
"name": "libjpeg-turbo",
|
||||
"host": false
|
||||
},
|
||||
{
|
||||
"name": "libsodium",
|
||||
"host": false,
|
||||
"platform": "windows & arm64"
|
||||
},
|
||||
{
|
||||
"name": "oboe",
|
||||
"platform": "android"
|
||||
@@ -69,15 +64,15 @@
|
||||
"features": [
|
||||
{
|
||||
"name": "amf",
|
||||
"platform": "(((windows & !arm) | linux) & static)"
|
||||
"platform": "((windows | linux) & static)"
|
||||
},
|
||||
{
|
||||
"name": "nvcodec",
|
||||
"platform": "(((windows & !arm) | linux) & static)"
|
||||
"platform": "((windows | linux) & static)"
|
||||
},
|
||||
{
|
||||
"name": "qsv",
|
||||
"platform": "(windows & !arm & static)"
|
||||
"platform": "(windows & static)"
|
||||
}
|
||||
],
|
||||
"platform": "((windows | (linux & !arm32) | osx) & static)"
|
||||
|
||||
Reference in New Issue
Block a user