mirror of
https://github.com/rustdesk/rustdesk.git
synced 2026-06-20 15:24:56 +03:00
Compare commits
16 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cd2fff0655 | ||
|
|
10f61ffdc2 | ||
|
|
d72952bf93 | ||
|
|
a7c55db9ac | ||
|
|
bff47e2b81 | ||
|
|
3d478c4935 | ||
|
|
a658e987b7 | ||
|
|
7c8b0adc1e | ||
|
|
a732ebc3e1 | ||
|
|
30c0867e40 | ||
|
|
8f50ea64dc | ||
|
|
0797ebb695 | ||
|
|
c9391fb894 | ||
|
|
8a955888bf | ||
|
|
36e812e550 | ||
|
|
8baa995c7a |
@@ -2,6 +2,8 @@
|
||||
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",
|
||||
|
||||
39
.github/patches/apply_flutter_3.44_source_patches.sh
vendored
Normal file
39
.github/patches/apply_flutter_3.44_source_patches.sh
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
#!/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,7 +7,6 @@ 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
|
||||
|
||||
@@ -18,10 +17,21 @@ 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
|
||||
@@ -64,13 +74,13 @@ jobs:
|
||||
uses: actions/cache@6f8efc29b200d32929f49075959781ed54ec270c # v3
|
||||
with:
|
||||
path: /tmp/flutter_rust_bridge
|
||||
key: vcpkg-${{ matrix.job.arch }}
|
||||
key: bridge-${{ matrix.job.flutter-version }}
|
||||
|
||||
- name: Install flutter
|
||||
uses: subosito/flutter-action@1a449444c387b1966244ae4d4f8c696479add0b2 # v2
|
||||
with:
|
||||
channel: "stable"
|
||||
flutter-version: ${{ env.FLUTTER_VERSION }}
|
||||
flutter-version: ${{ matrix.job.flutter-version }}
|
||||
cache: true
|
||||
|
||||
- name: Install flutter rust bridge deps
|
||||
@@ -78,7 +88,15 @@ 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
|
||||
pushd flutter && sed -i -e 's/extended_text: 14.0.0/extended_text: 13.0.0/g' pubspec.yaml && flutter pub get && popd
|
||||
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
|
||||
|
||||
- name: Run flutter rust bridge
|
||||
run: |
|
||||
@@ -88,7 +106,7 @@ jobs:
|
||||
- name: Upload Artifact
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: bridge-artifact
|
||||
name: ${{ matrix.job.artifact-name }}
|
||||
path: |
|
||||
./src/bridge_generated.rs
|
||||
./src/bridge_generated.io.rs
|
||||
|
||||
1
.github/workflows/ci.yml
vendored
1
.github/workflows/ci.yml
vendored
@@ -81,6 +81,7 @@ 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:
|
||||
|
||||
128
.github/workflows/flutter-build.yml
vendored
128
.github/workflows/flutter-build.yml
vendored
@@ -27,6 +27,11 @@ 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 }}"
|
||||
@@ -39,7 +44,7 @@ env:
|
||||
# 2. Update the `VCPKG_COMMIT_ID` in `ci.yml` and `playground.yml`.
|
||||
VCPKG_COMMIT_ID: "120deac3062162151622ca4860575a33844ba10b"
|
||||
ARMV7_VCPKG_COMMIT_ID: "6f29f12e82a8293156836ad81cc9bf5af41fe836" # 2025.01.13, got "/opt/artifacts/vcpkg/vcpkg: No such file or directory" with latest version
|
||||
VERSION: "1.4.7"
|
||||
VERSION: "1.4.8"
|
||||
NDK_VERSION: "r28c"
|
||||
#signing keys env variable checks
|
||||
ANDROID_SIGNING_KEY: "${{ secrets.ANDROID_SIGNING_KEY }}"
|
||||
@@ -53,14 +58,24 @@ jobs:
|
||||
|
||||
build-RustDeskTempTopMostWindow:
|
||||
uses: ./.github/workflows/third-party-RustDeskTempTopMostWindow.yml
|
||||
with:
|
||||
upload-artifact: ${{ inputs.upload-artifact }}
|
||||
target: windows-2022
|
||||
configuration: Release
|
||||
platform: x64
|
||||
target_version: Windows10
|
||||
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 }}
|
||||
configuration: Release
|
||||
platform: ${{ matrix.job.platform }}
|
||||
target_version: Windows10
|
||||
|
||||
build-for-windows-flutter:
|
||||
name: ${{ matrix.job.target }}
|
||||
@@ -76,9 +91,20 @@ 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
|
||||
@@ -95,36 +121,91 @@ jobs:
|
||||
- name: Restore bridge files
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
name: bridge-artifact
|
||||
# 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' }}
|
||||
path: ./
|
||||
|
||||
- name: Install LLVM and Clang
|
||||
uses: KyleMayes/install-llvm-action@1a3da29f56261a1e1f937ec88f0856a9b8321d7e # v1
|
||||
uses: KyleMayes/install-llvm-action@ebc0426251bc40c7cd31162802432c68818ab8f0 # v2.0.9
|
||||
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: ${{ env.FLUTTER_VERSION }}
|
||||
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
|
||||
}
|
||||
|
||||
# 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:
|
||||
@@ -163,11 +244,19 @@ 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
|
||||
python3 .\build.py --portable --hwcodec --flutter --vram --skip-portable-pack
|
||||
mv ./flutter/build/windows/x64/runner/Release ./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
|
||||
|
||||
# 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
|
||||
@@ -223,7 +312,7 @@ jobs:
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
if: ${{ inputs.upload-artifact }}
|
||||
with:
|
||||
name: topmostwindow-artifacts
|
||||
name: ${{ matrix.job.arch == 'aarch64' && 'topmostwindow-artifacts-ARM64' || 'topmostwindow-artifacts-x64' }}
|
||||
path: "./rustdesk"
|
||||
|
||||
- name: Upload unsigned
|
||||
@@ -256,13 +345,18 @@ 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
|
||||
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
|
||||
$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
|
||||
sha256sum ../../SignOutput/rustdesk-*.msi
|
||||
|
||||
- name: Sign rustdesk self-extracted file
|
||||
|
||||
2
.github/workflows/playground.yml
vendored
2
.github/workflows/playground.yml
vendored
@@ -17,7 +17,7 @@ env:
|
||||
TAG_NAME: "nightly"
|
||||
VCPKG_BINARY_SOURCES: "clear;x-gha,readwrite"
|
||||
VCPKG_COMMIT_ID: "120deac3062162151622ca4860575a33844ba10b"
|
||||
VERSION: "1.4.7"
|
||||
VERSION: "1.4.8"
|
||||
NDK_VERSION: "r26d"
|
||||
#signing keys env variable checks
|
||||
ANDROID_SIGNING_KEY: "${{ secrets.ANDROID_SIGNING_KEY }}"
|
||||
|
||||
@@ -45,16 +45,15 @@ 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 53b548a5398624f7149a382000397993542ad796
|
||||
cd RustDeskTempTopMostWindow && git checkout ecd8d6a139eee76845ea66423fb739af450fda90
|
||||
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
|
||||
name: topmostwindow-artifacts-${{ inputs.platform }}
|
||||
path: |
|
||||
./${{ env.build_output_dir }}/WindowInjection.dll
|
||||
|
||||
20
Cargo.lock
generated
20
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.8.4",
|
||||
"libloading 0.7.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2694,7 +2694,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.60.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3952,7 +3952,7 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
|
||||
[[package]]
|
||||
name = "hwcodec"
|
||||
version = "0.7.1"
|
||||
source = "git+https://github.com/rustdesk-org/hwcodec#398e5a8938dd8768ade0fcdc27ea80e8b4b38738"
|
||||
source = "git+https://github.com/rustdesk-org/hwcodec#778df1f99597722473b29443bac22ae6c23946fe"
|
||||
dependencies = [
|
||||
"bindgen 0.59.2",
|
||||
"cc",
|
||||
@@ -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.52.6",
|
||||
"windows-targets 0.48.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4695,7 +4695,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "magnum-opus"
|
||||
version = "0.4.0"
|
||||
source = "git+https://github.com/rustdesk-org/magnum-opus#5cd2bf989c148662fa3a2d9d539a71d71fd1d256"
|
||||
source = "git+https://github.com/rustdesk-org/magnum-opus#588c6e1f9ed50c3a01fa64f3bd3e7cdb0378a114"
|
||||
dependencies = [
|
||||
"bindgen 0.59.2",
|
||||
"pkg-config",
|
||||
@@ -6673,7 +6673,7 @@ dependencies = [
|
||||
"once_cell",
|
||||
"socket2 0.5.10",
|
||||
"tracing",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.60.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -7270,7 +7270,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustdesk"
|
||||
version = "1.4.7"
|
||||
version = "1.4.8"
|
||||
dependencies = [
|
||||
"android-wakelock",
|
||||
"android_logger",
|
||||
@@ -7385,7 +7385,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustdesk-portable-packer"
|
||||
version = "1.4.7"
|
||||
version = "1.4.8"
|
||||
dependencies = [
|
||||
"brotli",
|
||||
"dirs 5.0.1",
|
||||
@@ -7457,7 +7457,7 @@ dependencies = [
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys 0.11.0",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.60.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -7514,7 +7514,7 @@ dependencies = [
|
||||
"security-framework 3.5.1",
|
||||
"security-framework-sys",
|
||||
"webpki-root-certs",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.60.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "rustdesk"
|
||||
version = "1.4.7"
|
||||
version = "1.4.8"
|
||||
authors = ["rustdesk <info@rustdesk.com>"]
|
||||
edition = "2021"
|
||||
build= "build.rs"
|
||||
|
||||
@@ -18,7 +18,7 @@ AppDir:
|
||||
id: rustdesk
|
||||
name: rustdesk
|
||||
icon: rustdesk
|
||||
version: 1.4.7
|
||||
version: 1.4.8
|
||||
exec: usr/share/rustdesk/rustdesk
|
||||
exec_args: $@
|
||||
apt:
|
||||
|
||||
@@ -18,7 +18,7 @@ AppDir:
|
||||
id: rustdesk
|
||||
name: rustdesk
|
||||
icon: rustdesk
|
||||
version: 1.4.7
|
||||
version: 1.4.8
|
||||
exec: usr/share/rustdesk/rustdesk
|
||||
exec_args: $@
|
||||
apt:
|
||||
|
||||
11
build.py
11
build.py
@@ -17,7 +17,8 @@ osx = platform.platform().startswith(
|
||||
hbb_name = 'rustdesk' + ('.exe' if windows else '')
|
||||
exe_path = 'target/release/' + hbb_name
|
||||
if windows:
|
||||
flutter_build_dir = 'build/windows/x64/runner/Release/'
|
||||
win_arch = 'arm64' if platform.machine().lower() in ('arm64', 'aarch64') else 'x64'
|
||||
flutter_build_dir = f'build/windows/{win_arch}/runner/Release/'
|
||||
elif osx:
|
||||
flutter_build_dir = 'build/macos/Build/Products/Release/'
|
||||
else:
|
||||
@@ -410,7 +411,12 @@ def build_flutter_dmg(version, features):
|
||||
system2(
|
||||
"cp target/release/liblibrustdesk.dylib target/release/librustdesk.dylib")
|
||||
os.chdir('flutter')
|
||||
system2('flutter build macos --release')
|
||||
# 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('cp -rf ../target/release/service ./build/macos/Build/Products/Release/RustDesk.app/Contents/MacOS/')
|
||||
'''
|
||||
system2(
|
||||
@@ -506,6 +512,7 @@ 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')
|
||||
|
||||
7
flutter/assets/display_switcher.svg
Normal file
7
flutter/assets/display_switcher.svg
Normal file
@@ -0,0 +1,7 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
|
||||
<g fill="#000000" fill-rule="evenodd">
|
||||
<rect x="4" y="6" width="24" height="16" rx="3"/>
|
||||
<rect x="14.5" y="22" width="3" height="2"/>
|
||||
<rect x="9.5" y="24" width="13" height="2.5" rx="1.25"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 303 B |
@@ -598,6 +598,22 @@ 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,10 +72,24 @@ Widget waylandKeyboardScopeChip(BuildContext context, String text) {
|
||||
);
|
||||
}
|
||||
|
||||
// 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;
|
||||
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);
|
||||
}
|
||||
|
||||
class TTextMenu {
|
||||
@@ -964,7 +978,8 @@ Future<List<TToggleMenu>> toolbarDisplayToggle(
|
||||
|
||||
final privacyModeState = PrivacyModeState.find(id);
|
||||
if (pi.isSupportMultiDisplay &&
|
||||
(privacyModeState.isEmpty || allowDisplaySwitchInPrivacyMode(pi)) &&
|
||||
(privacyModeState.isEmpty ||
|
||||
allowDisplaySwitchInPrivacyMode(pi, privacyModeState.value)) &&
|
||||
pi.displaysCount.value > 1 &&
|
||||
bind.mainGetUserDefaultOption(key: kKeyShowMonitorsToolbar) == 'Y') {
|
||||
final value =
|
||||
@@ -1048,7 +1063,20 @@ List<TToggleMenu> toolbarPrivacyMode(
|
||||
return []; // No permission and not active, hide options.
|
||||
}
|
||||
|
||||
getDefaultMenu(Future<void> Function(SessionID sid, String opt) toggleFunc) {
|
||||
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) {
|
||||
final enabled = !ffiModel.viewOnly &&
|
||||
(hasPrivacyModePermission || privacyModeState.isNotEmpty);
|
||||
return TToggleMenu(
|
||||
@@ -1056,16 +1084,7 @@ List<TToggleMenu> toolbarPrivacyMode(
|
||||
onChanged: enabled
|
||||
? (value) {
|
||||
if (value == null) return;
|
||||
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);
|
||||
if (!checkDisplayAllowedForPrivacyMode(targetImplKey, value)) {
|
||||
return;
|
||||
}
|
||||
final option = 'privacy-mode';
|
||||
@@ -1083,7 +1102,7 @@ List<TToggleMenu> toolbarPrivacyMode(
|
||||
getDefaultMenu((sid, opt) async {
|
||||
bind.sessionToggleOption(sessionId: sid, value: opt);
|
||||
togglePrivacyModeTime = DateTime.now();
|
||||
})
|
||||
}, kPrivacyModeImplMag)
|
||||
];
|
||||
}
|
||||
if (privacyModeImpls.isEmpty) {
|
||||
@@ -1097,7 +1116,7 @@ List<TToggleMenu> toolbarPrivacyMode(
|
||||
bind.sessionTogglePrivacyMode(
|
||||
sessionId: sid, implKey: implKey, on: privacyModeState.isEmpty);
|
||||
togglePrivacyModeTime = DateTime.now();
|
||||
})
|
||||
}, implKey)
|
||||
];
|
||||
} else {
|
||||
final visibleImpls = hasPrivacyModePermission
|
||||
@@ -1118,6 +1137,9 @@ 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,6 +29,10 @@ 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";
|
||||
@@ -170,6 +174,8 @@ const String kOptionShowVirtualMouse = "show-virtual-mouse";
|
||||
const String kOptionVirtualMouseScale = "virtual-mouse-scale";
|
||||
const String kOptionShowVirtualJoystick = "show-virtual-joystick";
|
||||
const String kOptionAllowAskForNoteAtEndOfConnection = "allow-ask-for-note";
|
||||
const String kOptionAllowMonitorSwitchMainToolbar = "allow-monitor-switch-main-toolbar";
|
||||
const String kOptionAllowMonitorSwitchMinToolbar = "allow-monitor-switch-min-toolbar";
|
||||
const String kOptionEnableShowTerminalExtraKeys = "enable-show-terminal-extra-keys";
|
||||
|
||||
// network options
|
||||
|
||||
@@ -407,6 +407,7 @@ class _GeneralState extends State<_General> {
|
||||
final RxBool serviceStop =
|
||||
isWeb ? RxBool(false) : Get.find<RxBool>(tag: 'stop-service');
|
||||
RxBool serviceBtnEnabled = true.obs;
|
||||
final GlobalKey _minToolbarOptionKey = GlobalKey();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -605,6 +606,47 @@ class _GeneralState extends State<_General> {
|
||||
},
|
||||
));
|
||||
}
|
||||
children.add(_OptionCheckBox(
|
||||
context,
|
||||
'Show monitor switch button on the main toolbar',
|
||||
kOptionAllowMonitorSwitchMainToolbar,
|
||||
isServer: false,
|
||||
update: (enabled) async {
|
||||
if (!enabled) {
|
||||
await mainSetLocalBoolOption(
|
||||
kOptionAllowMonitorSwitchMinToolbar, false);
|
||||
}
|
||||
if (mounted) setState(() {});
|
||||
reloadAllWindows();
|
||||
if (enabled) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
final ctx = _minToolbarOptionKey.currentContext;
|
||||
if (ctx != null) {
|
||||
Scrollable.ensureVisible(
|
||||
ctx,
|
||||
alignment: 0.5,
|
||||
duration: const Duration(milliseconds: 250),
|
||||
curve: Curves.easeInOut,
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
));
|
||||
if (mainGetLocalBoolOptionSync(kOptionAllowMonitorSwitchMainToolbar)) {
|
||||
children.add(KeyedSubtree(
|
||||
key: _minToolbarOptionKey,
|
||||
child: _OptionCheckBox(
|
||||
context,
|
||||
'Show on the minimized toolbar',
|
||||
kOptionAllowMonitorSwitchMinToolbar,
|
||||
isServer: false,
|
||||
update: (_) {
|
||||
reloadAllWindows();
|
||||
},
|
||||
).marginOnly(left: _kCheckBoxLeftMargin * 3),
|
||||
));
|
||||
}
|
||||
return _Card(title: 'Other', children: children);
|
||||
}
|
||||
|
||||
|
||||
@@ -779,6 +779,7 @@ class _RemoteToolbarState extends State<RemoteToolbar> {
|
||||
borderRadius: borderRadius,
|
||||
child: _DraggableShowHide(
|
||||
id: widget.id,
|
||||
ffi: widget.ffi,
|
||||
sessionId: widget.ffi.sessionId,
|
||||
dragging: _dragging,
|
||||
fraction: _fraction,
|
||||
@@ -805,13 +806,25 @@ class _RemoteToolbarState extends State<RemoteToolbar> {
|
||||
BuildContext context, _ToolbarEdge edge, bool isHorizontal) {
|
||||
final List<Widget> toolbarItems = [];
|
||||
toolbarItems.add(_PinMenu(state: widget.state));
|
||||
toolbarItems.add(Obx(() {
|
||||
final privacyModeState = PrivacyModeState.find(widget.id);
|
||||
if ((privacyModeState.isEmpty ||
|
||||
allowDisplaySwitchInPrivacyMode(pi, privacyModeState.value)) &&
|
||||
pi.displaysCount.value > 1 &&
|
||||
mainGetLocalBoolOptionSync(kOptionAllowMonitorSwitchMainToolbar)) {
|
||||
return _MainMonitorSwitchButton(id: widget.id, ffi: widget.ffi);
|
||||
} else {
|
||||
return const Offstage();
|
||||
}
|
||||
}));
|
||||
if (!isWebDesktop) {
|
||||
toolbarItems.add(_MobileActionMenu(ffi: widget.ffi));
|
||||
}
|
||||
|
||||
toolbarItems.add(Obx(() {
|
||||
if ((PrivacyModeState.find(widget.id).isEmpty ||
|
||||
allowDisplaySwitchInPrivacyMode(pi)) &&
|
||||
final privacyModeState = PrivacyModeState.find(widget.id);
|
||||
if ((privacyModeState.isEmpty ||
|
||||
allowDisplaySwitchInPrivacyMode(pi, privacyModeState.value)) &&
|
||||
pi.displaysCount.value > 1) {
|
||||
return _MonitorMenu(
|
||||
id: widget.id,
|
||||
@@ -964,6 +977,88 @@ class _MobileActionMenu extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
class _MonitorCycle {
|
||||
final String id;
|
||||
final FFI ffi;
|
||||
const _MonitorCycle(this.id, this.ffi);
|
||||
|
||||
PeerInfo get _pi => ffi.ffiModel.pi;
|
||||
int get total => _pi.displays.length;
|
||||
int get _current => CurrentDisplayState.find(id).value;
|
||||
bool get _inRange => _current >= 0 && _current < total;
|
||||
|
||||
String get label => _inRange ? '${_current + 1}' : '*';
|
||||
String get tooltip => '${translate('Switch display')} ($label/$total)';
|
||||
|
||||
void next() {
|
||||
final t = total;
|
||||
if (t < 2) return;
|
||||
final from = _inRange ? _current : -1;
|
||||
final target = (from + 1) % t;
|
||||
final isChooseDisplayToOpenInNewWindow = _pi.isSupportMultiDisplay &&
|
||||
bind.sessionGetDisplaysAsIndividualWindows(sessionId: ffi.sessionId) ==
|
||||
'Y';
|
||||
if (isChooseDisplayToOpenInNewWindow) {
|
||||
openMonitorInNewTabOrWindow(target, ffi.id, _pi);
|
||||
} else {
|
||||
openMonitorInTheSameTab(target, ffi, _pi, updateCursorPos: false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class _MainMonitorSwitchButton extends StatelessWidget {
|
||||
final String id;
|
||||
final FFI ffi;
|
||||
|
||||
const _MainMonitorSwitchButton({
|
||||
Key? key,
|
||||
required this.id,
|
||||
required this.ffi,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final cycle = _MonitorCycle(id, ffi);
|
||||
return Obx(() {
|
||||
if (cycle.total < 2) return const Offstage();
|
||||
final label = cycle.label;
|
||||
|
||||
return _IconMenuButton(
|
||||
tooltip: cycle.tooltip,
|
||||
color: _ToolbarTheme.blueColor,
|
||||
hoverColor: _ToolbarTheme.hoverBlueColor,
|
||||
onPressed: cycle.next,
|
||||
icon: SizedBox(
|
||||
width: _ToolbarTheme.buttonSize,
|
||||
height: _ToolbarTheme.buttonSize,
|
||||
child: Stack(
|
||||
alignment: const Alignment(0, -0.125),
|
||||
children: [
|
||||
SvgPicture.asset(
|
||||
'assets/display_switcher.svg',
|
||||
colorFilter:
|
||||
const ColorFilter.mode(Colors.white, BlendMode.srcIn),
|
||||
width: _ToolbarTheme.buttonSize,
|
||||
height: _ToolbarTheme.buttonSize,
|
||||
),
|
||||
Text(
|
||||
label,
|
||||
textAlign: TextAlign.center,
|
||||
style: const TextStyle(
|
||||
color: Colors.black,
|
||||
fontSize: 11,
|
||||
height: 1,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class _MonitorMenu extends StatelessWidget {
|
||||
final String id;
|
||||
final FFI ffi;
|
||||
@@ -2970,6 +3065,7 @@ class RdoMenuButton<T> extends StatelessWidget {
|
||||
|
||||
class _DraggableShowHide extends StatefulWidget {
|
||||
final String id;
|
||||
final FFI ffi;
|
||||
final SessionID sessionId;
|
||||
final RxDouble fraction;
|
||||
final Rx<_ToolbarEdge> edge;
|
||||
@@ -2993,6 +3089,7 @@ class _DraggableShowHide extends StatefulWidget {
|
||||
const _DraggableShowHide({
|
||||
Key? key,
|
||||
required this.id,
|
||||
required this.ffi,
|
||||
required this.sessionId,
|
||||
required this.fraction,
|
||||
required this.edge,
|
||||
@@ -3249,6 +3346,9 @@ class _DraggableShowHideState extends State<_DraggableShowHide> {
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
_buildDraggable(context),
|
||||
Obx(() => collapse.isTrue
|
||||
? _MinimizedMonitorSwitchButton(id: widget.id, ffi: widget.ffi)
|
||||
: const Offstage()),
|
||||
Obx(() => buttonWrapper(
|
||||
() {
|
||||
widget.setFullscreen(!isFullscreen.value);
|
||||
@@ -3409,3 +3509,73 @@ class EdgeThicknessControl extends StatelessWidget {
|
||||
return slider;
|
||||
}
|
||||
}
|
||||
|
||||
class _MinimizedMonitorSwitchButton extends StatelessWidget {
|
||||
final String id;
|
||||
final FFI ffi;
|
||||
|
||||
const _MinimizedMonitorSwitchButton({
|
||||
Key? key,
|
||||
required this.id,
|
||||
required this.ffi,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
const double iconSize = 20;
|
||||
final cycle = _MonitorCycle(id, ffi);
|
||||
|
||||
return Obx(() {
|
||||
final label = cycle.label;
|
||||
if (!mainGetLocalBoolOptionSync(kOptionAllowMonitorSwitchMainToolbar) ||
|
||||
!mainGetLocalBoolOptionSync(kOptionAllowMonitorSwitchMinToolbar)) {
|
||||
return const Offstage();
|
||||
}
|
||||
if (cycle.total < 2) return const Offstage();
|
||||
final privacyModeState = PrivacyModeState.find(id);
|
||||
if (privacyModeState.isNotEmpty &&
|
||||
!allowDisplaySwitchInPrivacyMode(
|
||||
ffi.ffiModel.pi, privacyModeState.value)) {
|
||||
return const Offstage();
|
||||
}
|
||||
|
||||
return Tooltip(
|
||||
message: cycle.tooltip,
|
||||
child: TextButton(
|
||||
onPressed: cycle.next,
|
||||
style: ButtonStyle(
|
||||
minimumSize: MaterialStateProperty.all(const Size(0, 0)),
|
||||
padding: MaterialStateProperty.all(EdgeInsets.zero),
|
||||
backgroundColor: MaterialStateProperty.resolveWith((states) {
|
||||
if (states.contains(MaterialState.hovered)) {
|
||||
return _ToolbarTheme.blueColor.withOpacity(0.15);
|
||||
}
|
||||
return null;
|
||||
}),
|
||||
),
|
||||
child: Stack(
|
||||
alignment: const Alignment(0, -0.125),
|
||||
children: [
|
||||
SvgPicture.asset(
|
||||
'assets/display_switcher.svg',
|
||||
colorFilter:
|
||||
ColorFilter.mode(_ToolbarTheme.blueColor, BlendMode.srcIn),
|
||||
width: iconSize,
|
||||
height: iconSize,
|
||||
),
|
||||
Text(
|
||||
label,
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 9,
|
||||
height: 1,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,6 +29,8 @@ 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';
|
||||
@@ -37,10 +39,15 @@ 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);
|
||||
@@ -383,6 +390,7 @@ void _runApp(
|
||||
builder: (context, child) {
|
||||
child = _keepScaleBuilder(context, child);
|
||||
child = botToastBuilder(context, child);
|
||||
if (_cjkFontLoaded) child = _mergeCjkFallback(context, child);
|
||||
return child;
|
||||
},
|
||||
),
|
||||
@@ -533,6 +541,7 @@ 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);
|
||||
@@ -586,6 +595,19 @@ _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,7 +1220,11 @@ void showOptions(
|
||||
if (image != null) {
|
||||
displays.add(Padding(padding: const EdgeInsets.only(top: 8), child: image));
|
||||
}
|
||||
if (pi.displays.length > 1 && pi.currentDisplay != kAllDisplayValue) {
|
||||
final privacyModeState = PrivacyModeState.find(id);
|
||||
if (pi.displays.length > 1 &&
|
||||
pi.currentDisplay != kAllDisplayValue &&
|
||||
(privacyModeState.isEmpty ||
|
||||
allowDisplaySwitchInPrivacyMode(pi, privacyModeState.value))) {
|
||||
final cur = pi.currentDisplay;
|
||||
final children = <Widget>[];
|
||||
final isDarkTheme = MyTheme.currentThemeMode() == ThemeMode.dark;
|
||||
@@ -1274,8 +1278,6 @@ 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);
|
||||
|
||||
@@ -1307,7 +1307,8 @@ class InputModel {
|
||||
}
|
||||
if (isPhysicalMouse.value) {
|
||||
if (!_relativeMouse.handleRelativeMouseMove(e.localPosition)) {
|
||||
handleMouse(_getMouseEvent(e, _kMouseEventMove), e.position,
|
||||
final canvasPosition = _pointerPositionForRemoteCanvas(e);
|
||||
handleMouse(_getMouseEvent(e, _kMouseEventMove), canvasPosition,
|
||||
edgeScroll: useEdgeScroll);
|
||||
}
|
||||
}
|
||||
@@ -1548,7 +1549,8 @@ class InputModel {
|
||||
_relativeMouse
|
||||
.sendRelativeMouseButton(_getMouseEvent(e, _kMouseEventDown));
|
||||
} else {
|
||||
handleMouse(_getMouseEvent(e, _kMouseEventDown), e.position);
|
||||
final canvasPosition = _pointerPositionForRemoteCanvas(e);
|
||||
handleMouse(_getMouseEvent(e, _kMouseEventDown), canvasPosition);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1570,7 +1572,8 @@ class InputModel {
|
||||
_relativeMouse
|
||||
.sendRelativeMouseButton(_getMouseEvent(e, _kMouseEventUp));
|
||||
} else {
|
||||
handleMouse(_getMouseEvent(e, _kMouseEventUp), e.position);
|
||||
final canvasPosition = _pointerPositionForRemoteCanvas(e);
|
||||
handleMouse(_getMouseEvent(e, _kMouseEventUp), canvasPosition);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1592,12 +1595,40 @@ class InputModel {
|
||||
}
|
||||
if (isPhysicalMouse.value) {
|
||||
if (!_relativeMouse.handleRelativeMouseMove(e.localPosition)) {
|
||||
handleMouse(_getMouseEvent(e, _kMouseEventMove), e.position,
|
||||
final canvasPosition = _pointerPositionForRemoteCanvas(e);
|
||||
handleMouse(_getMouseEvent(e, _kMouseEventMove), canvasPosition,
|
||||
edgeScroll: useEdgeScroll);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert pointer coordinates into the visible remote canvas space.
|
||||
///
|
||||
/// On mobile, the remote page body is wrapped in `SafeArea`, but the pointer
|
||||
/// listener that feeds these events sits outside that subtree. As a result,
|
||||
/// `event.localPosition` still includes the top/left safe-area inset.
|
||||
///
|
||||
/// When the keyboard-visible path shows `KeyHelpTools`, the remote canvas is
|
||||
/// also shifted downward by `CanvasModel.getAdjustY()`. The downstream mouse
|
||||
/// mapping logic expects coordinates relative to the visible canvas area, so
|
||||
/// we subtract both the mobile safe-area padding and the current canvas
|
||||
/// adjustment before passing the position into mouse mapping.
|
||||
///
|
||||
/// Desktop and web desktop continue to use the global position directly
|
||||
/// because their pointer mapping is window-based.
|
||||
Offset _pointerPositionForRemoteCanvas(PointerEvent event) {
|
||||
if (isDesktop || isWebDesktop) {
|
||||
return event.position;
|
||||
}
|
||||
final mediaData = MediaQueryData.fromView(
|
||||
WidgetsBinding.instance.platformDispatcher.views.first);
|
||||
final adjustY = parent.target?.canvasModel.getAdjustY() ?? 0.0;
|
||||
return Offset(
|
||||
event.localPosition.dx - mediaData.padding.left,
|
||||
event.localPosition.dy - mediaData.padding.top - adjustY,
|
||||
);
|
||||
}
|
||||
|
||||
static Future<Rect?> fillRemoteCoordsAndGetCurFrame(
|
||||
List<RemoteWindowCoords> remoteWindowCoords) async {
|
||||
final coords =
|
||||
|
||||
109
flutter/lib/native/font_manager.dart
Normal file
109
flutter/lib/native/font_manager.dart
Normal file
@@ -0,0 +1,109 @@
|
||||
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;
|
||||
}
|
||||
8
flutter/lib/web/font_manager.dart
Normal file
8
flutter/lib/web/font_manager.dart
Normal file
@@ -0,0 +1,8 @@
|
||||
/// 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;
|
||||
@@ -16,7 +16,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev
|
||||
# Read more about iOS versioning at
|
||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||
# 1.1.9-1 works for android, but for ios it becomes 1.1.91, need to set it to 1.1.9-a.1 for iOS, will get 1.1.9.1, but iOS store not allow 4 numbers
|
||||
version: 1.4.7+65
|
||||
version: 1.4.8+66
|
||||
|
||||
environment:
|
||||
sdk: '^3.1.0'
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "rustdesk-portable-packer"
|
||||
version = "1.4.7"
|
||||
version = "1.4.8"
|
||||
edition = "2021"
|
||||
description = "RustDesk Remote Desktop"
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ fn link_vcpkg(mut path: PathBuf, name: &str) -> PathBuf {
|
||||
format!("{}-{}", target_arch, target_os)
|
||||
}
|
||||
} else if target_os == "windows" {
|
||||
"x64-windows-static".to_owned()
|
||||
format!("{}-windows-static", target_arch)
|
||||
} else {
|
||||
format!("{}-{}", target_arch, target_os)
|
||||
};
|
||||
|
||||
@@ -52,6 +52,33 @@ 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;
|
||||
|
||||
@@ -247,6 +274,8 @@ pub struct CapturerMag {
|
||||
rect: RECT,
|
||||
width: usize,
|
||||
height: usize,
|
||||
excluded_window_target: Option<(String, String)>,
|
||||
excluded_windows: Vec<HWND>,
|
||||
}
|
||||
|
||||
impl Drop for CapturerMag {
|
||||
@@ -261,6 +290,10 @@ 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);
|
||||
@@ -305,6 +338,8 @@ impl CapturerMag {
|
||||
},
|
||||
width,
|
||||
height,
|
||||
excluded_window_target: None,
|
||||
excluded_windows: Vec::new(),
|
||||
};
|
||||
|
||||
unsafe {
|
||||
@@ -436,19 +471,41 @@ impl CapturerMag {
|
||||
}
|
||||
|
||||
pub(crate) fn exclude(&mut self, cls: &str, name: &str) -> Result<bool> {
|
||||
let name_c = CString::new(name)?;
|
||||
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 _;
|
||||
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
|
||||
{
|
||||
@@ -456,16 +513,15 @@ impl CapturerMag {
|
||||
== set_window_filter_list_func(
|
||||
self.magnifier_window,
|
||||
MW_FILTERMODE_EXCLUDE,
|
||||
1,
|
||||
&mut hwnd,
|
||||
count,
|
||||
hwnds.as_mut_ptr(),
|
||||
)
|
||||
{
|
||||
return Err(Error::new(
|
||||
ErrorKind::Other,
|
||||
format!(
|
||||
"Failed MagSetWindowFilterList for cls {} name {}, error {}",
|
||||
cls,
|
||||
name,
|
||||
"Failed MagSetWindowFilterList for {} windows, error {}",
|
||||
count,
|
||||
Error::last_os_error()
|
||||
),
|
||||
));
|
||||
@@ -496,6 +552,7 @@ impl CapturerMag {
|
||||
}
|
||||
|
||||
pub(crate) fn frame(&mut self, data: &mut Vec<u8>) -> Result<()> {
|
||||
self.refresh_excluded_windows()?;
|
||||
Self::clear_data();
|
||||
|
||||
unsafe {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
pkgname=rustdesk
|
||||
pkgver=1.4.7
|
||||
pkgver=1.4.8
|
||||
pkgrel=0
|
||||
epoch=
|
||||
pkgdesc=""
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
#! /usr/bin/env bash
|
||||
sed -i "s/$1/$2/g" res/*spec res/PKGBUILD flutter/pubspec.yaml Cargo.toml .github/workflows/*yml flatpak/*json appimage/*yml libs/portable/Cargo.toml
|
||||
sed -i "s/\b$1\b/$2/g" res/*spec res/PKGBUILD flutter/pubspec.yaml Cargo.toml .github/workflows/*yml flatpak/*json appimage/*yml libs/portable/Cargo.toml
|
||||
cargo run # to bump version in cargo lock
|
||||
|
||||
@@ -7,6 +7,10 @@
|
||||
<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>
|
||||
@@ -22,6 +26,12 @@
|
||||
<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>
|
||||
@@ -30,6 +40,9 @@
|
||||
<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>
|
||||
@@ -53,6 +66,28 @@
|
||||
<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" />
|
||||
@@ -65,6 +100,7 @@
|
||||
<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</Platforms>
|
||||
<Platforms>x64;ARM64</Platforms>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Content Include="Includes.wxi" />
|
||||
|
||||
@@ -10,12 +10,17 @@ 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
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
Name: rustdesk
|
||||
Version: 1.4.7
|
||||
Version: 1.4.8
|
||||
Release: 0
|
||||
Summary: RPM package
|
||||
License: GPL-3.0
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
Name: rustdesk
|
||||
Version: 1.4.7
|
||||
Version: 1.4.8
|
||||
Release: 0
|
||||
Summary: RPM package
|
||||
License: GPL-3.0
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
Name: rustdesk
|
||||
Version: 1.4.7
|
||||
Version: 1.4.8
|
||||
Release: 0
|
||||
Summary: RPM package
|
||||
License: GPL-3.0
|
||||
|
||||
@@ -130,14 +130,18 @@ elseif(VCPKG_TARGET_IS_WINDOWS)
|
||||
--cc=cl \
|
||||
--enable-gpl \
|
||||
--enable-d3d11va \
|
||||
--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 \
|
||||
")
|
||||
|
||||
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-amf \
|
||||
--enable-encoder=h264_amf \
|
||||
--enable-encoder=hevc_amf \
|
||||
@@ -147,6 +151,7 @@ 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)
|
||||
@@ -154,6 +159,9 @@ 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()
|
||||
|
||||
@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("wayland-keyboard-input-reset-choice-tip", "إعادة تعيين اختيار إدخال لوحة المفاتيح"),
|
||||
("remember-wayland-keyboard-choice-tip", "لا تسأل مرة أخرى لهذا الكمبيوتر البعيد"),
|
||||
("Why this happens", "سبب حدوث ذلك"),
|
||||
("Switch display", "تبديل الشاشة"),
|
||||
("Show monitor switch button on the main toolbar", "إظهار زر تبديل الشاشة على شريط الأدوات الرئيسي"),
|
||||
("Show on the minimized toolbar", "الإظهار على شريط الأدوات المُصغّر"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("wayland-keyboard-input-reset-choice-tip", "Скінуць выбар уводу з клавіятуры"),
|
||||
("remember-wayland-keyboard-choice-tip", "Не пытацца зноў для гэтага аддаленага кампутара"),
|
||||
("Why this happens", "Чаму гэта адбываецца"),
|
||||
("Switch display", "Пераключыць дысплэй"),
|
||||
("Show monitor switch button on the main toolbar", "Паказваць кнопку пераключэння манітора на галоўнай панэлі інструментаў"),
|
||||
("Show on the minimized toolbar", "Паказваць на згорнутай панэлі інструментаў"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("wayland-keyboard-input-reset-choice-tip", "Нулиране на избора за въвеждане от клавиатура"),
|
||||
("remember-wayland-keyboard-choice-tip", "Не питай отново за този отдалечен компютър"),
|
||||
("Why this happens", "Защо се случва това"),
|
||||
("Switch display", "Превключване на дисплея"),
|
||||
("Show monitor switch button on the main toolbar", "Показване на бутона за превключване на монитора в главната лента с инструменти"),
|
||||
("Show on the minimized toolbar", "Показване в минимизираната лента с инструменти"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("wayland-keyboard-input-reset-choice-tip", "Restableix l'opció d'entrada de teclat"),
|
||||
("remember-wayland-keyboard-choice-tip", "No tornis a preguntar-ho per a aquest equip remot"),
|
||||
("Why this happens", "Per què passa això"),
|
||||
("Switch display", "Canvia de pantalla"),
|
||||
("Show monitor switch button on the main toolbar", "Mostra el botó de canvi de monitor a la barra d’eines principal"),
|
||||
("Show on the minimized toolbar", "Mostra a la barra d’eines minimitzada"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("wayland-keyboard-input-reset-choice-tip", "重置键盘输入选择"),
|
||||
("remember-wayland-keyboard-choice-tip", "以后对这台远程电脑不再询问"),
|
||||
("Why this happens", "了解原因"),
|
||||
("Switch display", "切换显示器"),
|
||||
("Show monitor switch button on the main toolbar", "在主工具栏上显示显示器切换按钮"),
|
||||
("Show on the minimized toolbar", "在最小化工具栏上显示"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("wayland-keyboard-input-reset-choice-tip", "Resetovat volbu vstupu z klávesnice"),
|
||||
("remember-wayland-keyboard-choice-tip", "Pro tento vzdálený počítač se již neptat"),
|
||||
("Why this happens", "Proč k tomu dochází"),
|
||||
("Switch display", "Přepnout obrazovku"),
|
||||
("Show monitor switch button on the main toolbar", "Zobrazit tlačítko přepnutí monitoru na hlavním panelu nástrojů"),
|
||||
("Show on the minimized toolbar", "Zobrazit na minimalizovaném panelu nástrojů"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("wayland-keyboard-input-reset-choice-tip", "Nulstil valg for tastaturinput"),
|
||||
("remember-wayland-keyboard-choice-tip", "Spørg ikke igen for denne fjerncomputer"),
|
||||
("Why this happens", "Hvorfor dette sker"),
|
||||
("Switch display", "Skift skærm"),
|
||||
("Show monitor switch button on the main toolbar", "Vis knap til skærmskift på hovedværktøjslinjen"),
|
||||
("Show on the minimized toolbar", "Vis på den minimerede værktøjslinje"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("wayland-keyboard-input-reset-choice-tip", "Auswahl der Tastatureingabe zurücksetzen"),
|
||||
("remember-wayland-keyboard-choice-tip", "Für diesen entfernten Computer nicht erneut fragen"),
|
||||
("Why this happens", "Warum dies passiert"),
|
||||
("Switch display", "Anzeige wechseln"),
|
||||
("Show monitor switch button on the main toolbar", "Schaltfläche zum Monitorwechsel in der Haupt-Symbolleiste anzeigen"),
|
||||
("Show on the minimized toolbar", "In der minimierten Symbolleiste anzeigen"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("wayland-keyboard-input-reset-choice-tip", "Επαναφορά επιλογής εισαγωγής από πληκτρολόγιο"),
|
||||
("remember-wayland-keyboard-choice-tip", "Να μην ερωτηθώ ξανά για αυτόν τον απομακρυσμένο υπολογιστή"),
|
||||
("Why this happens", "Γιατί συμβαίνει αυτό"),
|
||||
("Switch display", "Εναλλαγή οθόνης"),
|
||||
("Show monitor switch button on the main toolbar", "Εμφάνιση κουμπιού εναλλαγής οθόνης στην κύρια γραμμή εργαλείων"),
|
||||
("Show on the minimized toolbar", "Εμφάνιση στην ελαχιστοποιημένη γραμμή εργαλείων"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("wayland-keyboard-input-reset-choice-tip", "Restarigi la elekton de klavara enigo"),
|
||||
("remember-wayland-keyboard-choice-tip", "Ne demandi denove por ĉi tiu fora komputilo"),
|
||||
("Why this happens", "Kial ĉi tio okazas"),
|
||||
("Switch display", "Ŝalti ekranon"),
|
||||
("Show monitor switch button on the main toolbar", "Montri ekran-ŝaltan butonon en la ĉefa ilobreto"),
|
||||
("Show on the minimized toolbar", "Montri en la minimumigita ilobreto"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("wayland-keyboard-input-reset-choice-tip", "Restablecer la opción de entrada del teclado"),
|
||||
("remember-wayland-keyboard-choice-tip", "No volver a preguntar para este equipo remoto"),
|
||||
("Why this happens", "Por qué ocurre esto"),
|
||||
("Switch display", "Cambiar de pantalla"),
|
||||
("Show monitor switch button on the main toolbar", "Mostrar el botón de cambio de monitor en la barra de herramientas principal"),
|
||||
("Show on the minimized toolbar", "Mostrar en la barra de herramientas minimizada"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("wayland-keyboard-input-reset-choice-tip", "Lähtesta klaviatuurisisestuse valik"),
|
||||
("remember-wayland-keyboard-choice-tip", "Ära küsi selle kaugarvuti puhul uuesti"),
|
||||
("Why this happens", "Miks see juhtub"),
|
||||
("Switch display", "Vaheta kuva"),
|
||||
("Show monitor switch button on the main toolbar", "Näita monitori vahetamise nuppu peamisel tööriistaribal"),
|
||||
("Show on the minimized toolbar", "Näita minimeeritud tööriistaribal"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("wayland-keyboard-input-reset-choice-tip", "Berrezarri teklatuko sarreraren aukera"),
|
||||
("remember-wayland-keyboard-choice-tip", "Ez galdetu berriro urruneko ordenagailu honetarako"),
|
||||
("Why this happens", "Zergatik gertatzen den hau"),
|
||||
("Switch display", "Aldatu pantaila"),
|
||||
("Show monitor switch button on the main toolbar", "Erakutsi monitorea aldatzeko botoia tresna-barra nagusian"),
|
||||
("Show on the minimized toolbar", "Erakutsi minimizatutako tresna-barran"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("wayland-keyboard-input-reset-choice-tip", "بازنشانی انتخاب ورودی صفحه کلید"),
|
||||
("remember-wayland-keyboard-choice-tip", "برای این رایانه از راه دور دوباره نپرس"),
|
||||
("Why this happens", "چرا این اتفاق میافتد"),
|
||||
("Switch display", "تعویض نمایشگر"),
|
||||
("Show monitor switch button on the main toolbar", "نمایش دکمه تعویض نمایشگر در نوار ابزار اصلی"),
|
||||
("Show on the minimized toolbar", "نمایش در نوار ابزار کوچکشده"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("wayland-keyboard-input-reset-choice-tip", "Nollaa näppäimistösyötteen valinta"),
|
||||
("remember-wayland-keyboard-choice-tip", "Älä kysy uudelleen tältä etätietokoneelta"),
|
||||
("Why this happens", "Miksi näin tapahtuu"),
|
||||
("Switch display", "Vaihda näyttöä"),
|
||||
("Show monitor switch button on the main toolbar", "Näytä näytön vaihtopainike päätyökalurivillä"),
|
||||
("Show on the minimized toolbar", "Näytä pienennetyssä työkalurivissä"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("wayland-keyboard-input-reset-choice-tip", "Réinitialiser le choix de la saisie au clavier"),
|
||||
("remember-wayland-keyboard-choice-tip", "Ne plus demander pour cet appareil distant"),
|
||||
("Why this happens", "Pourquoi cela se produit"),
|
||||
("Switch display", "Changer d’écran"),
|
||||
("Show monitor switch button on the main toolbar", "Afficher le bouton de changement d’écran dans la barre d’outils principale"),
|
||||
("Show on the minimized toolbar", "Afficher dans la barre d’outils réduite"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("wayland-keyboard-input-reset-choice-tip", "კლავიატურის შეყვანის არჩევანის ჩამოყრა"),
|
||||
("remember-wayland-keyboard-choice-tip", "აღარ მკითხო ამ დისტანციური კომპიუტერისთვის"),
|
||||
("Why this happens", "რატომ ხდება ეს"),
|
||||
("Switch display", "ეკრანის გადართვა"),
|
||||
("Show monitor switch button on the main toolbar", "მონიტორის გადართვის ღილაკის ჩვენება მთავარ ხელსაწყოთა ზოლზე"),
|
||||
("Show on the minimized toolbar", "ჩვენება ჩაკეცილ ხელსაწყოთა ზოლზე"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("wayland-keyboard-input-reset-choice-tip", "કીબોર્ડ ઇનપુટ પસંદગી રિસેટ કરો"),
|
||||
("remember-wayland-keyboard-choice-tip", "આ રિમોટ કમ્પ્યુટર માટે ફરીથી પૂછશો નહીં"),
|
||||
("Why this happens", "આવું શા માટે થાય છે"),
|
||||
("Switch display", "ડિસ્પ્લે બદલો"),
|
||||
("Show monitor switch button on the main toolbar", "મુખ્ય ટૂલબાર પર મોનિટર સ્વિચ બટન બતાવો"),
|
||||
("Show on the minimized toolbar", "ન્યૂનતમ કરેલા ટૂલબાર પર બતાવો"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("wayland-keyboard-input-reset-choice-tip", "אפס את בחירת קלט המקלדת"),
|
||||
("remember-wayland-keyboard-choice-tip", "אל תשאל שוב עבור מחשב מרוחק זה"),
|
||||
("Why this happens", "מדוע זה קורה"),
|
||||
("Switch display", "החלפת צג"),
|
||||
("Show monitor switch button on the main toolbar", "הצגת לחצן החלפת צג בסרגל הכלים הראשי"),
|
||||
("Show on the minimized toolbar", "הצגה בסרגל הכלים הממוזער"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("wayland-keyboard-input-reset-choice-tip", "कीबोर्ड इनपुट चयन रीसेट करें"),
|
||||
("remember-wayland-keyboard-choice-tip", "इस रिमोट कंप्यूटर के लिए दोबारा न पूछें"),
|
||||
("Why this happens", "ऐसा क्यों होता है"),
|
||||
("Switch display", "डिस्प्ले बदलें"),
|
||||
("Show monitor switch button on the main toolbar", "मुख्य टूलबार पर मॉनिटर स्विच बटन दिखाएं"),
|
||||
("Show on the minimized toolbar", "न्यूनतम किए गए टूलबार पर दिखाएं"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("wayland-keyboard-input-reset-choice-tip", "Poništi izbor unosa tipkovnicom"),
|
||||
("remember-wayland-keyboard-choice-tip", "Ne pitaj ponovno za ovo udaljeno računalo"),
|
||||
("Why this happens", "Zašto se ovo događa"),
|
||||
("Switch display", "Promijeni zaslon"),
|
||||
("Show monitor switch button on the main toolbar", "Prikaži gumb za prebacivanje monitora na glavnoj alatnoj traci"),
|
||||
("Show on the minimized toolbar", "Prikaži na minimiziranoj alatnoj traci"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("wayland-keyboard-input-reset-choice-tip", "Billentyűzetbevitel választásának visszaállítása"),
|
||||
("remember-wayland-keyboard-choice-tip", "Ne kérdezze meg újra ennél a távoli számítógépnél"),
|
||||
("Why this happens", "Miért történik ez"),
|
||||
("Switch display", "Kijelző váltása"),
|
||||
("Show monitor switch button on the main toolbar", "Monitorváltó gomb megjelenítése a fő eszköztáron"),
|
||||
("Show on the minimized toolbar", "Megjelenítés a kis méretű eszköztáron"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("wayland-keyboard-input-reset-choice-tip", "Setel ulang pilihan masukan keyboard"),
|
||||
("remember-wayland-keyboard-choice-tip", "Jangan tanya lagi untuk komputer jarak jauh ini"),
|
||||
("Why this happens", "Mengapa ini terjadi"),
|
||||
("Switch display", "Ganti tampilan"),
|
||||
("Show monitor switch button on the main toolbar", "Tampilkan tombol pengalih monitor di bilah alat utama"),
|
||||
("Show on the minimized toolbar", "Tampilkan di bilah alat yang diperkecil"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("wayland-keyboard-input-reset-choice-tip", "Ripristina scelta input da tastiera"),
|
||||
("remember-wayland-keyboard-choice-tip", "Non chiedere più per questo computer remoto"),
|
||||
("Why this happens", "Perché accade questo"),
|
||||
("Switch display", "Cambia schermo"),
|
||||
("Show monitor switch button on the main toolbar", "Mostra il pulsante di cambio monitor nella barra degli strumenti principale"),
|
||||
("Show on the minimized toolbar", "Mostra nella barra degli strumenti ridotta a icona"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -197,9 +197,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Please enter the folder name", "フォルダー名を入力してください"),
|
||||
("Fix it", "修復する"),
|
||||
("Warning", "警告"),
|
||||
("Login screen using Wayland is not supported", "Wayland を使用したログインスクリーンはサポートされていません"),
|
||||
("Login screen using Wayland is not supported", "Wayland を使用したログイン画面は対応していません"),
|
||||
("Reboot required", "再起動が必要です"),
|
||||
("Unsupported display server", "サポートされていないディスプレイサーバー"),
|
||||
("Unsupported display server", "非対応のディスプレイサーバー"),
|
||||
("x11 expected", "X11 が必要です"),
|
||||
("Port", "ポート"),
|
||||
("Settings", "設定"),
|
||||
@@ -268,11 +268,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Share screen", "画面を共有"),
|
||||
("Chat", "チャット"),
|
||||
("Total", "合計"),
|
||||
("items", "個のアイテム"),
|
||||
("items", "個の項目"),
|
||||
("Selected", "選択済み"),
|
||||
("Screen Capture", "画面キャプチャ"),
|
||||
("Screen Capture", "画面をキャプチャ"),
|
||||
("Input Control", "入力操作"),
|
||||
("Audio Capture", "音声キャプチャ"),
|
||||
("Audio Capture", "オーディオをキャプチャ"),
|
||||
("Do you accept?", "許可しますか?"),
|
||||
("Open System Setting", "システム設定を開く"),
|
||||
("How to get Android input permission?", "Android の入力権限を取得するには?"),
|
||||
@@ -281,7 +281,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("android_new_connection_tip", "新しい操作リクエストが届きました。この端末を操作しようとしています。"),
|
||||
("android_service_will_start_tip", "「画面キャプチャ」を有効にするとサービスが自動的に開始され、他の端末がこの端末への接続をリクエストできるようになります。"),
|
||||
("android_stop_service_tip", "サービスを停止すると、自動的に現在のセッションがすべて閉じられます。"),
|
||||
("android_version_audio_tip", "現在の Android バージョンでは音声キャプチャはサポートされていません。Android 10 以降に更新してください。"),
|
||||
("android_version_audio_tip", "使用している Android はオーディオキャプチャに対応していません。Android 10 以降に更新してください。"),
|
||||
("android_start_service_tip", "「サービスを開始」をタップするか、「画面キャプチャ」の許可を有効にすると、画面共有サービスが開始されます。"),
|
||||
("android_permission_may_not_change_tip", "権限の変更は現在のセッションには適用されません。再接続後に適用されます。"),
|
||||
("Account", "アカウント"),
|
||||
@@ -292,7 +292,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Failed", "失敗"),
|
||||
("Succeeded", "成功"),
|
||||
("Someone turns on privacy mode, exit", "プライバシーモードがオンになりました。終了します。"),
|
||||
("Unsupported", "サポートされていません"),
|
||||
("Unsupported", "対応していません"),
|
||||
("Peer denied", "リモートホストに拒否されました"),
|
||||
("Please install plugins", "プラグインをインストールしてください"),
|
||||
("Peer exit", "リモートホストが退出しました"),
|
||||
@@ -376,10 +376,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Confirm before closing multiple tabs", "複数のタブを閉じる前に確認する"),
|
||||
("Keyboard Settings", "キーボードの設定"),
|
||||
("Full Access", "フルアクセス"),
|
||||
("Screen Share", "画面共有"),
|
||||
("Screen Share", "画面を共有"),
|
||||
("ubuntu-21-04-required", "Wayland を使用するには、Ubuntu 21.04 以降のバージョンが必要です。"),
|
||||
("wayland-requires-higher-linux-version", "Wayland を使用するには、より新しい Linux ディストリビューションが必要です。 X11 デスクトップを試すか、OS を変更してください。"),
|
||||
("xdp-portal-unavailable", "Wayland の画面キャプチャに失敗しました。XDG Desktop Portal がクラッシュしたか、利用できない可能性があります。`systemctl --user restart xdg-desktop-portal` で再起動してみてください。"),
|
||||
("xdp-portal-unavailable", "Wayland の画面キャプチャに失敗しました。XDG デスクトップポータルがクラッシュしたか、利用できない可能性があります。`systemctl --user restart xdg-desktop-portal` で再起動してみてください。"),
|
||||
("JumpLink", "表示"),
|
||||
("Please Select the screen to be shared(Operate on the peer side).", "共有する画面を選択してください(リモートコンピューターが操作します)"),
|
||||
("Show RustDesk", "RustDesk を表示"),
|
||||
@@ -397,7 +397,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Request access to your device", "デバイスへのアクセス要求"),
|
||||
("Hide connection management window", "接続管理画面を隠す"),
|
||||
("hide_cm_tip", "パスワードによるセッションを許可し、固定パスワードを使用する場合にのみ、管理画面の非表示を許可する。"),
|
||||
("wayland_experiment_tip", "Wayland のサポートは試験的なものです。無人アクセスを使用する場合はX11デスクトップをご利用ください。"),
|
||||
("wayland_experiment_tip", "Wayland の対応は試験的なものです。無人アクセスを使用する場合はX11デスクトップをご利用ください。"),
|
||||
("Right click to select tabs", "右クリックでタブを選択"),
|
||||
("Skipped", "スキップ"),
|
||||
("Add to address book", "アドレス帳に追加"),
|
||||
@@ -568,7 +568,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("input_source_1_tip", "入力ソース 1"),
|
||||
("input_source_2_tip", "入力ソース 2"),
|
||||
("Swap control-command key", "ctrl と command キーを入れ替える"),
|
||||
("swap-left-right-mouse", "マウスのクリックを入れ替える"),
|
||||
("swap-left-right-mouse", "マウスクリックを入れ替える"),
|
||||
("2FA code", "二要素認証コード"),
|
||||
("More", "詳細"),
|
||||
("enable-2fa-title", "二要素認証を有効化する"),
|
||||
@@ -601,10 +601,10 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("default_proxy_tip", "既定のプロトコルとポートは Socks5 と 1080 です。"),
|
||||
("no_audio_input_device_tip", "オーディオ入力デバイスが見つかりません。"),
|
||||
("Incoming", "受信"),
|
||||
("Outgoing", "発信"),
|
||||
("Clear Wayland screen selection", "Wayland の画面選択をクリア"),
|
||||
("clear_Wayland_screen_selection_tip", "画面選択をクリア後、共有画面を再び選択できます。"),
|
||||
("confirm_clear_Wayland_screen_selection_tip", "本当に Wayland の画面選択をクリアしますか?"),
|
||||
("Outgoing", "送信"),
|
||||
("Clear Wayland screen selection", "Wayland の画面選択を消去"),
|
||||
("clear_Wayland_screen_selection_tip", "画面選択を消去後、共有画面を再び選択できます。"),
|
||||
("confirm_clear_Wayland_screen_selection_tip", "本当に Wayland の画面選択を消去しますか?"),
|
||||
("android_new_voice_call_tip", "新しい音声通話リクエストを受信しました。承認すると音声通話に切り替わります。"),
|
||||
("texture_render_tip", "テクスチャレンダリングを使用し、画像をより滑らかに描画します。レンダリングの問題が発生した場合は無効にしてみてください。"),
|
||||
("Use texture rendering", "テクスチャレンダリングを使用する"),
|
||||
@@ -643,7 +643,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("one-way-file-transfer-tip", "コントロールをされる側では一方向のファイル転送が有効になります。"),
|
||||
("Authentication Required", "認証が必要です"),
|
||||
("Authenticate", "認証"),
|
||||
("web_id_input_tip", "同じサーバー内の ID を入力できます。Web クライアントでは直接 IP アドレスによるアクセスはサポートされていません。\n別のサーバー上のデバイスにアクセスする場合は、サーバーアドレス (<id>@<server_address>?key=<key_value>) を入力してください。\n 例: 9123456234@192.168.16.1:21117?key=5Qbwsde3unUcJBtrx9ZkvUmwFNoExHzpryHuPUdqlWM=\nパブリックサーバー上のデバイスにアクセスする場合は、「<id>@public」と入力してください。パブリックサーバーはキーは不要です。"),
|
||||
("web_id_input_tip", "同じサーバー内の ID を入力できます。Web クライアントでは IP アドレスによる直接アクセスに対応していません。\n別のサーバー上のデバイスにアクセスする場合は、サーバーアドレス (<id>@<server_address>?key=<key_value>) を入力してください。\n 例: 9123456234@192.168.16.1:21117?key=5Qbwsde3unUcJBtrx9ZkvUmwFNoExHzpryHuPUdqlWM=\nパブリックサーバー上のデバイスにアクセスする場合は、「<id>@public」と入力してください。パブリックサーバーはキーは不要です。"),
|
||||
("Download", "ダウンロード"),
|
||||
("Upload folder", "フォルダーをアップロード"),
|
||||
("Upload files", "ファイルをアップロード"),
|
||||
@@ -674,7 +674,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("dont-show-again-tip", "今後は表示しない"),
|
||||
("Take screenshot", "スクリーンショットを撮影"),
|
||||
("Taking screenshot", "スクリーンショットを撮影中"),
|
||||
("screenshot-merged-screen-not-supported-tip", "複数のディスプレイのスクリーンショットの結合は、現在サポートされていません。単一のディスプレイに切り替えてもう一度お試しください。"),
|
||||
("screenshot-merged-screen-not-supported-tip", "複数のディスプレイのスクリーンショットの結合は、現在非対応です。単一のディスプレイに切り替えてもう一度お試しください。"),
|
||||
("screenshot-action-tip", "スクリーンショットを続行する方法を選択してください。"),
|
||||
("Save as", "保存先"),
|
||||
("Copy to clipboard", "クリップボードにコピー"),
|
||||
@@ -685,7 +685,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("download-new-version-failed-tip", "ダウンロードに失敗しました。もう一度お試しいただくか、「ダウンロード」ボタンをクリックしてリリースページからダウンロードし、手動でアップグレードしてください。"),
|
||||
("Auto update", "ソフトウェアの自動更新を行う"),
|
||||
("update-failed-check-msi-tip", "インストール方法の確認に失敗しました。「ダウンロード」ボタンをクリックしてリリースページからダウンロードし、手動でアップグレードしてください。"),
|
||||
("websocket_tip", "WebSocket を使用する場合、リレー接続のみがサポートされます。"),
|
||||
("websocket_tip", "WebSocket を使用する場合、リレー接続のみ対応しています。"),
|
||||
("Use WebSocket", "WebSocket を使用する"),
|
||||
("Trackpad speed", "トラックパッドの速度"),
|
||||
("Default trackpad speed", "既定のトラックパッドの速度"),
|
||||
@@ -695,7 +695,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("View camera", "カメラを表示"),
|
||||
("Enable camera", "カメラを有効化する"),
|
||||
("No cameras", "カメラなし"),
|
||||
("view_camera_unsupported_tip", "リモートデバイスはカメラの表示をサポートしていません。"),
|
||||
("view_camera_unsupported_tip", "リモートデバイスはカメラの表示に対応していません"),
|
||||
("Terminal", "ターミナル"),
|
||||
("Enable terminal", "ターミナルを有効化する"),
|
||||
("New tab", "新しいタブ"),
|
||||
@@ -706,7 +706,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Incorrect username or password.", "ユーザー名またはパスワードが正しくありません。"),
|
||||
("The user is not an administrator.", "このユーザーは管理者ではありません。"),
|
||||
("Failed to check if the user is an administrator.", "ユーザーが管理者であるかどうかを確認できませんでした。"),
|
||||
("Supported only in the installed version.", "インストールされたバージョンでのみサポートされます。"),
|
||||
("Supported only in the installed version.", "インストールされたバージョンでのみ対応しています。"),
|
||||
("elevation_username_tip", "ユーザー名またはドメインのユーザー名を入力してください。"),
|
||||
("Preparing for installation ...", "インストールの準備中です..."),
|
||||
("Show my cursor", "自分のカーソルを表示する"),
|
||||
@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("wayland-keyboard-input-reset-choice-tip", "キーボード入力の選択をリセット"),
|
||||
("remember-wayland-keyboard-choice-tip", "このリモートコンピューターでは今後確認しない"),
|
||||
("Why this happens", "この問題が起こる理由"),
|
||||
("Switch display", "ディスプレイを切り替え"),
|
||||
("Show monitor switch button on the main toolbar", "メインツールバーにモニター切り替えボタンを表示"),
|
||||
("Show on the minimized toolbar", "最小化したツールバーに表示"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("wayland-keyboard-input-reset-choice-tip", "키보드 입력 선택 초기화"),
|
||||
("remember-wayland-keyboard-choice-tip", "이 원격 컴퓨터에 대해 다시 묻지 않기"),
|
||||
("Why this happens", "이런 현상이 발생하는 이유"),
|
||||
("Switch display", "디스플레이 전환"),
|
||||
("Show monitor switch button on the main toolbar", "기본 도구 모음에 모니터 전환 버튼 표시"),
|
||||
("Show on the minimized toolbar", "최소화된 도구 모음에 표시"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("wayland-keyboard-input-reset-choice-tip", "Пернетақта еңгізу таңдауын қалпына келтіру"),
|
||||
("remember-wayland-keyboard-choice-tip", "Осы қашықтағы компьютер үшін қайта сұрамау"),
|
||||
("Why this happens", "Бұл неге болады"),
|
||||
("Switch display", "Дисплейді ауыстыру"),
|
||||
("Show monitor switch button on the main toolbar", "Негізгі құралдар тақтасында мониторды ауыстыру түймесін көрсету"),
|
||||
("Show on the minimized toolbar", "Кішірейтілген құралдар тақтасында көрсету"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("wayland-keyboard-input-reset-choice-tip", "Atstatyti klaviatūros įvesties pasirinkimą"),
|
||||
("remember-wayland-keyboard-choice-tip", "Daugiau neklausti dėl šio nuotolinio kompiuterio"),
|
||||
("Why this happens", "Kodėl taip nutinka"),
|
||||
("Switch display", "Perjungti ekraną"),
|
||||
("Show monitor switch button on the main toolbar", "Rodyti monitoriaus perjungimo mygtuką pagrindinėje įrankių juostoje"),
|
||||
("Show on the minimized toolbar", "Rodyti sumažintoje įrankių juostoje"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("wayland-keyboard-input-reset-choice-tip", "Atiestatīt tastatūras ievades izvēli"),
|
||||
("remember-wayland-keyboard-choice-tip", "Vairs nejautāt par šo attālo datoru"),
|
||||
("Why this happens", "Kāpēc tas notiek"),
|
||||
("Switch display", "Pārslēgt displeju"),
|
||||
("Show monitor switch button on the main toolbar", "Rādīt monitora pārslēgšanas pogu galvenajā rīkjoslā"),
|
||||
("Show on the minimized toolbar", "Rādīt minimizētajā rīkjoslā"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("wayland-keyboard-input-reset-choice-tip", "കീബോർഡ് ഇൻപുട്ട് തിരഞ്ഞെടുപ്പ് റീസെറ്റ് ചെയ്യുക"),
|
||||
("remember-wayland-keyboard-choice-tip", "ഈ റിമോട്ട് കമ്പ്യൂട്ടറിനായി ഇനി ചോദിക്കരുത്"),
|
||||
("Why this happens", "ഇത് എന്തുകൊണ്ട് സംഭവിക്കുന്നു"),
|
||||
("Switch display", "ഡിസ്പ്ലേ മാറ്റുക"),
|
||||
("Show monitor switch button on the main toolbar", "പ്രധാന ടൂൾബാറിൽ മോണിറ്റർ സ്വിച്ച് ബട്ടൺ കാണിക്കുക"),
|
||||
("Show on the minimized toolbar", "ചെറുതാക്കിയ ടൂൾബാറിൽ കാണിക്കുക"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("wayland-keyboard-input-reset-choice-tip", "Tilbakestill valg for tastaturinndata"),
|
||||
("remember-wayland-keyboard-choice-tip", "Ikke spør igjen for denne eksterne datamaskinen"),
|
||||
("Why this happens", "Hvorfor dette skjer"),
|
||||
("Switch display", "Bytt skjerm"),
|
||||
("Show monitor switch button on the main toolbar", "Vis knapp for skjermbytte på hovedverktøylinjen"),
|
||||
("Show on the minimized toolbar", "Vis på den minimerte verktøylinjen"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("wayland-keyboard-input-reset-choice-tip", "Keuze voor toetsenbordinvoer opnieuw instellen"),
|
||||
("remember-wayland-keyboard-choice-tip", "Niet meer vragen voor deze externe computer"),
|
||||
("Why this happens", "Waarom dit gebeurt"),
|
||||
("Switch display", "Beeldscherm wisselen"),
|
||||
("Show monitor switch button on the main toolbar", "Knop voor monitorwisseling weergeven op de hoofdwerkbalk"),
|
||||
("Show on the minimized toolbar", "Weergeven op de geminimaliseerde werkbalk"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("wayland-keyboard-input-reset-choice-tip", "Zresetuj wybór dotyczący wprowadzania z klawiatury"),
|
||||
("remember-wayland-keyboard-choice-tip", "Nie pytaj ponownie dla tego zdalnego komputera"),
|
||||
("Why this happens", "Dlaczego tak się dzieje"),
|
||||
("Switch display", "Przełącz ekran"),
|
||||
("Show monitor switch button on the main toolbar", "Pokaż przycisk przełączania monitora na głównym pasku narzędzi"),
|
||||
("Show on the minimized toolbar", "Pokaż na zminimalizowanym pasku narzędzi"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("wayland-keyboard-input-reset-choice-tip", "Repor escolha de entrada de teclado"),
|
||||
("remember-wayland-keyboard-choice-tip", "Não voltar a perguntar para este computador remoto"),
|
||||
("Why this happens", "Porque é que isto acontece"),
|
||||
("Switch display", "Trocar de ecrã"),
|
||||
("Show monitor switch button on the main toolbar", "Mostrar o botão de troca de monitor na barra de ferramentas principal"),
|
||||
("Show on the minimized toolbar", "Mostrar na barra de ferramentas minimizada"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -24,9 +24,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Refresh random password", "Atualizar senha aleatória"),
|
||||
("Set your own password", "Configure sua própria senha"),
|
||||
("Enable keyboard/mouse", "Habilitar teclado/mouse"),
|
||||
("Enable clipboard", "Habilitar Área de Transferência"),
|
||||
("Enable file transfer", "Habilitar Transferência de Arquivos"),
|
||||
("Enable TCP tunneling", "Habilitar Tunelamento TCP"),
|
||||
("Enable clipboard", "Habilitar área de transferência"),
|
||||
("Enable file transfer", "Habilitar transferência de arquivos"),
|
||||
("Enable TCP tunneling", "Habilitar tunelamento TCP"),
|
||||
("IP Whitelisting", "Lista de IPs Confiáveis"),
|
||||
("ID/Relay Server", "Servidor ID/Relay"),
|
||||
("Import server config", "Importar Configuração do Servidor"),
|
||||
@@ -693,11 +693,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Enable IPv6 P2P connection", "Habilitar conexão IPv6 P2P"),
|
||||
("Enable UDP hole punching", "Habilitar UDP hole punching"),
|
||||
("View camera", "Visualizar câmera"),
|
||||
("Enable camera", "Ativar câmera"),
|
||||
("No cameras", "Sem câmeras"),
|
||||
("Enable camera", "Habilitar câmera"),
|
||||
("No cameras", "Nenhuma câmeras"),
|
||||
("view_camera_unsupported_tip", "O dispositivo remoto não suporta visualização da câmera."),
|
||||
("Terminal", "Terminal"),
|
||||
("Enable terminal", "Habilitar Terminal"),
|
||||
("Enable terminal", "Habilitar terminal"),
|
||||
("New tab", "Nova aba"),
|
||||
("Keep terminal sessions on disconnect", "Manter sessões de terminal ao desconectar"),
|
||||
("Terminal (Run as administrator)", "Terminal (Executar como administrador)"),
|
||||
@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("wayland-keyboard-input-reset-choice-tip", "Redefinir escolha de entrada do teclado"),
|
||||
("remember-wayland-keyboard-choice-tip", "Não perguntar novamente para este computador remoto"),
|
||||
("Why this happens", "Por que isso acontece"),
|
||||
("Switch display", "Trocar de tela"),
|
||||
("Show monitor switch button on the main toolbar", "Mostrar o botão de troca de monitor na barra de ferramentas principal"),
|
||||
("Show on the minimized toolbar", "Mostrar na barra de ferramentas minimizada"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("wayland-keyboard-input-reset-choice-tip", "Resetează alegerea pentru introducerea de la tastatură"),
|
||||
("remember-wayland-keyboard-choice-tip", "Nu mai întreba pentru acest computer la distanță"),
|
||||
("Why this happens", "De ce se întâmplă acest lucru"),
|
||||
("Switch display", "Comută afișajul"),
|
||||
("Show monitor switch button on the main toolbar", "Afișează butonul de comutare a monitorului în bara de instrumente principală"),
|
||||
("Show on the minimized toolbar", "Afișează în bara de instrumente minimizată"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("wayland-keyboard-input-reset-choice-tip", "Сбросить выбор для ввода с клавиатуры"),
|
||||
("remember-wayland-keyboard-choice-tip", "Больше не спрашивать для этого удалённого компьютера"),
|
||||
("Why this happens", "Почему это происходит"),
|
||||
("Switch display", "Переключить дисплей"),
|
||||
("Show monitor switch button on the main toolbar", "Показывать кнопку переключения монитора на главной панели инструментов"),
|
||||
("Show on the minimized toolbar", "Показывать на свёрнутой панели инструментов"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("wayland-keyboard-input-reset-choice-tip", "Reseta s'isseberada de s'insertada cun su tecladu"),
|
||||
("remember-wayland-keyboard-choice-tip", "No torres a preguntare pro custu elaboradore remotu"),
|
||||
("Why this happens", "Pro ite custu càpitat"),
|
||||
("Switch display", "Càmbia ischermu"),
|
||||
("Show monitor switch button on the main toolbar", "Mustra su butone de càmbiu de monitor in sa barra de aina printzipale"),
|
||||
("Show on the minimized toolbar", "Mustra in sa barra de aina minimizada"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("wayland-keyboard-input-reset-choice-tip", "Obnoviť voľbu vstupu z klávesnice"),
|
||||
("remember-wayland-keyboard-choice-tip", "Nepýtať sa znova pre tento vzdialený počítač"),
|
||||
("Why this happens", "Prečo sa to deje"),
|
||||
("Switch display", "Prepnúť obrazovku"),
|
||||
("Show monitor switch button on the main toolbar", "Zobraziť tlačidlo prepnutia monitora na hlavnom paneli nástrojov"),
|
||||
("Show on the minimized toolbar", "Zobraziť na minimalizovanom paneli nástrojov"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("wayland-keyboard-input-reset-choice-tip", "Ponastavi izbiro vnosa s tipkovnice"),
|
||||
("remember-wayland-keyboard-choice-tip", "Za ta oddaljeni računalnik ne vprašaj več"),
|
||||
("Why this happens", "Zakaj se to dogaja"),
|
||||
("Switch display", "Preklopi zaslon"),
|
||||
("Show monitor switch button on the main toolbar", "Pokaži gumb za preklop monitorja v glavni orodni vrstici"),
|
||||
("Show on the minimized toolbar", "Pokaži v pomanjšani orodni vrstici"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("wayland-keyboard-input-reset-choice-tip", "Rivendos zgjedhjen e hyrjes nga tastiera"),
|
||||
("remember-wayland-keyboard-choice-tip", "Mos pyet më për këtë kompjuter në distancë"),
|
||||
("Why this happens", "Pse ndodh kjo"),
|
||||
("Switch display", "Ndërro ekranin"),
|
||||
("Show monitor switch button on the main toolbar", "Shfaq butonin e ndërrimit të monitorit te shiriti kryesor i veglave"),
|
||||
("Show on the minimized toolbar", "Shfaq te shiriti i minimizuar i veglave"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("wayland-keyboard-input-reset-choice-tip", "Resetuj izbor unosa sa tastature"),
|
||||
("remember-wayland-keyboard-choice-tip", "Ne pitaj ponovo za ovaj udaljeni računar"),
|
||||
("Why this happens", "Zašto se ovo dešava"),
|
||||
("Switch display", "Промени екран"),
|
||||
("Show monitor switch button on the main toolbar", "Прикажи дугме за пребацивање монитора на главној траци са алаткама"),
|
||||
("Show on the minimized toolbar", "Прикажи на умањеној траци са алаткама"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("wayland-keyboard-input-reset-choice-tip", "Återställ val av tangentbordsinmatning"),
|
||||
("remember-wayland-keyboard-choice-tip", "Fråga inte igen för den här fjärrdatorn"),
|
||||
("Why this happens", "Varför detta händer"),
|
||||
("Switch display", "Växla skärm"),
|
||||
("Show monitor switch button on the main toolbar", "Visa knapp för skärmväxling i huvudverktygsfältet"),
|
||||
("Show on the minimized toolbar", "Visa i det minimerade verktygsfältet"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("wayland-keyboard-input-reset-choice-tip", "விசைப்பலகை உள்ளீட்டுத் தேர்வை மீட்டமை"),
|
||||
("remember-wayland-keyboard-choice-tip", "இந்தத் தொலை கணினிக்கு மீண்டும் கேட்க வேண்டாம்"),
|
||||
("Why this happens", "இது ஏன் நிகழ்கிறது"),
|
||||
("Switch display", "திரையை மாற்று"),
|
||||
("Show monitor switch button on the main toolbar", "முதன்மை கருவிப்பட்டையில் திரை மாற்று பொத்தானைக் காட்டு"),
|
||||
("Show on the minimized toolbar", "சிறிதாக்கப்பட்ட கருவிப்பட்டையில் காட்டு"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("wayland-keyboard-input-reset-choice-tip", ""),
|
||||
("remember-wayland-keyboard-choice-tip", ""),
|
||||
("Why this happens", ""),
|
||||
("Switch display", ""),
|
||||
("Show monitor switch button on the main toolbar", ""),
|
||||
("Show on the minimized toolbar", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("wayland-keyboard-input-reset-choice-tip", "รีเซ็ตตัวเลือกการป้อนข้อมูลจากคีย์บอร์ด"),
|
||||
("remember-wayland-keyboard-choice-tip", "ไม่ต้องถามอีกสำหรับคอมพิวเตอร์ปลายทางนี้"),
|
||||
("Why this happens", "เหตุใดจึงเกิดขึ้น"),
|
||||
("Switch display", "สลับจอแสดงผล"),
|
||||
("Show monitor switch button on the main toolbar", "แสดงปุ่มสลับจอภาพบนแถบเครื่องมือหลัก"),
|
||||
("Show on the minimized toolbar", "แสดงบนแถบเครื่องมือที่ย่อเล็กสุด"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("wayland-keyboard-input-reset-choice-tip", "Klavye girişi seçimini sıfırla"),
|
||||
("remember-wayland-keyboard-choice-tip", "Bu uzak bilgisayar için bir daha sorma"),
|
||||
("Why this happens", "Bunun nedeni"),
|
||||
("Switch display", "Ekranı değiştir"),
|
||||
("Show monitor switch button on the main toolbar", "Ana araç çubuğunda monitör değiştirme düğmesini göster"),
|
||||
("Show on the minimized toolbar", "Simge durumuna küçültülmüş araç çubuğunda göster"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("wayland-keyboard-input-reset-choice-tip", "重設鍵盤輸入選擇"),
|
||||
("remember-wayland-keyboard-choice-tip", "不要再為此遠端電腦詢問"),
|
||||
("Why this happens", "發生原因"),
|
||||
("Switch display", "切換螢幕"),
|
||||
("Show monitor switch button on the main toolbar", "在主工具列上顯示螢幕切換按鈕"),
|
||||
("Show on the minimized toolbar", "在最小化工具列上顯示"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("wayland-keyboard-input-reset-choice-tip", "Скинути вибір щодо введення з клавіатури"),
|
||||
("remember-wayland-keyboard-choice-tip", "Більше не запитувати для цього віддаленого комп'ютера"),
|
||||
("Why this happens", "Чому це відбувається"),
|
||||
("Switch display", "Перемкнути дисплей"),
|
||||
("Show monitor switch button on the main toolbar", "Показувати кнопку перемикання монітора на головній панелі інструментів"),
|
||||
("Show on the minimized toolbar", "Показувати на згорнутій панелі інструментів"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -758,5 +758,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("wayland-keyboard-input-reset-choice-tip", "Đặt lại lựa chọn nhập bàn phím"),
|
||||
("remember-wayland-keyboard-choice-tip", "Không hỏi lại cho máy tính từ xa này"),
|
||||
("Why this happens", "Tại sao điều này xảy ra"),
|
||||
("Switch display", "Chuyển màn hình"),
|
||||
("Show monitor switch button on the main toolbar", "Hiển thị nút chuyển đổi màn hình trên thanh công cụ chính"),
|
||||
("Show on the minimized toolbar", "Hiển thị trên thanh công cụ thu nhỏ"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -462,10 +462,14 @@ impl DesktopManager {
|
||||
let (child_xorg, child_wm) = Self::start_x11(uid, gid, username, display_num, &envs)?;
|
||||
is_child_running.store(true, Ordering::SeqCst);
|
||||
|
||||
// capture the logind session scope (from a live child) for teardown, see
|
||||
// reap_session_scope.
|
||||
let scope_dir = Self::session_scope_dir(child_xorg.id());
|
||||
|
||||
log::info!("Start xorg and wm done, notify and wait xtop x11");
|
||||
allow_err!(tx_res.send("".to_owned()));
|
||||
|
||||
Self::wait_stop_x11(child_xorg, child_wm);
|
||||
Self::wait_stop_x11(child_xorg, child_wm, scope_dir, display_num);
|
||||
log::info!("Wait x11 stop done");
|
||||
Ok(())
|
||||
}
|
||||
@@ -665,7 +669,222 @@ impl DesktopManager {
|
||||
}
|
||||
}
|
||||
|
||||
fn try_wait_stop_x11(child_xorg: &mut Child, child_wm: &mut Child) -> bool {
|
||||
// resolve the "session-<id>.scope" directory pam_systemd put the x session in, read
|
||||
// from a live child pid. cgroup v2 mounts every cgroup under /sys/fs/cgroup, v1/hybrid
|
||||
// keeps the scope under the systemd controller mount; pick by the controller field and
|
||||
// confirm the cgroup is real. empty if there is no such scope (e.g. no logind).
|
||||
fn session_scope_dir(pid: u32) -> String {
|
||||
let path = format!("/proc/{}/cgroup", pid);
|
||||
let content = match std::fs::read_to_string(&path) {
|
||||
Ok(c) => c,
|
||||
Err(e) => {
|
||||
log::warn!("Failed to read {} to find session scope: {}", path, e);
|
||||
return "".to_owned();
|
||||
}
|
||||
};
|
||||
for line in content.lines() {
|
||||
// "<hierarchy>:<controllers>:<path>"; v2 unified is "0::<path>", the v1
|
||||
// systemd hierarchy is "<n>:name=systemd:<path>".
|
||||
let mut fields = line.splitn(3, ':');
|
||||
let (controllers, cgroup) = match (fields.next(), fields.next(), fields.next()) {
|
||||
(Some(_), Some(c), Some(p)) => (c, p),
|
||||
_ => continue,
|
||||
};
|
||||
let scope = match Self::session_scope(cgroup) {
|
||||
Some(s) => s,
|
||||
None => continue,
|
||||
};
|
||||
let mount = if controllers.is_empty() {
|
||||
"/sys/fs/cgroup"
|
||||
} else if controllers.split(',').any(|c| c == "name=systemd") {
|
||||
"/sys/fs/cgroup/systemd"
|
||||
} else {
|
||||
continue;
|
||||
};
|
||||
let dir = format!("{}{}", mount, scope);
|
||||
if Path::new(&format!("{}/cgroup.procs", dir)).exists() {
|
||||
return dir;
|
||||
}
|
||||
}
|
||||
"".to_owned()
|
||||
}
|
||||
|
||||
// the "/.../session-<id>.scope" prefix of a cgroup path, dropping any nested child
|
||||
// cgroup below it so a descendant scope does not get mistaken for the session.
|
||||
fn session_scope(cgroup: &str) -> Option<String> {
|
||||
let mut scope = String::new();
|
||||
for comp in cgroup.split('/').filter(|c| !c.is_empty()) {
|
||||
scope.push('/');
|
||||
scope.push_str(comp);
|
||||
if comp.starts_with("session-") && comp.ends_with(".scope") {
|
||||
return Some(scope);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
// on teardown reap the whole session scope subtree, not just the xorg + wm pids:
|
||||
// the per-session pipewire and other desktop children otherwise outlive them and
|
||||
// hold the logind session in "closing", leaking sockets + displays on reconnect
|
||||
// (rustdesk/rustdesk#15183). SIGTERM first so pipewire unlinks its sockets, then
|
||||
// SIGKILL stragglers; skip our own pid (pam put the service in the scope too).
|
||||
fn reap_session_scope(scope_dir: &str) {
|
||||
if scope_dir.is_empty() {
|
||||
return;
|
||||
}
|
||||
let me = std::process::id();
|
||||
// spare the --server's own children and any descendants of them sharing this scope
|
||||
// (see pid_is_spared); only the desktop session's leftovers are reaped.
|
||||
let spared: Vec<u32> = crate::server::CHILD_PROCESS
|
||||
.lock()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.map(|c| c.id())
|
||||
.collect();
|
||||
for sig in [hbb_common::libc::SIGTERM, hbb_common::libc::SIGKILL] {
|
||||
let mut pids = Vec::new();
|
||||
Self::collect_scope_pids(Path::new(scope_dir), &mut pids);
|
||||
let mut any = false;
|
||||
for pid in pids {
|
||||
if pid == me || Self::pid_is_spared(pid, &spared, me) {
|
||||
continue;
|
||||
}
|
||||
any = true;
|
||||
log::info!("Reaping leftover session process {} (signal {})", pid, sig);
|
||||
unsafe {
|
||||
if hbb_common::libc::kill(pid as hbb_common::libc::pid_t, sig) != 0 {
|
||||
let err = std::io::Error::last_os_error();
|
||||
// ESRCH = it already exited (or did between snapshot and now).
|
||||
if err.raw_os_error() != Some(hbb_common::libc::ESRCH) {
|
||||
log::warn!("Failed to signal session process {}: {}", pid, err);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if !any {
|
||||
break;
|
||||
}
|
||||
if sig == hbb_common::libc::SIGTERM {
|
||||
std::thread::sleep(Duration::from_millis(300));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// a tracked --server child (the sudo wrapper run_as_user spawns) or any descendant of
|
||||
// one: with use_pty sudo runs --cm-no-ui under a monitor with its own pid, so walk the
|
||||
// parent chain (stopping at the --server) to spare the worker, not just the wrapper.
|
||||
fn pid_is_spared(pid: u32, spared: &[u32], me: u32) -> bool {
|
||||
let mut cur = pid;
|
||||
for _ in 0..32 {
|
||||
if spared.contains(&cur) {
|
||||
return true;
|
||||
}
|
||||
if cur <= 1 || cur == me {
|
||||
return false;
|
||||
}
|
||||
match Self::parent_pid(cur) {
|
||||
Some(ppid) => cur = ppid,
|
||||
None => return false,
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn parent_pid(pid: u32) -> Option<u32> {
|
||||
// /proc/<pid>/stat is "pid (comm) state ppid ..."; comm can contain spaces and ')',
|
||||
// so read the fields after the last ')'.
|
||||
let stat = std::fs::read_to_string(format!("/proc/{}/stat", pid)).ok()?;
|
||||
stat.rsplit_once(')')?
|
||||
.1
|
||||
.split_whitespace()
|
||||
.nth(1)?
|
||||
.parse()
|
||||
.ok()
|
||||
}
|
||||
|
||||
// collect every pid in the cgroup subtree rooted at dir. "cgroup.procs" lists only
|
||||
// the procs directly in a cgroup, so recurse into child cgroup directories to catch
|
||||
// processes the desktop session moved into descendant scopes.
|
||||
fn collect_scope_pids(dir: &Path, out: &mut Vec<u32>) {
|
||||
let procs = dir.join("cgroup.procs");
|
||||
match std::fs::read_to_string(&procs) {
|
||||
Ok(content) => {
|
||||
out.extend(content.lines().filter_map(|l| l.trim().parse::<u32>().ok()));
|
||||
}
|
||||
Err(e) if e.kind() != std::io::ErrorKind::NotFound => {
|
||||
log::warn!("Failed to read {}: {}", procs.display(), e);
|
||||
}
|
||||
Err(_) => {}
|
||||
}
|
||||
let entries = match std::fs::read_dir(dir) {
|
||||
Ok(e) => e,
|
||||
Err(e) if e.kind() != std::io::ErrorKind::NotFound => {
|
||||
log::warn!("Failed to list cgroup dir {}: {}", dir.display(), e);
|
||||
return;
|
||||
}
|
||||
Err(_) => return,
|
||||
};
|
||||
for entry in entries {
|
||||
let entry = match entry {
|
||||
Ok(entry) => entry,
|
||||
Err(e) => {
|
||||
log::warn!("Failed to read entry under {}: {}", dir.display(), e);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
match entry.file_type() {
|
||||
Ok(t) if t.is_dir() => Self::collect_scope_pids(&entry.path(), out),
|
||||
Ok(_) => {}
|
||||
Err(e) if e.kind() != std::io::ErrorKind::NotFound => {
|
||||
log::warn!("Failed to stat {}: {}", entry.path().display(), e);
|
||||
}
|
||||
Err(_) => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// a SIGKILL'd Xorg (how wait_x11_children_exit ends it) leaves "/tmp/.X<n>-lock" and
|
||||
// "/tmp/.X11-unix/X<n>" behind, and get_avail_display() treats either file as "display
|
||||
// in use", so the number is never reused and climbs until none are free
|
||||
// (rustdesk/rustdesk#15183). a clean exit would remove them; do the same on teardown,
|
||||
// but skip it if a live process still holds the lock: another server could have taken
|
||||
// the number in the gap, and removing its files would break that display.
|
||||
fn cleanup_x_display_files(display_num: u32) {
|
||||
let lock = format!("/tmp/.X{}-lock", display_num);
|
||||
if let Ok(content) = std::fs::read_to_string(&lock) {
|
||||
if let Ok(pid) = content.trim().parse::<i32>() {
|
||||
if Self::pid_alive(pid) {
|
||||
log::info!("X display {} still held by pid {}, leaving its files", display_num, pid);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
for path in [lock, format!("/tmp/.X11-unix/X{}", display_num)] {
|
||||
if let Err(e) = std::fs::remove_file(&path) {
|
||||
if e.kind() != std::io::ErrorKind::NotFound {
|
||||
log::warn!("Failed to remove stale X file {}: {}", path, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// signal-0 probe: the pid exists if kill succeeds or fails with EPERM (alive but not
|
||||
// ours); only ESRCH means it is gone.
|
||||
fn pid_alive(pid: i32) -> bool {
|
||||
unsafe {
|
||||
if hbb_common::libc::kill(pid as hbb_common::libc::pid_t, 0) == 0 {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
std::io::Error::last_os_error().raw_os_error() == Some(hbb_common::libc::EPERM)
|
||||
}
|
||||
|
||||
fn try_wait_stop_x11(
|
||||
child_xorg: &mut Child,
|
||||
child_wm: &mut Child,
|
||||
scope_dir: &str,
|
||||
display_num: u32,
|
||||
) -> bool {
|
||||
let mut desktop_manager = DESKTOP_MANAGER.lock().unwrap();
|
||||
let mut exited = true;
|
||||
if let Some(desktop_manager) = &mut (*desktop_manager) {
|
||||
@@ -677,6 +896,8 @@ impl DesktopManager {
|
||||
if exited {
|
||||
log::debug!("Wait x11 children exiting");
|
||||
Self::wait_x11_children_exit(child_xorg, child_wm);
|
||||
Self::reap_session_scope(scope_dir);
|
||||
Self::cleanup_x_display_files(display_num);
|
||||
desktop_manager
|
||||
.is_child_running
|
||||
.store(false, Ordering::SeqCst);
|
||||
@@ -686,9 +907,14 @@ impl DesktopManager {
|
||||
exited
|
||||
}
|
||||
|
||||
fn wait_stop_x11(mut child_xorg: Child, mut child_wm: Child) {
|
||||
fn wait_stop_x11(
|
||||
mut child_xorg: Child,
|
||||
mut child_wm: Child,
|
||||
scope_dir: String,
|
||||
display_num: u32,
|
||||
) {
|
||||
loop {
|
||||
if Self::try_wait_stop_x11(&mut child_xorg, &mut child_wm) {
|
||||
if Self::try_wait_stop_x11(&mut child_xorg, &mut child_wm, &scope_dir, display_num) {
|
||||
break;
|
||||
}
|
||||
std::thread::sleep(Duration::from_millis(super::SERVICE_INTERVAL));
|
||||
@@ -806,3 +1032,54 @@ fn pam_get_service_name() -> String {
|
||||
"gdm".to_owned()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn session_scope_truncates_at_first_scope() {
|
||||
assert_eq!(
|
||||
DesktopManager::session_scope("/user.slice/user-1000.slice/session-3.scope").as_deref(),
|
||||
Some("/user.slice/user-1000.slice/session-3.scope")
|
||||
);
|
||||
// a nested child scope must not be mistaken for the session
|
||||
assert_eq!(
|
||||
DesktopManager::session_scope(
|
||||
"/user.slice/user-1000.slice/session-3.scope/app-foo.scope"
|
||||
)
|
||||
.as_deref(),
|
||||
Some("/user.slice/user-1000.slice/session-3.scope")
|
||||
);
|
||||
assert_eq!(
|
||||
DesktopManager::session_scope(
|
||||
"/user.slice/user-1000.slice/user@1000.service/app.slice/x.service"
|
||||
),
|
||||
None
|
||||
);
|
||||
assert_eq!(DesktopManager::session_scope("/"), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn collect_scope_pids_walks_descendant_cgroups() {
|
||||
// regression for #15183: pids in descendant cgroups must be collected too
|
||||
let base = std::env::temp_dir().join(format!("rustdesk-cgtest-{}", std::process::id()));
|
||||
let _ = std::fs::remove_dir_all(&base);
|
||||
let scope = base.join("session-3.scope");
|
||||
let child = scope.join("app-foo.scope");
|
||||
let nested = child.join("deeper.scope");
|
||||
std::fs::create_dir_all(&nested).unwrap();
|
||||
std::fs::create_dir_all(scope.join("empty.scope")).unwrap();
|
||||
std::fs::write(scope.join("cgroup.procs"), "100\n101\n").unwrap();
|
||||
std::fs::write(scope.join("cgroup.controllers"), "memory pids\n").unwrap();
|
||||
std::fs::write(child.join("cgroup.procs"), "200\n").unwrap();
|
||||
std::fs::write(nested.join("cgroup.procs"), "300\n").unwrap();
|
||||
|
||||
let mut pids = Vec::new();
|
||||
DesktopManager::collect_scope_pids(&scope, &mut pids);
|
||||
pids.sort();
|
||||
let _ = std::fs::remove_dir_all(&base);
|
||||
|
||||
assert_eq!(pids, vec![100, 101, 200, 300]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,13 +4,14 @@ use hbb_common::{allow_err, bail, log, ResultType};
|
||||
use std::{
|
||||
ffi::CString,
|
||||
io::Error,
|
||||
mem::size_of,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
use winapi::{
|
||||
shared::{
|
||||
minwindef::FALSE,
|
||||
minwindef::{BOOL, FALSE, LPARAM, TRUE},
|
||||
ntdef::{HANDLE, NULL},
|
||||
windef::HWND,
|
||||
windef::{HDC, HMONITOR, HWND, RECT},
|
||||
},
|
||||
um::{
|
||||
handleapi::CloseHandle,
|
||||
@@ -31,7 +32,13 @@ 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,
|
||||
@@ -102,22 +109,17 @@ impl PrivacyMode for PrivacyModeImpl {
|
||||
);
|
||||
}
|
||||
|
||||
if self.handlers.is_default() {
|
||||
log::info!("turn_on_privacy, dll not found when started, try start");
|
||||
let should_start_broker = self.handlers.is_default();
|
||||
if should_start_broker {
|
||||
log::info!("turn_on_privacy, broker not running, try start");
|
||||
self.start()?;
|
||||
std::thread::sleep(std::time::Duration::from_millis(1_000));
|
||||
}
|
||||
|
||||
let hwnd = wait_find_privacy_hwnd(0)?;
|
||||
if hwnd.is_null() {
|
||||
bail!("No privacy window created");
|
||||
if let Err(e) = self.show_privacy_windows(conn_id, true) {
|
||||
self.stop();
|
||||
return Err(e);
|
||||
}
|
||||
super::win_input::hook()?;
|
||||
unsafe {
|
||||
ShowWindow(hwnd as _, SW_SHOW);
|
||||
}
|
||||
self.conn_id = conn_id;
|
||||
self.hwnd = hwnd as _;
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
@@ -128,27 +130,33 @@ impl PrivacyMode for PrivacyModeImpl {
|
||||
) -> ResultType<()> {
|
||||
self.check_off_conn_id(conn_id)?;
|
||||
super::win_input::unhook()?;
|
||||
|
||||
unsafe {
|
||||
let hwnd = wait_find_privacy_hwnd(0)?;
|
||||
if !hwnd.is_null() {
|
||||
ShowWindow(hwnd, SW_HIDE);
|
||||
}
|
||||
let hwnds = find_privacy_hwnds()?;
|
||||
let hide_result = set_privacy_windows_visible(&hwnds, false);
|
||||
if hide_result.is_err() {
|
||||
self.stop();
|
||||
}
|
||||
|
||||
// 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 {
|
||||
if let Some(state) = state {
|
||||
allow_err!(super::set_privacy_mode_state(
|
||||
conn_id,
|
||||
state,
|
||||
PRIVACY_MODE_IMPL.to_string(),
|
||||
1_000
|
||||
));
|
||||
// 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
|
||||
));
|
||||
}
|
||||
}
|
||||
self.conn_id = INVALID_PRIVACY_MODE_CONN_ID.to_owned();
|
||||
self.hwnd = 0;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
hide_result.map(|_| ())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -206,8 +214,7 @@ impl PrivacyModeImpl {
|
||||
);
|
||||
}
|
||||
|
||||
let hwnd = wait_find_privacy_hwnd(1_000)?;
|
||||
if !hwnd.is_null() {
|
||||
if wait_find_privacy_hwnds(PRIVACY_WINDOW_WAIT_MILLIS).is_ok() {
|
||||
log::info!("Privacy window is ready");
|
||||
return Ok(());
|
||||
}
|
||||
@@ -276,14 +283,19 @@ impl PrivacyModeImpl {
|
||||
);
|
||||
};
|
||||
|
||||
inject_dll(
|
||||
if let Err(e) = 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) {
|
||||
// CloseHandle
|
||||
TerminateProcess(proc_info.hProcess, 0);
|
||||
CloseHandle(proc_info.hThread);
|
||||
CloseHandle(proc_info.hProcess);
|
||||
|
||||
@@ -296,9 +308,9 @@ impl PrivacyModeImpl {
|
||||
self.handlers.hthread = proc_info.hThread as _;
|
||||
self.handlers.hprocess = proc_info.hProcess as _;
|
||||
|
||||
let hwnd = wait_find_privacy_hwnd(1_000)?;
|
||||
if hwnd.is_null() {
|
||||
bail!("Failed to get hwnd after started");
|
||||
if let Err(e) = wait_find_privacy_hwnds(PRIVACY_WINDOW_WAIT_MILLIS) {
|
||||
self.handlers.reset();
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -309,6 +321,49 @@ 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 {
|
||||
@@ -363,21 +418,217 @@ unsafe fn inject_dll<'a>(hproc: HANDLE, hthread: HANDLE, dll_file: &'a str) -> R
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(super) fn wait_find_privacy_hwnd(msecs: u128) -> ResultType<HWND> {
|
||||
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());
|
||||
|
||||
let tm_begin = Instant::now();
|
||||
let wndname = CString::new(PRIVACY_WINDOW_NAME)?;
|
||||
loop {
|
||||
unsafe {
|
||||
let hwnd = FindWindowA(NULL as _, wndname.as_ptr() as _);
|
||||
if !hwnd.is_null() {
|
||||
return Ok(hwnd);
|
||||
}
|
||||
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
|
||||
});
|
||||
}
|
||||
|
||||
if msecs == 0 || tm_begin.elapsed().as_millis() > msecs {
|
||||
return Ok(NULL as _);
|
||||
let visible = if require_visible { "visible " } else { "" };
|
||||
bail!(
|
||||
"Expected {}privacy windows to cover {} monitors, covered {}, found {}",
|
||||
visible,
|
||||
monitor_rects.len(),
|
||||
covered,
|
||||
hwnds.len(),
|
||||
);
|
||||
}
|
||||
|
||||
std::thread::sleep(Duration::from_millis(100));
|
||||
std::thread::sleep(Duration::from_millis(PRIVACY_WINDOW_POLL_INTERVAL_MILLIS));
|
||||
}
|
||||
}
|
||||
|
||||
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,6 +272,10 @@ 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,6 +20,11 @@
|
||||
"name": "libjpeg-turbo",
|
||||
"host": false
|
||||
},
|
||||
{
|
||||
"name": "libsodium",
|
||||
"host": false,
|
||||
"platform": "windows & arm64"
|
||||
},
|
||||
{
|
||||
"name": "oboe",
|
||||
"platform": "android"
|
||||
@@ -64,15 +69,15 @@
|
||||
"features": [
|
||||
{
|
||||
"name": "amf",
|
||||
"platform": "((windows | linux) & static)"
|
||||
"platform": "(((windows & !arm) | linux) & static)"
|
||||
},
|
||||
{
|
||||
"name": "nvcodec",
|
||||
"platform": "((windows | linux) & static)"
|
||||
"platform": "(((windows & !arm) | linux) & static)"
|
||||
},
|
||||
{
|
||||
"name": "qsv",
|
||||
"platform": "(windows & static)"
|
||||
"platform": "(windows & !arm & static)"
|
||||
}
|
||||
],
|
||||
"platform": "((windows | (linux & !arm32) | osx) & static)"
|
||||
|
||||
Reference in New Issue
Block a user