Compare commits

..

1 Commits

Author SHA1 Message Date
rustdesk 0a0625126e fix https://github.com/rustdesk/rustdesk/issues/15326 2026-06-18 12:09:51 +08:00
120 changed files with 733 additions and 3100 deletions
-2
View File
@@ -2,8 +2,6 @@
rustflags = ["-Ctarget-feature=+crt-static"]
[target.i686-pc-windows-msvc]
rustflags = ["-C", "target-feature=+crt-static", "-C", "link-args=/NODEFAULTLIB:MSVCRT"]
[target.aarch64-pc-windows-msvc]
rustflags = ["-Ctarget-feature=+crt-static"]
[target.'cfg(target_os="macos")']
rustflags = [
"-C", "link-args=-sectcreate __CGPreLoginApp __cgpreloginapp /dev/null",
@@ -1,39 +0,0 @@
#!/usr/bin/env bash
# Applies the Flutter 3.44-only source/pubspec changes on the fly, in CI only.
#
# Windows arm64 needs Flutter >= 3.44 (the first stable release shipping an arm64 Dart SDK +
# engine), which renamed DialogTheme/TabBarTheme -> *Data and needs newer extended_text/
# google_fonts. Every other platform is still on Flutter 3.24.5, where the old names/versions
# are required, so these changes are kept OUT of the committed sources and applied here instead.
#
# Used by BOTH the Windows arm64 build (flutter-build.yml) and its dedicated bridge artifact
# (bridge.yml) so they share an identical 3.44 source state -- the generated *.freezed.dart must
# compile against the same Flutter/freezed version the arm64 build resolves.
#
# Remove this script (and commit the changes) once upstream bumps Flutter across the board.
#
# Run from the repository root. sed is used (not a git-apply patch) because the checked-out
# sources are CRLF on the windows-11-arm runner; the substitutions below are anchor-free and
# therefore CRLF-safe.
set -euo pipefail
# ThemeData API renames (Flutter 3.27+):
sed -i 's/dialogTheme: DialogTheme(/dialogTheme: DialogThemeData(/g' flutter/lib/common.dart
sed -i 's/tabBarTheme: const TabBarTheme(/tabBarTheme: const TabBarThemeData(/g' flutter/lib/common.dart
sed -i '/static ThemeData lightTheme = ThemeData(/,/static ThemeData darkTheme = ThemeData(/s/dialogTheme: DialogThemeData(/dialogTheme: DialogThemeData(\
backgroundColor: Colors.white,/' flutter/lib/common.dart
sed -i '/static ThemeData darkTheme = ThemeData(/,/scrollbarTheme: scrollbarThemeDark,/s/dialogTheme: DialogThemeData(/dialogTheme: DialogThemeData(\
backgroundColor: Color(0xFF18191E),/' flutter/lib/common.dart
# Dependency bumps required by the newer Dart/Flutter:
sed -i 's/extended_text: 14.0.0/extended_text: 15.0.2/' flutter/pubspec.yaml
sed -i 's/google_fonts: \^6.2.1/google_fonts: ^8.1.0/' flutter/pubspec.yaml
# Fail loudly if any expected string drifted, so we never silently build unpatched:
grep -qF 'dialogTheme: DialogThemeData(' flutter/lib/common.dart
grep -qF 'tabBarTheme: const TabBarThemeData(' flutter/lib/common.dart
grep -qF 'backgroundColor: Colors.white,' flutter/lib/common.dart
grep -qF 'backgroundColor: Color(0xFF18191E),' flutter/lib/common.dart
grep -qF 'extended_text: 15.0.2' flutter/pubspec.yaml
grep -qF 'google_fonts: ^8.1.0' flutter/pubspec.yaml
git --no-pager diff -- flutter/lib/common.dart flutter/pubspec.yaml
+5 -23
View File
@@ -7,6 +7,7 @@ on:
env:
CARGO_EXPAND_VERSION: "1.0.95"
FLUTTER_VERSION: "3.22.3"
FLUTTER_RUST_BRIDGE_VERSION: "1.80.1"
RUST_VERSION: "1.75" # https://github.com/rustdesk/rustdesk/discussions/7503
@@ -17,21 +18,10 @@ jobs:
fail-fast: false
matrix:
job:
# Default bridge for every platform still on Flutter 3.24.5 (generated with 3.22.3).
- {
target: x86_64-unknown-linux-gnu,
os: ubuntu-22.04,
extra-build-args: "",
flutter-version: "3.22.3",
artifact-name: "bridge-artifact",
}
# Dedicated bridge for the Windows arm64 build (Flutter 3.44); runs in parallel.
- {
target: x86_64-unknown-linux-gnu,
os: ubuntu-22.04,
extra-build-args: "",
flutter-version: "3.44.0",
artifact-name: "bridge-artifact-flutter-3.44",
}
steps:
- name: Checkout source code
@@ -74,13 +64,13 @@ jobs:
uses: actions/cache@6f8efc29b200d32929f49075959781ed54ec270c # v3
with:
path: /tmp/flutter_rust_bridge
key: bridge-${{ matrix.job.flutter-version }}
key: vcpkg-${{ matrix.job.arch }}
- name: Install flutter
uses: subosito/flutter-action@1a449444c387b1966244ae4d4f8c696479add0b2 # v2
with:
channel: "stable"
flutter-version: ${{ matrix.job.flutter-version }}
flutter-version: ${{ env.FLUTTER_VERSION }}
cache: true
- name: Install flutter rust bridge deps
@@ -88,15 +78,7 @@ jobs:
run: |
cargo install cargo-expand --version ${{ env.CARGO_EXPAND_VERSION }} --locked
cargo install flutter_rust_bridge_codegen --version ${{ env.FLUTTER_RUST_BRIDGE_VERSION }} --features "uuid" --locked
if [[ "${{ matrix.job.flutter-version }}" == "3.22.3" ]]; then
# Default Flutter 3.22.3: extended_text 14 needs a newer Dart, so downgrade for resolution.
sed -i -e 's/extended_text: 14.0.0/extended_text: 13.0.0/g' flutter/pubspec.yaml
else
# Flutter 3.44 bridge for Windows arm64: match that build's source/pubspec state so the
# generated *.freezed.dart compiles against the same Flutter/freezed it resolves.
bash .github/patches/apply_flutter_3.44_source_patches.sh
fi
pushd flutter && flutter pub get && popd
pushd flutter && sed -i -e 's/extended_text: 14.0.0/extended_text: 13.0.0/g' pubspec.yaml && flutter pub get && popd
- name: Run flutter rust bridge
run: |
@@ -106,7 +88,7 @@ jobs:
- name: Upload Artifact
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: ${{ matrix.job.artifact-name }}
name: bridge-artifact
path: |
./src/bridge_generated.rs
./src/bridge_generated.io.rs
-1
View File
@@ -81,7 +81,6 @@ jobs:
# - { target: x86_64-apple-darwin , os: macos-10.15 }
# - { target: x86_64-pc-windows-gnu , os: windows-2022 }
# - { target: x86_64-pc-windows-msvc , os: windows-2022 }
# - { target: aarch64-pc-windows-msvc , os: windows-11-arm }
- { target: x86_64-unknown-linux-gnu , os: ubuntu-24.04 }
# - { target: x86_64-unknown-linux-musl , os: ubuntu-20.04, use-cross: true }
steps:
+14 -109
View File
@@ -27,11 +27,6 @@ env:
LLVM_VERSION: "15.0.6"
FLUTTER_VERSION: "3.24.5"
ANDROID_FLUTTER_VERSION: "3.24.5"
# Windows arm64 only: the first stable Flutter to ship a native arm64 Windows Dart SDK +
# engine is 3.44. Every other platform stays on FLUTTER_VERSION (3.24.5) until Windows 7
# support is restored after the upstream-wide Flutter bump. The arm64 job patches the few
# 3.44-only source/pubspec changes on the fly (see "Patch RustDesk sources for Flutter 3.44").
FLUTTER_WINDOWS_ARM_VERSION: "3.44.0"
# for arm64 linux because official Dart SDK does not work
FLUTTER_ELINUX_VERSION: "3.16.9"
TAG_NAME: "${{ inputs.upload-tag }}"
@@ -58,24 +53,14 @@ jobs:
build-RustDeskTempTopMostWindow:
uses: ./.github/workflows/third-party-RustDeskTempTopMostWindow.yml
strategy:
fail-fast: false
matrix:
job:
- {
target: windows-2022,
platform: x64,
}
- {
target: windows-11-arm,
platform: ARM64,
}
with:
upload-artifact: ${{ inputs.upload-artifact }}
target: ${{ matrix.job.target }}
target: windows-2022
configuration: Release
platform: ${{ matrix.job.platform }}
platform: x64
target_version: Windows10
strategy:
fail-fast: false
build-for-windows-flutter:
name: ${{ matrix.job.target }}
@@ -91,20 +76,9 @@ jobs:
target: x86_64-pc-windows-msvc,
os: windows-2022,
arch: x86_64,
flutter-arch: x64,
vcpkg-triplet: x64-windows-static,
build-args: "--vram",
}
- {
target: aarch64-pc-windows-msvc,
os: windows-11-arm,
arch: aarch64,
flutter-arch: arm64,
vcpkg-triplet: arm64-windows-static,
# vram is x86/x64-only (NVENC needs CUDA, Intel MediaSDK needs __rdtsc);
# no NV/Intel/AMD hardware exists on Windows-on-ARM, so vram stays disabled here.
build-args: "",
}
# - { target: aarch64-pc-windows-msvc, os: windows-2022, arch: aarch64 }
steps:
- name: Export GitHub Actions cache environment variables
uses: actions/github-script@d7906e4ad0b1822421a7e6a35d5ca353c962f410 # v6
@@ -121,91 +95,36 @@ jobs:
- name: Restore bridge files
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
# arm64 is on Flutter 3.44, so it needs the bridge generated with the same Flutter
# (its *.freezed.dart must match the freezed the arm64 build resolves). x64 and every
# other platform keep the default 3.22.3-generated bridge.
name: ${{ matrix.job.arch == 'aarch64' && 'bridge-artifact-flutter-3.44' || 'bridge-artifact' }}
name: bridge-artifact
path: ./
- name: Install LLVM and Clang
uses: KyleMayes/install-llvm-action@ebc0426251bc40c7cd31162802432c68818ab8f0 # v2.0.9
uses: KyleMayes/install-llvm-action@1a3da29f56261a1e1f937ec88f0856a9b8321d7e # v1
with:
version: ${{ env.LLVM_VERSION }}
- name: Install flutter
id: flutter
# arm64 builds with FLUTTER_WINDOWS_ARM_VERSION (>=3.44); x64 stays on FLUTTER_VERSION.
# subosito only ships an x64 Windows SDK (Flutter's release manifest lists x64 only),
# so it installs x64 on both arches. The arm64 runner re-bootstraps Dart to arm64 in
# the next step.
uses: subosito/flutter-action@2783a3f08e1baf891508463f8c6653c258246225 # v2.12.0; https://github.com/subosito/flutter-action/issues/277
with:
channel: "stable"
flutter-version: ${{ matrix.job.arch == 'aarch64' && env.FLUTTER_WINDOWS_ARM_VERSION || env.FLUTTER_VERSION }}
architecture: x64
- name: Force arm64 Dart SDK + engine
# The x64 SDK subosito installs bundles an x64 Dart with a matching engine-dart-sdk.stamp,
# so update_dart_sdk.ps1 short-circuits (stamp matches -> return) and keeps x64 Dart;
# `flutter build windows` then targets the Dart VM's arch = x64, even on this arm64 host.
# On this native-arm64 runner (PROCESSOR_ARCHITECTURE=ARM64), deleting the stamp and
# re-running update_dart_sdk.ps1 pulls the arm64 Dart (available since Flutter 3.44.0).
# https://github.com/flutter/flutter/issues/186730#issuecomment-4573214964
if: ${{ matrix.job.arch == 'aarch64' }}
run: |
$flutterRoot = "${{ steps.flutter.outputs['CACHE-PATH'] }}"
Write-Host "PROCESSOR_ARCHITECTURE=$env:PROCESSOR_ARCHITECTURE"
Write-Host "Flutter root: $flutterRoot"
Remove-Item -Force "$flutterRoot\bin\cache\engine-dart-sdk.stamp" -ErrorAction SilentlyContinue
& "$flutterRoot\bin\internal\update_dart_sdk.ps1"
# Confirm the Dart we ended up with is arm64 ("on windows_arm64"); fail loudly if not.
$dartVer = & "$flutterRoot\bin\dart.bat" --version 2>&1 | Out-String
Write-Host $dartVer
if ($dartVer -notmatch "windows_arm64") {
Write-Error "Expected an arm64 Dart SDK but got: $dartVer"
exit 1
}
& "$flutterRoot\bin\flutter.bat" precache --windows
# Fail fast if precache pulled the wrong-arch Windows engine: an arm64 Dart should
# fetch windows-arm64 engine artifacts. Bailing here saves the ~25min Rust build.
$engineDir = "$flutterRoot\bin\cache\artifacts\engine"
Write-Host "Engine artifacts present:"
Get-ChildItem $engineDir -Directory | Select-Object -ExpandProperty Name | Write-Host
if (-not (Test-Path "$engineDir\windows-arm64-release")) {
Write-Error "Expected windows-arm64-release engine artifacts but they are missing (wrong-arch SDK)."
exit 1
}
flutter-version: ${{ env.FLUTTER_VERSION }}
# https://github.com/flutter/flutter/issues/155685
# x64 only: arm64 uses the stock native arm64 Windows engine, and the rustdesk/engine
# windows-x64-release.zip is built for the 3.24-era x64 engine (matches FLUTTER_VERSION).
- name: Replace engine with rustdesk custom flutter engine
if: ${{ matrix.job.arch == 'x86_64' }}
run: |
flutter doctor -v
flutter precache --windows
Invoke-WebRequest -Uri https://github.com/rustdesk/engine/releases/download/main/windows-x64-release.zip -OutFile windows-x64-release.zip
Expand-Archive -Path windows-x64-release.zip -DestinationPath windows-x64-release
mv -Force windows-x64-release/* C:/hostedtoolcache/windows/flutter/stable-${{ env.FLUTTER_VERSION }}-x64/bin/cache/artifacts/engine/windows-x64-release/
mv -Force windows-x64-release/*  C:/hostedtoolcache/windows/flutter/stable-${{ env.FLUTTER_VERSION }}-x64/bin/cache/artifacts/engine/windows-x64-release/
- name: Patch flutter
# x64 stays on Flutter 3.24.5, which needs the dropdown filter patch.
# arm64 is on Flutter 3.44 (patched separately below) and does not use this patch.
if: ${{ matrix.job.arch == 'x86_64' }}
shell: bash
run: |
cp .github/patches/flutter_3.24.4_dropdown_menu_enableFilter.diff $(dirname $(dirname $(which flutter)))
cd $(dirname $(dirname $(which flutter)))
[[ "3.24.5" == ${{env.FLUTTER_VERSION}} ]] && git apply flutter_3.24.4_dropdown_menu_enableFilter.diff
- name: Patch RustDesk sources for Flutter 3.44 (arm64)
# arm64 is the only target on Flutter 3.44; apply its source/pubspec deltas on the fly
# (shared with the 3.44 bridge job) so the committed sources stay on Flutter 3.24.5.
# `flutter build` then runs `pub get`, regenerating pubspec.lock for the bumped deps.
if: ${{ matrix.job.arch == 'aarch64' }}
shell: bash
run: bash .github/patches/apply_flutter_3.44_source_patches.sh
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 # v1
with:
@@ -244,19 +163,11 @@ jobs:
head -n 100 "${VCPKG_ROOT}/buildtrees/ffmpeg/build-${{ matrix.job.vcpkg-triplet }}-rel-out.log" || true
shell: bash
- name: Set SODIUM_LIB_DIR (arm64)
# libsodium-sys ships no arm64 Windows prebuilt lib; point it at the vcpkg-built one
# (only for arm64 — leaving it unset lets x64 use the crate's bundled lib).
if: ${{ matrix.job.arch == 'aarch64' }}
shell: bash
run: echo "SODIUM_LIB_DIR=$VCPKG_ROOT/installed/${{ matrix.job.vcpkg-triplet }}/lib" >> "$GITHUB_ENV"
- name: Build rustdesk
run: |
# Windows: build RustDesk
# --hwcodec is shared by all Windows targets; per-target extras (e.g. --vram) come from the matrix
python3 .\build.py --portable --flutter --skip-portable-pack --hwcodec ${{ matrix.job.build-args }}
mv ./flutter/build/windows/${{ matrix.job.flutter-arch }}/runner/Release ./rustdesk
python3 .\build.py --portable --hwcodec --flutter --vram --skip-portable-pack
mv ./flutter/build/windows/x64/runner/Release ./rustdesk
# Download usbmmidd_v2.zip and extract it to ./rustdesk
Invoke-WebRequest -Uri https://github.com/rustdesk-org/rdev/releases/download/usbmmidd_v2/usbmmidd_v2.zip -OutFile usbmmidd_v2.zip
@@ -312,7 +223,7 @@ jobs:
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
if: ${{ inputs.upload-artifact }}
with:
name: ${{ matrix.job.arch == 'aarch64' && 'topmostwindow-artifacts-ARM64' || 'topmostwindow-artifacts-x64' }}
name: topmostwindow-artifacts
path: "./rustdesk"
- name: Upload unsigned
@@ -345,18 +256,13 @@ jobs:
uses: microsoft/setup-msbuild@6fb02220983dee41ce7ae257b6f4d8f9bf5ed4ce # v2
- name: Build msi
# Builds the MSI for the matrix arch. res/msi (WiX v4 + native CustomActions) carries
# both x64 and ARM64 platform configs; WcaUtil/DUtil ship arm64 libs. msbuild platform
# is x64 / ARM64; the produced Package.msi is globbed since its bin/<platform>/ dir varies.
if: env.UPLOAD_ARTIFACT == 'true'
run: |
pushd ./res/msi
python preprocess.py --arp -d ../../rustdesk
nuget restore msi.sln
$msiPlatform = if ('${{ matrix.job.arch }}' -eq 'aarch64') { 'ARM64' } else { 'x64' }
msbuild msi.sln -p:Configuration=Release -p:Platform=$msiPlatform /p:TargetVersion=Windows10
$msi = Get-ChildItem ./Package/bin/*/Release/en-us/Package.msi | Select-Object -First 1
mv $msi.FullName ../../SignOutput/rustdesk-${{ env.VERSION }}-${{ matrix.job.arch }}.msi
msbuild msi.sln -p:Configuration=Release -p:Platform=x64 /p:TargetVersion=Windows10
mv ./Package/bin/x64/Release/en-us/Package.msi ../../SignOutput/rustdesk-${{ env.VERSION }}-${{ matrix.job.arch }}.msi
sha256sum ../../SignOutput/rustdesk-*.msi
- name: Sign rustdesk self-extracted file
@@ -1984,7 +1890,6 @@ jobs:
sudo apt-get install -y libarchive-tools libfuse2
# set-up appimage-builder
# https://github.com/AppImage/AppImageKit/issues/1395
sudo pip3 install "setuptools_scm<10"
sudo pip3 install git+https://github.com/rustdesk-org/appimage-builder.git
# run appimage-builder
pushd appimage
@@ -45,15 +45,16 @@ jobs:
run: |
git clone https://github.com/rustdesk-org/RustDeskTempTopMostWindow RustDeskTempTopMostWindow
# Build. commit 53b548a5398624f7149a382000397993542ad796 is tag v0.3
- name: Build the project
run: |
cd RustDeskTempTopMostWindow && git checkout ecd8d6a139eee76845ea66423fb739af450fda90
cd RustDeskTempTopMostWindow && git checkout 53b548a5398624f7149a382000397993542ad796
msbuild ${{ env.project_path }} -p:Configuration=${{ inputs.configuration }} -p:Platform=${{ inputs.platform }} /p:TargetVersion=${{ inputs.target_version }}
- name: Archive build artifacts
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
if: ${{ inputs.upload-artifact }}
with:
name: topmostwindow-artifacts-${{ inputs.platform }}
name: topmostwindow-artifacts
path: |
./${{ env.build_output_dir }}/WindowInjection.dll
+85
View File
@@ -0,0 +1,85 @@
name: wf-cliprdr CI
on:
workflow_dispatch:
pull_request:
paths:
- "libs/clipboard/src/windows/**"
- "tests/test_invariant_wf_cliprdr.c"
- ".github/workflows/wf-cliprdr-ci.yml"
push:
branches:
- master
paths:
- "libs/clipboard/src/windows/**"
- "tests/test_invariant_wf_cliprdr.c"
- ".github/workflows/wf-cliprdr-ci.yml"
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
test:
name: wf_cliprdr invariant test
runs-on: windows-2022
steps:
- name: Checkout source code
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
persist-credentials: false
- name: Set up MSVC
uses: ilammy/msvc-dev-cmd@0b201ec74fa43914dc39ae48a89fd1d8cb592756
with:
arch: x64
- name: Setup vcpkg with GitHub Actions binary cache
uses: lukka/run-vcpkg@b1a0dd252f06b9e25b3c022a9a03bd7a427fb6a2 # v11
with:
vcpkgDirectory: C:\vcpkg
doNotCache: false
- name: Install vcpkg dependency
shell: pwsh
run: |
& "$env:VCPKG_ROOT\vcpkg.exe" install check:x64-windows --classic --x-install-root="$env:VCPKG_ROOT\installed"
- name: Build test
shell: pwsh
run: |
$testRoot = Join-Path $env:GITHUB_WORKSPACE 'build\wf-cliprdr'
New-Item -ItemType Directory -Force $testRoot | Out-Null
$testSource = (($env:GITHUB_WORKSPACE -replace '\\', '/') + '/tests/test_invariant_wf_cliprdr.c')
$cmakeLists = @(
'cmake_minimum_required(VERSION 3.20)'
'project(test_invariant_wf_cliprdr C)'
''
'set(CMAKE_C_STANDARD 11)'
'set(CMAKE_C_STANDARD_REQUIRED ON)'
'set(CMAKE_C_EXTENSIONS OFF)'
''
'find_package(check CONFIG REQUIRED)'
''
'add_executable(test_invariant_wf_cliprdr'
' "TEST_SOURCE"'
')'
''
'target_link_libraries(test_invariant_wf_cliprdr PRIVATE'
' $<$<TARGET_EXISTS:Check::check>:Check::check>'
' $<$<NOT:$<TARGET_EXISTS:Check::check>>:Check::checkShared>'
')'
) -join [Environment]::NewLine
$cmakeLists.Replace('TEST_SOURCE', $testSource) | Set-Content -NoNewline (Join-Path $testRoot 'CMakeLists.txt')
cmake -S $testRoot -B (Join-Path $testRoot 'out') -G "Visual Studio 17 2022" -A x64 -DCMAKE_TOOLCHAIN_FILE="$env:VCPKG_ROOT\scripts\buildsystems\vcpkg.cmake" -DVCPKG_TARGET_TRIPLET=x64-windows
cmake --build (Join-Path $testRoot 'out') --config Release
- name: Run test
shell: pwsh
run: .\build\wf-cliprdr\out\Release\test_invariant_wf_cliprdr.exe
Generated
+16 -15
View File
@@ -1324,7 +1324,7 @@ dependencies = [
[[package]]
name = "clipboard-master"
version = "4.0.0-beta.6"
source = "git+https://github.com/rustdesk-org/clipboard-master#7762d74e38db37cfeb6ded88c964b9cdbddfb6db"
source = "git+https://github.com/rustdesk-org/clipboard-master#ddc39f00a6211959489ae683aa6ae6eedf03a809"
dependencies = [
"objc",
"objc-foundation",
@@ -2329,7 +2329,7 @@ version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412"
dependencies = [
"libloading 0.7.4",
"libloading 0.8.4",
]
[[package]]
@@ -2694,7 +2694,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
dependencies = [
"libc",
"windows-sys 0.60.2",
"windows-sys 0.52.0",
]
[[package]]
@@ -4494,7 +4494,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e310b3a6b5907f99202fcdb4960ff45b93735d7c7d96b760fcff8db2dc0e103d"
dependencies = [
"cfg-if 1.0.0",
"windows-targets 0.48.5",
"windows-targets 0.52.6",
]
[[package]]
@@ -4695,7 +4695,7 @@ dependencies = [
[[package]]
name = "magnum-opus"
version = "0.4.0"
source = "git+https://github.com/rustdesk-org/magnum-opus#588c6e1f9ed50c3a01fa64f3bd3e7cdb0378a114"
source = "git+https://github.com/rustdesk-org/magnum-opus#5cd2bf989c148662fa3a2d9d539a71d71fd1d256"
dependencies = [
"bindgen 0.59.2",
"pkg-config",
@@ -6673,7 +6673,7 @@ dependencies = [
"once_cell",
"socket2 0.5.10",
"tracing",
"windows-sys 0.60.2",
"windows-sys 0.52.0",
]
[[package]]
@@ -6920,7 +6920,7 @@ dependencies = [
[[package]]
name = "rdev"
version = "0.5.0-2"
source = "git+https://github.com/rustdesk-org/rdev#871bf1c856d6a30af2f56ab8848396a025140855"
source = "git+https://github.com/rustdesk-org/rdev#f9b60b1dd0f3300a1b797d7a74c116683cd232c8"
dependencies = [
"cocoa 0.24.1",
"core-foundation 0.9.4",
@@ -7457,7 +7457,7 @@ dependencies = [
"errno",
"libc",
"linux-raw-sys 0.11.0",
"windows-sys 0.60.2",
"windows-sys 0.52.0",
]
[[package]]
@@ -7514,7 +7514,7 @@ dependencies = [
"security-framework 3.5.1",
"security-framework-sys",
"webpki-root-certs",
"windows-sys 0.60.2",
"windows-sys 0.52.0",
]
[[package]]
@@ -9733,9 +9733,9 @@ dependencies = [
[[package]]
name = "wayland-protocols-wlr"
version = "0.3.9"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "efd94963ed43cf9938a090ca4f7da58eb55325ec8200c3848963e98dc25b78ec"
checksum = "fd993de54a40a40fbe5601d9f1fbcaef0aebcc5fda447d7dc8f6dcbaae4f8953"
dependencies = [
"bitflags 2.9.1",
"wayland-backend",
@@ -10838,15 +10838,16 @@ dependencies = [
[[package]]
name = "wl-clipboard-rs"
version = "0.9.3"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e9651471a32e87d96ef3a127715382b2d11cc7c8bb9822ded8a7cc94072eb0a3"
checksum = "4de22eebb1d1e2bad2d970086e96da0e12cde0b411321e5b0f7b2a1f876aa26f"
dependencies = [
"libc",
"log",
"os_pipe",
"rustix 1.1.2",
"thiserror 2.0.17",
"rustix 0.38.34",
"tempfile",
"thiserror 1.0.61",
"tree_magic_mini",
"wayland-backend",
"wayland-client",
+1 -1
View File
@@ -213,7 +213,7 @@ exclude = ["vdi/host", "examples/custom_plugin"]
libxdo-sys = { path = "libs/libxdo-sys-stub" }
[package.metadata.winres]
LegalCopyright = "Copyright © 2026 Purslane Tech Pte. Ltd. All rights reserved."
LegalCopyright = "Copyright © 2025 Purslane Ltd. All rights reserved."
ProductName = "RustDesk"
FileDescription = "RustDesk Remote Desktop"
OriginalFilename = "rustdesk.exe"
+2 -9
View File
@@ -17,8 +17,7 @@ osx = platform.platform().startswith(
hbb_name = 'rustdesk' + ('.exe' if windows else '')
exe_path = 'target/release/' + hbb_name
if windows:
win_arch = 'arm64' if platform.machine().lower() in ('arm64', 'aarch64') else 'x64'
flutter_build_dir = f'build/windows/{win_arch}/runner/Release/'
flutter_build_dir = 'build/windows/x64/runner/Release/'
elif osx:
flutter_build_dir = 'build/macos/Build/Products/Release/'
else:
@@ -411,12 +410,7 @@ def build_flutter_dmg(version, features):
system2(
"cp target/release/liblibrustdesk.dylib target/release/librustdesk.dylib")
os.chdir('flutter')
# cargo builds a single-arch dylib for the host; restrict Xcode to the same arch
# so the universal-by-default ARCHS_STANDARD doesn't try to link a missing slice.
# FLUTTER_XCODE_* env vars are forwarded to xcodebuild as build settings.
mac_arch = 'arm64' if platform.machine().lower() in ('arm64', 'aarch64') else 'x86_64'
system2(
f'FLUTTER_XCODE_ARCHS={mac_arch} FLUTTER_XCODE_ONLY_ACTIVE_ARCH=YES flutter build macos --release')
system2('flutter build macos --release')
system2('cp -rf ../target/release/service ./build/macos/Build/Products/Release/RustDesk.app/Contents/MacOS/')
'''
system2(
@@ -512,7 +506,6 @@ def main():
'target\\release\\rustdesk.exe')
else:
print('Not signed')
os.makedirs(res_dir, exist_ok=True)
system2(
f'cp -rf target/release/RustDesk.exe {res_dir}')
os.chdir('libs/portable')
@@ -8,7 +8,6 @@ package com.carriez.flutter_hbb
import android.accessibilityservice.AccessibilityService
import android.accessibilityservice.GestureDescription
import android.content.Intent
import android.graphics.Path
import android.os.Build
import android.os.Bundle
@@ -69,16 +68,6 @@ class InputService : AccessibilityService() {
get() = ctx != null
}
private fun notifyInputState() {
val inputState = isOpen.toString()
Handler(Looper.getMainLooper()).post {
MainActivity.flutterMethodChannel?.invokeMethod(
"on_state_changed",
mapOf("name" to "input", "value" to inputState)
)
}
}
private val logTag = "input service"
private var leftIsDown = false
private var touchPath = Path()
@@ -727,7 +716,6 @@ class InputService : AccessibilityService() {
override fun onServiceConnected() {
super.onServiceConnected()
ctx = this
notifyInputState()
val info = AccessibilityServiceInfo()
if (Build.VERSION.SDK_INT >= 33) {
info.flags = FLAG_INPUT_METHOD_EDITOR or FLAG_RETRIEVE_INTERACTIVE_WINDOWS
@@ -746,16 +734,8 @@ class InputService : AccessibilityService() {
override fun onDestroy() {
ctx = null
// Keep this fallback even though onUnbind usually notifies first.
notifyInputState()
super.onDestroy()
}
override fun onUnbind(intent: Intent?): Boolean {
ctx = null
notifyInputState()
return super.onUnbind(intent)
}
override fun onInterrupt() {}
}
@@ -200,13 +200,12 @@ class MainActivity : FlutterActivity() {
"stop_input" -> {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
InputService.ctx?.disableSelf()
} else {
InputService.ctx = null
Companion.flutterMethodChannel?.invokeMethod(
"on_state_changed",
mapOf("name" to "input", "value" to InputService.isOpen.toString())
)
}
InputService.ctx = null
Companion.flutterMethodChannel?.invokeMethod(
"on_state_changed",
mapOf("name" to "input", "value" to InputService.isOpen.toString())
)
result.success(true)
}
"cancel_notification" -> {
-7
View File
@@ -1,7 +0,0 @@
<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>

Before

Width:  |  Height:  |  Size: 303 B

+18 -40
View File
@@ -72,24 +72,10 @@ Widget waylandKeyboardScopeChip(BuildContext context, String text) {
);
}
bool _isWindowsMode1PrivacyImpl(String privacyModeImpl) {
return privacyModeImpl == kPrivacyModeImplMag ||
privacyModeImpl == kPrivacyModeImplExcludeFromCapture;
}
// macOS privacy mode blacks out all online displays. Windows Mode 1 also
// covers every local monitor with privacy overlay windows, so remote display
// switching does not weaken local privacy protection.
//
// Keep this separate from the capture backend capability. The legacy Windows
// magnifier capturer is not reliable for multi-monitor capture; WebRTC's
// screen_capturer_win_magnifier also disables it when SM_CMONITORS != 1:
// https://webrtc.googlesource.com/src/+/1845922d5a1bf9c27deeffb4a8c8daea124434c1/modules/desktop_capture/win/screen_capturer_win_magnifier.cc
bool allowDisplaySwitchInPrivacyMode(PeerInfo pi, String privacyModeImpl) {
return pi.platform == kPeerPlatformMacOS ||
(pi.platform == kPeerPlatformWindows &&
_isWindowsMode1PrivacyImpl(privacyModeImpl) &&
versionCmp(pi.version, '1.4.8') >= 0);
// macOS privacy mode blacks out all online displays, so switching the remote
// display does not weaken the local privacy protection.
bool allowDisplaySwitchInPrivacyMode(PeerInfo pi) {
return pi.platform == kPeerPlatformMacOS;
}
class TTextMenu {
@@ -978,8 +964,7 @@ Future<List<TToggleMenu>> toolbarDisplayToggle(
final privacyModeState = PrivacyModeState.find(id);
if (pi.isSupportMultiDisplay &&
(privacyModeState.isEmpty ||
allowDisplaySwitchInPrivacyMode(pi, privacyModeState.value)) &&
(privacyModeState.isEmpty || allowDisplaySwitchInPrivacyMode(pi)) &&
pi.displaysCount.value > 1 &&
bind.mainGetUserDefaultOption(key: kKeyShowMonitorsToolbar) == 'Y') {
final value =
@@ -1063,20 +1048,7 @@ List<TToggleMenu> toolbarPrivacyMode(
return []; // No permission and not active, hide options.
}
bool checkDisplayAllowedForPrivacyMode(String targetImplKey, bool turnOn) {
if (!turnOn ||
allowDisplaySwitchInPrivacyMode(pi, targetImplKey) ||
(ffiModel.pi.currentDisplay == 0 &&
!bind.sessionIsMultiUiSession(sessionId: sessionId))) {
return true;
}
msgBox(sessionId, 'custom-nook-nocancel-hasclose', 'info',
'Please switch to Display 1 first', '', ffi.dialogManager);
return false;
}
getDefaultMenu(Future<void> Function(SessionID sid, String opt) toggleFunc,
String targetImplKey) {
getDefaultMenu(Future<void> Function(SessionID sid, String opt) toggleFunc) {
final enabled = !ffiModel.viewOnly &&
(hasPrivacyModePermission || privacyModeState.isNotEmpty);
return TToggleMenu(
@@ -1084,7 +1056,16 @@ List<TToggleMenu> toolbarPrivacyMode(
onChanged: enabled
? (value) {
if (value == null) return;
if (!checkDisplayAllowedForPrivacyMode(targetImplKey, value)) {
if (!allowDisplaySwitchInPrivacyMode(pi) &&
ffiModel.pi.currentDisplay != 0 &&
ffiModel.pi.currentDisplay != kAllDisplayValue) {
msgBox(
sessionId,
'custom-nook-nocancel-hasclose',
'info',
'Please switch to Display 1 first',
'',
ffi.dialogManager);
return;
}
final option = 'privacy-mode';
@@ -1102,7 +1083,7 @@ List<TToggleMenu> toolbarPrivacyMode(
getDefaultMenu((sid, opt) async {
bind.sessionToggleOption(sessionId: sid, value: opt);
togglePrivacyModeTime = DateTime.now();
}, kPrivacyModeImplMag)
})
];
}
if (privacyModeImpls.isEmpty) {
@@ -1116,7 +1097,7 @@ List<TToggleMenu> toolbarPrivacyMode(
bind.sessionTogglePrivacyMode(
sessionId: sid, implKey: implKey, on: privacyModeState.isEmpty);
togglePrivacyModeTime = DateTime.now();
}, implKey)
})
];
} else {
final visibleImpls = hasPrivacyModePermission
@@ -1137,9 +1118,6 @@ List<TToggleMenu> toolbarPrivacyMode(
? (value) {
if (value == null) return;
if (value && !hasPrivacyModePermission) return;
if (!checkDisplayAllowedForPrivacyMode(implKey, value)) {
return;
}
togglePrivacyModeTime = DateTime.now();
bind.sessionTogglePrivacyMode(
sessionId: sessionId, implKey: implKey, on: value);
-6
View File
@@ -29,10 +29,6 @@ const String kPlatformAdditionsHasFileClipboard = "has_file_clipboard";
const String kPlatformAdditionsSupportedPrivacyModeImpl =
"supported_privacy_mode_impl";
const String kPrivacyModeImplMag = 'privacy_mode_impl_mag';
const String kPrivacyModeImplExcludeFromCapture =
'privacy_mode_impl_exclude_from_capture';
const String kPeerPlatformWindows = "Windows";
const String kPeerPlatformLinux = "Linux";
const String kPeerPlatformMacOS = "Mac OS";
@@ -174,8 +170,6 @@ 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,7 +407,6 @@ 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) {
@@ -483,15 +482,13 @@ class _GeneralState extends State<_General> {
}
Widget other() {
final incomingOnly = bind.isIncomingOnly();
final outgoingOnly = bind.isOutgoingOnly();
final showAutoUpdate = isWindows && bind.mainIsInstalled();
final children = <Widget>[
if (!isWeb && !incomingOnly)
if (!isWeb && !bind.isIncomingOnly())
_OptionCheckBox(context, 'Confirm before closing multiple tabs',
kOptionEnableConfirmClosingTabs,
isServer: false),
if (!incomingOnly)
if (!bind.isIncomingOnly())
_OptionCheckBox(
context,
'allow-remote-toolbar-docking-any-edge',
@@ -501,10 +498,9 @@ class _GeneralState extends State<_General> {
reloadAllWindows();
},
),
if (!isWeb && !outgoingOnly)
_OptionCheckBox(context, 'Adaptive bitrate', kOptionEnableAbr),
_OptionCheckBox(context, 'Adaptive bitrate', kOptionEnableAbr),
if (!isWeb) wallpaper(),
if (!isWeb && !incomingOnly) ...[
if (!isWeb && !bind.isIncomingOnly()) ...[
_OptionCheckBox(
context,
'Open connection in new tab',
@@ -543,40 +539,40 @@ class _GeneralState extends State<_General> {
isServer: false,
),
),
],
if (!isWeb && !bind.isCustomClient())
_OptionCheckBox(
context,
'Check for software update on startup',
kOptionEnableCheckUpdate,
isServer: false,
),
if (showAutoUpdate)
_OptionCheckBox(
context,
'Auto update',
kOptionAllowAutoUpdate,
isServer: true,
),
if (isWindows && !outgoingOnly)
_OptionCheckBox(
context,
'Capture screen using DirectX',
kOptionDirectxCapture,
),
if (!isWeb && !incomingOnly) ...[
_OptionCheckBox(
context,
'Enable UDP hole punching',
kOptionEnableUdpPunch,
isServer: false,
),
_OptionCheckBox(
context,
'Enable IPv6 P2P connection',
kOptionEnableIpv6Punch,
isServer: false,
),
if (!isWeb && !bind.isCustomClient())
_OptionCheckBox(
context,
'Check for software update on startup',
kOptionEnableCheckUpdate,
isServer: false,
),
if (showAutoUpdate)
_OptionCheckBox(
context,
'Auto update',
kOptionAllowAutoUpdate,
isServer: true,
),
if (isWindows && !bind.isOutgoingOnly())
_OptionCheckBox(
context,
'Capture screen using DirectX',
kOptionDirectxCapture,
),
if (!bind.isIncomingOnly()) ...[
_OptionCheckBox(
context,
'Enable UDP hole punching',
kOptionEnableUdpPunch,
isServer: false,
),
_OptionCheckBox(
context,
'Enable IPv6 P2P connection',
kOptionEnableIpv6Punch,
isServer: false,
),
],
],
];
@@ -609,47 +605,6 @@ 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);
}
@@ -2474,7 +2429,7 @@ class _AboutState extends State<_About> {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Copyright © ${DateTime.now().toString().substring(0, 4)} Purslane Tech Pte. Ltd.\n$license',
'Copyright © ${DateTime.now().toString().substring(0, 4)} Purslane Ltd.\n$license',
style: const TextStyle(color: Colors.white),
),
Text(
@@ -95,13 +95,6 @@ class _TerminalPageState extends State<TerminalPage>
// Register this terminal model with FFI for event routing
_ffi.registerTerminalModel(widget.terminalId, _terminalModel);
// Auto-close tab when shell exits
_terminalModel.onClosed = () {
if (mounted) {
widget.tabController.closeBy(widget.tabKey);
}
};
// Initialize terminal connection
WidgetsBinding.instance.addPostFrameCallback((_) {
widget.tabController.onSelected?.call(widget.id);
+5 -175
View File
@@ -779,7 +779,6 @@ class _RemoteToolbarState extends State<RemoteToolbar> {
borderRadius: borderRadius,
child: _DraggableShowHide(
id: widget.id,
ffi: widget.ffi,
sessionId: widget.ffi.sessionId,
dragging: _dragging,
fraction: _fraction,
@@ -806,25 +805,13 @@ 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(() {
final privacyModeState = PrivacyModeState.find(widget.id);
if ((privacyModeState.isEmpty ||
allowDisplaySwitchInPrivacyMode(pi, privacyModeState.value)) &&
if ((PrivacyModeState.find(widget.id).isEmpty ||
allowDisplaySwitchInPrivacyMode(pi)) &&
pi.displaysCount.value > 1) {
return _MonitorMenu(
id: widget.id,
@@ -977,88 +964,6 @@ 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;
@@ -1167,8 +1072,8 @@ class _MonitorMenu extends StatelessWidget {
tooltip: isMulti
? ''
: isAllMonitors
? 'All monitors'
: '#{${i + 1}} monitor',
? 'all monitors'
: '#${i + 1} monitor',
hMargin: isMulti ? null : 6,
vMargin: isMulti ? null : 12,
topLevel: false,
@@ -2852,7 +2757,7 @@ class _IconMenuButtonState extends State<_IconMenuButton> {
horizontal: widget.hMargin ?? _ToolbarTheme.buttonHMargin,
vertical: widget.vMargin ?? _ToolbarTheme.buttonVMargin);
button = Tooltip(
message: translate(widget.tooltip),
message: widget.tooltip,
child: button,
);
if (widget.topLevel) {
@@ -3065,7 +2970,6 @@ 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;
@@ -3089,7 +2993,6 @@ class _DraggableShowHide extends StatefulWidget {
const _DraggableShowHide({
Key? key,
required this.id,
required this.ffi,
required this.sessionId,
required this.fraction,
required this.edge,
@@ -3346,9 +3249,6 @@ 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);
@@ -3509,73 +3409,3 @@ 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,
),
),
],
),
),
);
});
}
}
+3 -5
View File
@@ -1220,11 +1220,7 @@ void showOptions(
if (image != null) {
displays.add(Padding(padding: const EdgeInsets.only(top: 8), child: image));
}
final privacyModeState = PrivacyModeState.find(id);
if (pi.displays.length > 1 &&
pi.currentDisplay != kAllDisplayValue &&
(privacyModeState.isEmpty ||
allowDisplaySwitchInPrivacyMode(pi, privacyModeState.value))) {
if (pi.displays.length > 1 && pi.currentDisplay != kAllDisplayValue) {
final cur = pi.currentDisplay;
final children = <Widget>[];
final isDarkTheme = MyTheme.currentThemeMode() == ThemeMode.dark;
@@ -1278,6 +1274,8 @@ void showOptions(
await toolbarDisplayToggle(context, id, gFFI);
List<TToggleMenu> privacyModeList = [];
// privacy mode
final privacyModeState = PrivacyModeState.find(id);
if ((gFFI.ffiModel.pi.features.privacyMode && gFFI.ffiModel.keyboard) ||
privacyModeState.isNotEmpty) {
privacyModeList = toolbarPrivacyMode(privacyModeState, context, id, gFFI);
@@ -83,13 +83,6 @@ class _TerminalPageState extends State<TerminalPage>
// Register this terminal model with FFI for event routing
_ffi.registerTerminalModel(widget.terminalId, _terminalModel);
// Auto-close connection when shell exits
_terminalModel.onClosed = () {
if (mounted) {
closeConnection(id: widget.id);
}
};
// Web desktop users have full hardware keyboard access, so the on-screen
// terminal extra keys bar is unnecessary and disabled.
_showTerminalExtraKeys = !isWebDesktop &&
+13 -164
View File
@@ -142,22 +142,12 @@ class FileModel {
}
Future<void> postOverrideFileConfirm(Map<String, dynamic> evt) async {
final id = int.tryParse(evt['id']?.toString() ?? '');
if (id == null || !jobController.hasTransferConflictJob(id)) {
debugPrint("Ignore stale override confirm event: $evt");
return;
}
evtLoop.pushEvent(
_FileDialogEvent(WeakReference(this), FileDialogType.overwrite, evt));
}
Future<void> overrideFileConfirm(Map<String, dynamic> evt,
{bool? overrideConfirm, bool skip = false}) async {
final id = int.tryParse(evt['id']?.toString() ?? '') ?? 0;
if (id == 0 || !jobController.hasTransferConflictJob(id)) {
debugPrint("Ignore override confirm for inactive job: $evt");
return;
}
// If `skip == true`, it means to skip this file without showing dialog.
// Because `resp` may be null after the user operation or the last remembered operation,
// and we should distinguish them.
@@ -166,12 +156,15 @@ class FileModel {
? await showFileConfirmDialog(translate("Overwrite"),
"${evt['read_path']}", true, evt['is_identical'] == "true")
: null);
if (!jobController.hasTransferConflictJob(id)) {
debugPrint("Ignore override confirm result for inactive job: $evt");
return;
}
final id = int.tryParse(evt['id']) ?? 0;
if (false == resp) {
await jobController.cancelTransferConflictBatch(id);
final jobIndex = jobController.getJob(id);
if (jobIndex != -1) {
await jobController.cancelJob(id);
final job = jobController.jobTable[jobIndex];
job.state = JobState.done;
jobController.jobTable.refresh();
}
} else {
var need_override = false;
if (resp == null) {
@@ -183,7 +176,6 @@ class FileModel {
}
// Update the loop config.
if (fileConfirmCheckboxRemember) {
jobController.rememberTransferConflictBatch(id, resp);
evtLoop.setSkip(!need_override);
}
await bind.sessionSetConfirmOverrideFile(
@@ -293,8 +285,6 @@ class FileModel {
final isWindows = otherSideData.options.isWindows;
final showHidden = otherSideData.options.showHidden;
final jobID = jobController.addTransferJob(entry, false);
jobController.registerTransferConflictBatch([jobID],
batchId: int.tryParse(obj['batchId']?.toString() ?? ''));
webSendLocalFiles(
handleIndex: handleIndex,
actId: jobID,
@@ -580,15 +570,8 @@ class FileController {
final toPath = otherSideData.directory.path;
final isWindows = otherSideData.options.isWindows;
final showHidden = otherSideData.options.showHidden;
final transferJobs = <(Entry, int)>[];
final transferJobIds = <int>[];
for (var from in items.items) {
final jobID = jobController.addTransferJob(from, isRemoteToLocal);
transferJobs.add((from, jobID));
transferJobIds.add(jobID);
}
jobController.registerTransferConflictBatch(transferJobIds);
for (final (from, jobID) in transferJobs) {
bind.sessionSendFiles(
sessionId: sessionId,
actId: jobID,
@@ -934,10 +917,6 @@ class JobController {
static final JobID jobID = JobID();
final jobTable = List<JobProgress>.empty(growable: true).obs;
final jobResultListener = JobResultListener<Map<String, dynamic>>();
int _nextTransferConflictBatchId = 1;
final Map<int, int> _transferConflictJobToBatch = {};
int? _transferConflictRememberBatchId;
bool? _transferConflictRememberOverrideConfirm;
final GetSessionID getSessionID;
final GetDialogManager getDialogManager;
SessionID get sessionId => getSessionID();
@@ -950,57 +929,6 @@ class JobController {
return jobTable.indexWhere((element) => element.id == id);
}
void registerTransferConflictBatch(Iterable<int> jobIds, {int? batchId}) {
final ids = jobIds.toList(growable: false);
if (ids.isEmpty) {
return;
}
batchId ??= _nextTransferConflictBatchId++;
if (batchId >= _nextTransferConflictBatchId) {
_nextTransferConflictBatchId = batchId + 1;
}
for (final jobId in ids) {
_transferConflictJobToBatch[jobId] = batchId;
}
}
int? transferConflictBatchId(int jobId) {
return _transferConflictJobToBatch[jobId];
}
bool hasTransferConflictJob(int jobId) {
return transferConflictBatchId(jobId) != null;
}
bool isTransferConflictRememberBatch(int? batchId) {
return batchId != null && batchId == _transferConflictRememberBatchId;
}
bool? transferConflictRememberOverrideConfirm(int? batchId) {
if (!isTransferConflictRememberBatch(batchId)) {
return null;
}
return _transferConflictRememberOverrideConfirm;
}
void rememberTransferConflictBatch(int jobId, bool? overrideConfirm) {
_transferConflictRememberBatchId = _transferConflictJobToBatch[jobId];
_transferConflictRememberOverrideConfirm = overrideConfirm;
}
void unregisterTransferConflictJob(int jobId) {
final batchId = _transferConflictJobToBatch.remove(jobId);
if (batchId == null) {
return;
}
if (!_transferConflictJobToBatch.containsValue(batchId)) {
if (_transferConflictRememberBatchId == batchId) {
_transferConflictRememberBatchId = null;
_transferConflictRememberOverrideConfirm = null;
}
}
}
// return jobID
int addTransferJob(Entry from, bool isRemoteToLocal) {
final jobID = JobController.jobID.next();
@@ -1072,10 +1000,7 @@ class JobController {
id = int.parse(evt['id']);
} catch (_) {}
final jobIndex = getJob(id);
if (jobIndex == -1) {
unregisterTransferConflictJob(id);
return true;
}
if (jobIndex == -1) return true;
final job = jobTable[jobIndex];
job.recvJobRes = true;
if (job.type == JobType.deleteFile) {
@@ -1101,9 +1026,6 @@ class JobController {
job.state = JobState.done;
}
jobTable.refresh();
if (job.state == JobState.done || job.state == JobState.error) {
unregisterTransferConflictJob(id);
}
if (job.type == JobType.deleteDir) {
return job.state == JobState.done;
} else {
@@ -1113,15 +1035,9 @@ class JobController {
void jobError(Map<String, dynamic> evt) {
final err = evt['err'].toString();
final id = int.tryParse(evt['id']?.toString() ?? '');
if (id == null) {
debugPrint("Ignore job error with invalid id: $evt");
return;
}
int jobIndex = getJob(id);
int jobIndex = getJob(int.parse(evt['id']));
if (jobIndex != -1) {
final job = jobTable[jobIndex];
if (job.state == JobState.done && job.err == "cancel") return;
job.state = JobState.error;
job.err = err;
job.recvJobRes = true;
@@ -1144,11 +1060,6 @@ class JobController {
}
}
jobTable.refresh();
if (job.state == JobState.done || job.state == JobState.error) {
unregisterTransferConflictJob(job.id);
}
} else {
unregisterTransferConflictJob(id);
}
if (err == _kOneWayFileTransferError) {
if (DateTime.now().millisecondsSinceEpoch - _lastTimeShowMsgbox > 3000) {
@@ -1185,42 +1096,9 @@ class JobController {
}
Future<void> cancelJob(int id) async {
unregisterTransferConflictJob(id);
await bind.sessionCancelJob(sessionId: sessionId, actId: id);
}
Future<void> cancelTransferConflictBatch(int jobId) async {
final batchId = _transferConflictJobToBatch[jobId];
final batchJobIds = batchId == null ? [jobId] : <int>[];
if (batchId != null) {
for (final entry in _transferConflictJobToBatch.entries) {
if (entry.value == batchId) {
batchJobIds.add(entry.key);
}
}
for (final id in batchJobIds) {
unregisterTransferConflictJob(id);
}
}
final jobIdsToCancel = batchJobIds.toSet();
for (final job in jobTable) {
if (!jobIdsToCancel.contains(job.id) || job.state == JobState.done) {
continue;
}
job.state = JobState.done;
job.err = "cancel";
job.recvJobRes = true;
}
jobTable.refresh();
for (final id in batchJobIds) {
try {
await bind.sessionCancelJob(sessionId: sessionId, actId: id);
} catch (e) {
debugPrint("Failed to cancel transfer job $id in conflict batch: $e");
}
}
}
Future<void> loadLastJob(Map<String, dynamic> evt) async {
debugPrint("load last job: $evt");
Map<String, dynamic> jobDetail = json.decode(evt['value']);
@@ -1267,7 +1145,7 @@ class JobController {
..state = JobState.paused;
jobTable.add(jobProgress);
}
registerTransferConflictBatch([currJobId]);
await bind.sessionAddJob(
sessionId: sessionId,
isRemote: isRemote,
@@ -1315,9 +1193,6 @@ class JobController {
void clear() {
jobTable.clear();
_transferConflictJobToBatch.clear();
_transferConflictRememberBatchId = null;
_transferConflictRememberOverrideConfirm = null;
jobResultListener.clear();
}
}
@@ -1660,9 +1535,6 @@ class JobProgress {
String display() {
if (type == JobType.transfer) {
if (state == JobState.done && err == "cancel") {
return translate("Cancel");
}
if (state == JobState.done && err == "skipped") {
return translate("Skipped");
}
@@ -1972,44 +1844,21 @@ class _FileDialogEvent extends BaseEvent<FileDialogType, Map<String, dynamic>> {
class FileDialogEventLoop
extends BaseEventLoop<FileDialogType, Map<String, dynamic>> {
int? _batchId;
bool? _overrideConfirm;
bool _skip = false;
@override
Future<void> onPreConsume(
BaseEvent<FileDialogType, Map<String, dynamic>> evt) async {
final event = evt as _FileDialogEvent;
final model = event.fileModel.target;
final jobId = int.tryParse(evt.data['id']?.toString() ?? '');
final batchId = model == null || jobId == null
? null
: model.jobController.transferConflictBatchId(jobId);
final keepRemembered = model != null &&
model.jobController.isTransferConflictRememberBatch(batchId);
// The loop only preloads the remembered batch choice. The model updates it
// after the user answers the current overwrite dialog.
if (_batchId != batchId && !keepRemembered) {
_batchId = batchId;
_overrideConfirm = null;
_skip = false;
} else {
_batchId = batchId;
}
if (keepRemembered) {
_overrideConfirm =
model.jobController.transferConflictRememberOverrideConfirm(batchId);
_skip = _overrideConfirm == null;
}
var event = evt as _FileDialogEvent;
event.setOverrideConfirm(_overrideConfirm);
event.setSkip(_skip);
debugPrint(
"FileDialogEventLoop: consuming<jobId: ${evt.data['id']} batchId: $_batchId overrideConfirm: $_overrideConfirm, skip: $_skip>");
"FileDialogEventLoop: consuming<jobId: ${evt.data['id']} overrideConfirm: $_overrideConfirm, skip: $_skip>");
}
@override
Future<void> onEventsClear() {
_batchId = null;
_overrideConfirm = null;
_skip = false;
return super.onEventsClear();
+4 -35
View File
@@ -1307,8 +1307,7 @@ class InputModel {
}
if (isPhysicalMouse.value) {
if (!_relativeMouse.handleRelativeMouseMove(e.localPosition)) {
final canvasPosition = _pointerPositionForRemoteCanvas(e);
handleMouse(_getMouseEvent(e, _kMouseEventMove), canvasPosition,
handleMouse(_getMouseEvent(e, _kMouseEventMove), e.position,
edgeScroll: useEdgeScroll);
}
}
@@ -1549,8 +1548,7 @@ class InputModel {
_relativeMouse
.sendRelativeMouseButton(_getMouseEvent(e, _kMouseEventDown));
} else {
final canvasPosition = _pointerPositionForRemoteCanvas(e);
handleMouse(_getMouseEvent(e, _kMouseEventDown), canvasPosition);
handleMouse(_getMouseEvent(e, _kMouseEventDown), e.position);
}
}
}
@@ -1572,8 +1570,7 @@ class InputModel {
_relativeMouse
.sendRelativeMouseButton(_getMouseEvent(e, _kMouseEventUp));
} else {
final canvasPosition = _pointerPositionForRemoteCanvas(e);
handleMouse(_getMouseEvent(e, _kMouseEventUp), canvasPosition);
handleMouse(_getMouseEvent(e, _kMouseEventUp), e.position);
}
}
}
@@ -1595,40 +1592,12 @@ class InputModel {
}
if (isPhysicalMouse.value) {
if (!_relativeMouse.handleRelativeMouseMove(e.localPosition)) {
final canvasPosition = _pointerPositionForRemoteCanvas(e);
handleMouse(_getMouseEvent(e, _kMouseEventMove), canvasPosition,
handleMouse(_getMouseEvent(e, _kMouseEventMove), e.position,
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 =
+2 -34
View File
@@ -1,6 +1,7 @@
import 'dart:async';
import 'dart:convert';
import 'package:desktop_multi_window/desktop_multi_window.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hbb/common.dart';
import 'package:flutter_hbb/consts.dart';
@@ -37,10 +38,6 @@ class TerminalModel with ChangeNotifier {
void Function(int w, int h, int pw, int ph)? onResizeExternal;
/// Called when the terminal session ends (shell exits).
/// The listener (typically TerminalPage) can use this to auto-close the tab/page.
VoidCallback? onClosed;
Future<void> _handleInput(String data) async {
// Soft keyboards (notably iOS) emit '\n' when Enter is pressed, while a
// real keyboard's Enter sends '\r'. Some Android keyboards also emit '\n'.
@@ -250,33 +247,6 @@ class TerminalModel with ChangeNotifier {
}
}
static int getExitCodeFromEvt(Map<String, dynamic> evt) {
if (evt.containsKey('exit_code')) {
final v = evt['exit_code'];
if (v is int) {
// Desktop and mobile send exit_code as an int
return v;
} else if (v is String) {
// Web sends exit_code as a string
final parsed = int.tryParse(v);
if (parsed != null) {
return parsed;
} else {
debugPrint(
'[TerminalModel] Failed to parse exit_code as integer: $v. Expected a numeric string.');
return 0;
}
} else {
debugPrint(
'[TerminalModel] Unexpected exit_code type: ${v.runtimeType}, value: $v. Expected int or String.');
return 0;
}
} else {
debugPrint('[TerminalModel] Event does not contain exit_code');
return 0;
}
}
void handleTerminalResponse(Map<String, dynamic> evt) {
final String? type = evt['type'];
final int evtTerminalId = getTerminalIdFromEvt(evt);
@@ -499,12 +469,10 @@ class TerminalModel with ChangeNotifier {
}
void _handleTerminalClosed(Map<String, dynamic> evt) {
final int exitCode = getExitCodeFromEvt(evt);
final int exitCode = evt['exit_code'] ?? 0;
_writeToTerminal('\r\nTerminal closed with exit code: $exitCode\r\n');
_terminalOpened = false;
notifyListeners();
// Auto-close the tab/page
onClosed?.call();
}
void _handleTerminalError(Map<String, dynamic> evt) {
@@ -11,4 +11,4 @@ PRODUCT_NAME = RustDesk
PRODUCT_BUNDLE_IDENTIFIER = com.carriez.flutterHbb
// The copyright displayed in application information
PRODUCT_COPYRIGHT = Copyright © 2026 Purslane Tech Pte. Ltd. All rights reserved.
PRODUCT_COPYRIGHT = Copyright © 2025 Purslane Ltd. All rights reserved.
+2 -2
View File
@@ -89,11 +89,11 @@ BEGIN
BEGIN
BLOCK "040904e4"
BEGIN
VALUE "CompanyName", "Purslane Tech Pte. Ltd." "\0"
VALUE "CompanyName", "Purslane Ltd" "\0"
VALUE "FileDescription", "RustDesk Remote Desktop" "\0"
VALUE "FileVersion", VERSION_AS_STRING "\0"
VALUE "InternalName", "rustdesk" "\0"
VALUE "LegalCopyright", "Copyright © 2026 Purslane Tech Pte. Ltd. All rights reserved." "\0"
VALUE "LegalCopyright", "Copyright © 2025 Purslane Ltd. All rights reserved." "\0"
VALUE "OriginalFilename", "rustdesk.exe" "\0"
VALUE "ProductName", "RustDesk" "\0"
VALUE "ProductVersion", VERSION_AS_STRING "\0"
+10 -3
View File
@@ -533,7 +533,7 @@ impl FuseServer {
offset: i64,
size: u32,
) -> Result<Vec<u8>, std::io::Error> {
let request_stream_id = rand::random();
// todo: async and concurrent read, generate stream_id per request
let cb_requested = unsafe {
// convert `size` from u32 to i32
// yet with same bit representation
@@ -543,7 +543,7 @@ impl FuseServer {
let (n_position_high, n_position_low) =
((offset >> 32) as i32, (offset & (u32::MAX as i64)) as i32);
let request = ClipboardFile::FileContentsRequest {
stream_id: request_stream_id,
stream_id: node.stream_id,
list_index: node.index as i32,
dw_flags: 2,
n_position_low,
@@ -573,7 +573,7 @@ impl FuseServer {
stream_id,
requested_data,
} => {
if stream_id != request_stream_id {
if stream_id != node.stream_id {
log::debug!("stream id mismatch, ignore");
continue;
}
@@ -611,6 +611,11 @@ struct FuseNode {
/// connection id
pub conn_id: i32,
// todo: use stream_id to identify a FileContents request-reply
// instead of a whole file
/// stream id
pub stream_id: i32,
/// file index in peer's file list
/// NOTE:
/// it is NOT the same as inode, this is the index in the file list
@@ -634,6 +639,7 @@ impl FuseNode {
pub fn from_description(inode: Inode, desc: FileDescription) -> Self {
Self {
conn_id: desc.conn_id,
stream_id: rand::random(),
index: inode as usize - 2,
name: desc
.name
@@ -650,6 +656,7 @@ impl FuseNode {
pub fn new_root() -> Self {
Self {
conn_id: 0,
stream_id: rand::random(),
index: 0,
name: String::from("/"),
parent: None,
+50 -280
View File
@@ -4,7 +4,7 @@ use super::filetype::FileDescription;
use crate::{ClipboardFile, CliprdrError};
use cs::FuseServer;
use fuser::MountOption;
use hbb_common::{config::Config, log};
use hbb_common::{config::APP_NAME, log};
use parking_lot::Mutex;
use std::{
io,
@@ -15,13 +15,13 @@ use std::{
lazy_static::lazy_static! {
static ref FUSE_MOUNT_POINT_CLIENT: Arc<String> = {
let mnt_path = fuse_mount_point("cliprdr-client");
let mnt_path = format!("/tmp/{}/{}", APP_NAME.read().unwrap(), "cliprdr-client");
// No need to run `canonicalize()` here.
Arc::new(mnt_path)
};
static ref FUSE_MOUNT_POINT_SERVER: Arc<String> = {
let mnt_path = fuse_mount_point("cliprdr-server");
let mnt_path = format!("/tmp/{}/{}", APP_NAME.read().unwrap(), "cliprdr-server");
// No need to run `canonicalize()` here.
Arc::new(mnt_path)
};
@@ -32,21 +32,6 @@ lazy_static::lazy_static! {
static FUSE_TIMEOUT: Duration = Duration::from_secs(3);
#[derive(Debug, PartialEq, Eq)]
enum MountPointState {
HealthyMount,
NotMounted,
StaleMount,
Unknown,
}
fn fuse_mount_point(name: &str) -> String {
let mut path = PathBuf::from(Config::ipc_path(""));
path.pop();
path.push(name);
path.to_string_lossy().to_string()
}
pub fn get_exclude_paths(is_client: bool) -> Arc<String> {
if is_client {
FUSE_MOUNT_POINT_CLIENT.clone()
@@ -70,26 +55,15 @@ pub fn init_fuse_context(is_client: bool) -> Result<(), CliprdrError> {
FUSE_CONTEXT_SERVER.lock()
};
if let Some(ctx) = fuse_context_lock.as_ref() {
match inspect_mount_point_state(&ctx.mount_point) {
MountPointState::HealthyMount => return Ok(()),
MountPointState::StaleMount | MountPointState::NotMounted => {
log::warn!(
"clipboard FUSE mount {} is disconnected, remounting",
ctx.mount_point.display()
);
let stale_context = fuse_context_lock.take();
drop(fuse_context_lock);
drop(stale_context);
return init_fuse_context(is_client);
}
MountPointState::Unknown => {
log::warn!(
"failed to verify clipboard FUSE mount {}",
ctx.mount_point.display()
);
return Err(CliprdrError::CliprdrInit);
}
if is_mount_point_healthy(&ctx.mount_point) {
return Ok(());
}
log::warn!(
"clipboard FUSE mount {} is disconnected, remounting",
ctx.mount_point.display()
);
let stale_context = fuse_context_lock.take();
drop(stale_context);
}
let mount_point = if is_client {
FUSE_MOUNT_POINT_CLIENT.clone()
@@ -98,32 +72,10 @@ pub fn init_fuse_context(is_client: bool) -> Result<(), CliprdrError> {
};
let mount_point = std::path::PathBuf::from(&*mount_point);
match inspect_mount_point_state(&mount_point) {
MountPointState::HealthyMount => {
log::warn!(
"clipboard FUSE mount {} is already active in another context",
mount_point.display()
);
return Err(CliprdrError::ClipboardOccupied);
}
MountPointState::StaleMount => {
log::warn!(
"clipboard FUSE mount {} is stale, cleaning up before remount",
mount_point.display()
);
unmount_fuse_mount_point(&mount_point);
validate_mount_state_after_stale_cleanup(
&mount_point,
inspect_mount_point_state(&mount_point),
)?;
}
MountPointState::Unknown => return Err(CliprdrError::CliprdrInit),
MountPointState::NotMounted => {}
}
let (server, tx) = FuseServer::new(FUSE_TIMEOUT);
let server = Arc::new(Mutex::new(server));
prepare_fuse_mount_point(&mount_point)?;
prepare_fuse_mount_point(&mount_point);
let mnt_opts = [
MountOption::FSName("rustdesk-cliprdr-fs".to_string()),
MountOption::NoAtime,
@@ -216,201 +168,57 @@ struct FuseContext {
}
// this function must be called after the main IPC is up
fn prepare_fuse_mount_point(mount_point: &PathBuf) -> Result<(), CliprdrError> {
fn prepare_fuse_mount_point(mount_point: &Path) {
use std::{
fs::{self, Permissions},
os::unix::prelude::PermissionsExt,
};
if let Some(parent) = mount_point.parent() {
reject_symlink_path(parent)?;
if let Err(e) = fs::create_dir_all(parent) {
log::warn!("failed to create FUSE mount parent {:?}: {:?}", parent, e);
return Err(CliprdrError::CliprdrInit);
}
}
reject_symlink_path(mount_point)?;
unmount_fuse_mount_point(mount_point);
let recovered_stale_mount = if let Err(e) = fs::create_dir_all(mount_point) {
if let Err(e) = fs::create_dir_all(mount_point) {
log::warn!(
"failed to create clipboard FUSE mount point {}, trying stale mount cleanup: {:?}",
mount_point.display(),
"failed to create FUSE mount point {:?}: {:?}",
mount_point,
e
);
unmount_fuse_mount_point(mount_point);
fs::create_dir_all(mount_point).map_err(|e| {
log::error!(
"failed to create clipboard FUSE mount point {} after cleanup: {:?}",
mount_point.display(),
e
);
CliprdrError::CliprdrInit
})?;
true
} else {
false
};
}
if let Err(e) = fs::set_permissions(mount_point, Permissions::from_mode(0o777)) {
log::warn!(
"failed to set clipboard FUSE mount point permissions {}: {:?}",
mount_point.display(),
"failed to set FUSE mount point permissions {:?}: {:?}",
mount_point,
e
);
}
if !recovered_stale_mount {
unmount_fuse_mount_point(mount_point);
}
Ok(())
}
fn inspect_mount_point_state(mount_point: &Path) -> MountPointState {
if ensure_mount_point_path_is_safe(mount_point).is_err() {
return MountPointState::Unknown;
}
inspect_mount_point_state_with(
mount_point,
std::fs::metadata(mount_point),
std::fs::read_to_string("/proc/self/mountinfo"),
)
fn is_mount_point_healthy(mount_point: &Path) -> bool {
is_mount_point_healthy_result(std::fs::metadata(mount_point))
}
fn validate_mount_state_after_stale_cleanup(
mount_point: &Path,
mount_state: MountPointState,
) -> Result<(), CliprdrError> {
match mount_state {
MountPointState::NotMounted => Ok(()),
MountPointState::HealthyMount => {
log::warn!(
"clipboard FUSE mount {} is still active after stale cleanup",
mount_point.display()
);
Err(CliprdrError::ClipboardOccupied)
}
MountPointState::StaleMount => {
log::warn!(
"clipboard FUSE mount {} is still stale after cleanup",
mount_point.display()
);
Err(CliprdrError::CliprdrInit)
}
MountPointState::Unknown => {
log::warn!(
"failed to verify clipboard FUSE mount {} after cleanup",
mount_point.display()
);
Err(CliprdrError::CliprdrInit)
}
}
}
fn inspect_mount_point_state_with<T>(
mount_point: &Path,
metadata_result: io::Result<T>,
mountinfo_result: io::Result<String>,
) -> MountPointState {
match metadata_result {
Ok(_) => match mountinfo_result {
Ok(mountinfo) => {
if is_mount_point_listed_in_mountinfo(mount_point, &mountinfo) {
MountPointState::HealthyMount
} else {
MountPointState::NotMounted
}
}
Err(e) => {
log::warn!("failed to read mountinfo for {:?}: {:?}", mount_point, e);
MountPointState::Unknown
}
},
Err(e) if e.raw_os_error() == Some(libc::ENOTCONN) => MountPointState::StaleMount,
Err(e) if e.kind() == io::ErrorKind::NotFound => MountPointState::NotMounted,
fn is_mount_point_healthy_result<T>(result: io::Result<T>) -> bool {
match result {
Ok(_) => true,
Err(e) => {
log::warn!("failed to inspect FUSE mount {:?}: {:?}", mount_point, e);
MountPointState::Unknown
e.raw_os_error() != Some(libc::ENOTCONN) && e.kind() != io::ErrorKind::NotFound
}
}
}
fn is_mount_point_listed_in_mountinfo(mount_point: &Path, mountinfo: &str) -> bool {
let mount_point = mount_point.to_string_lossy();
mountinfo.lines().any(|line| {
let mut fields = line.split_whitespace();
let _mount_id = fields.next();
let _parent_id = fields.next();
let _major_minor = fields.next();
let _root = fields.next();
let mount_path = fields.next();
mount_path == Some(mount_point.as_ref())
})
}
fn reject_symlink_metadata_result(
path: &Path,
metadata_result: io::Result<std::fs::Metadata>,
allow_disconnected_mount: bool,
) -> Result<(), CliprdrError> {
match metadata_result {
Ok(metadata) if metadata.file_type().is_symlink() => {
log::warn!("refusing to use symlinked FUSE path {:?}", path);
Err(CliprdrError::CliprdrInit)
}
Ok(_) => Ok(()),
Err(e) if allow_disconnected_mount && e.raw_os_error() == Some(libc::ENOTCONN) => Ok(()),
Err(e) if e.kind() == io::ErrorKind::NotFound => Ok(()),
Err(e) => {
log::warn!("failed to inspect FUSE path {:?}: {:?}", path, e);
Err(CliprdrError::CliprdrInit)
}
}
}
fn reject_symlink_path(path: &Path) -> Result<(), CliprdrError> {
reject_symlink_metadata_result(path, std::fs::symlink_metadata(path), false)
}
fn ensure_mount_point_path_is_safe(mount_point: &Path) -> Result<(), CliprdrError> {
if let Some(parent) = mount_point.parent() {
reject_symlink_path(parent)?;
}
reject_symlink_metadata_result(mount_point, std::fs::symlink_metadata(mount_point), true)
}
fn unmount_fuse_mount_point(mount_point: &Path) {
if ensure_mount_point_path_is_safe(mount_point).is_err() {
log::warn!(
"refusing to unmount unsafe clipboard FUSE mount point {:?}",
mount_point
);
if run_unmount_command("umount", &["-l"], mount_point) {
return;
}
if inspect_mount_point_state_with(
mount_point,
std::fs::metadata(mount_point),
std::fs::read_to_string("/proc/self/mountinfo"),
) == MountPointState::NotMounted
{
if run_unmount_command("fusermount3", &["-uz"], mount_point) {
return;
}
for (program, args) in unmount_command_candidates() {
if run_unmount_command(program, args, mount_point) {
return;
}
}
log::warn!(
"failed to unmount clipboard FUSE mount point {:?}",
mount_point
);
}
fn unmount_command_candidates() -> [(&'static str, &'static [&'static str]); 3] {
[
("fusermount3", &["-uz"]),
("fusermount", &["-uz"]),
("umount", &["-l"]),
]
run_unmount_command("fusermount", &["-uz"], mount_point);
}
fn run_unmount_command(program: &str, args: &[&str], mount_point: &Path) -> bool {
@@ -438,24 +246,22 @@ fn run_unmount_command(program: &str, args: &[&str], mount_point: &Path) -> bool
}
fn uninit_fuse_context_(is_client: bool) {
let ctx = {
let mut fuse_context_lock = if is_client {
FUSE_CONTEXT_CLIENT.lock()
} else {
FUSE_CONTEXT_SERVER.lock()
};
fuse_context_lock.take()
let mut fuse_context_lock = if is_client {
FUSE_CONTEXT_CLIENT.lock()
} else {
FUSE_CONTEXT_SERVER.lock()
};
let ctx = fuse_context_lock.take();
drop(ctx);
}
impl Drop for FuseContext {
fn drop(&mut self) {
self.session.lock().take().map(|s| s.join());
log::info!(
"unmounting clipboard FUSE from {}",
self.mount_point.display()
);
log::info!("unmounting clipboard FUSE from {}", self.mount_point.display());
unmount_fuse_mount_point(&self.mount_point);
if let Some(session) = self.session.lock().take() {
session.join();
}
}
}
@@ -497,60 +303,24 @@ mod tests {
use super::*;
use std::{fs, io};
#[cfg(target_family = "unix")]
use std::os::unix::fs::symlink;
#[test]
fn classifies_mount_point_state_from_metadata_and_mountinfo() {
let mount_point = std::env::temp_dir().join(format!(
"rustdesk-fuse-mount-state-test-{}-{}",
std::process::id(),
line!()
));
let mountinfo = format!(
"123 1 0:45 / {} rw,nosuid,nodev - fuse.rustdesk rustdesk rw\n",
mount_point.display()
);
fn reports_disconnected_fuse_mount_as_unhealthy() {
let err = io::Error::from_raw_os_error(libc::ENOTCONN);
assert_eq!(
inspect_mount_point_state_with(&mount_point, Ok(()), Ok(mountinfo)),
MountPointState::HealthyMount
);
assert_eq!(
inspect_mount_point_state_with(&mount_point, Ok(()), Ok(String::new())),
MountPointState::NotMounted
);
let disconnected_metadata: io::Result<()> =
Err(io::Error::from_raw_os_error(libc::ENOTCONN));
assert_eq!(
inspect_mount_point_state_with(&mount_point, disconnected_metadata, Ok(String::new())),
MountPointState::StaleMount
);
assert!(!is_mount_point_healthy_result::<()>(Err(err)));
}
#[test]
#[cfg(target_family = "unix")]
fn rejects_symlink_mount_point() {
let base = std::env::temp_dir().join(format!(
"rustdesk-fuse-symlink-test-{}-{}",
std::process::id(),
line!()
fn reports_existing_directory_mount_point_as_healthy() {
let mount_point = std::env::temp_dir().join(format!(
"rustdesk-fuse-mount-health-test-{}",
std::process::id()
));
let mount_parent = base.join("parent");
let mount_point = mount_parent.join("cliprdr-client");
let symlink_target = base.join("symlink-target");
let _ = fs::remove_dir_all(&base);
fs::create_dir_all(&base).unwrap();
fs::create_dir_all(&mount_parent).unwrap();
fs::create_dir_all(&symlink_target).unwrap();
symlink(&symlink_target, &mount_point).unwrap();
let _ = fs::remove_dir_all(&mount_point);
fs::create_dir(&mount_point).unwrap();
assert!(matches!(
prepare_fuse_mount_point(&mount_point),
Err(CliprdrError::CliprdrInit)
));
assert!(is_mount_point_healthy(&mount_point));
let _ = fs::remove_dir_all(&base);
let _ = fs::remove_dir_all(&mount_point);
}
}
+12 -46
View File
@@ -192,16 +192,20 @@ impl LocalFile {
});
};
let read_result = if offset != self.offset.load(Ordering::Relaxed) {
if offset != self.offset.load(Ordering::Relaxed) {
handle
.seek(std::io::SeekFrom::Start(offset))
.and_then(|_| handle.read_exact(buf))
} else {
handle.read_exact(buf)
};
if let Err(e) = read_result {
return Err(self.invalidate_handle(e));
.map_err(|e| CliprdrError::FileError {
path: self.path.to_string_lossy().to_string(),
err: e,
})?;
}
handle
.read_exact(buf)
.map_err(|e| CliprdrError::FileError {
path: self.path.to_string_lossy().to_string(),
err: e,
})?;
let new_offset = offset + (buf.len() as u64);
self.offset.store(new_offset, Ordering::Relaxed);
@@ -213,15 +217,6 @@ impl LocalFile {
Ok(())
}
fn invalidate_handle(&mut self, err: std::io::Error) -> CliprdrError {
self.offset.store(0, Ordering::Relaxed);
self.handle = None;
CliprdrError::FileError {
path: self.path.to_string_lossy().to_string(),
err,
}
}
}
pub(super) fn construct_file_list(paths: &[PathBuf]) -> Result<Vec<LocalFile>, CliprdrError> {
@@ -283,10 +278,7 @@ pub(super) fn construct_file_list(paths: &[PathBuf]) -> Result<Vec<LocalFile>, C
#[cfg(test)]
mod file_list_test {
use std::{
path::PathBuf,
sync::atomic::{AtomicU64, Ordering},
};
use std::{path::PathBuf, sync::atomic::AtomicU64};
use hbb_common::bytes::{BufMut, BytesMut};
@@ -392,30 +384,4 @@ mod file_list_test {
as_bin_parse_test("/test")?;
Ok(())
}
#[test]
fn read_exact_at_reopens_after_read_failure() -> Result<(), Box<dyn std::error::Error>> {
let file_path = std::env::temp_dir().join(format!(
"rustdesk-clipboard-local-file-{}",
std::process::id()
));
std::fs::write(&file_path, b"")?;
let mut file = LocalFile::try_open(&std::env::temp_dir(), &file_path)?;
file.size = 1;
let mut buf = [0u8; 1];
assert!(file.read_exact_at(&mut buf, 0).is_err());
assert!(file.handle.is_none());
assert_eq!(file.offset.load(Ordering::Relaxed), 0);
std::fs::write(&file_path, [42u8])?;
file.read_exact_at(&mut buf, 0)?;
assert_eq!(buf, [42u8]);
assert!(file.handle.is_none());
std::fs::remove_file(file_path)?;
Ok(())
}
}
+4 -150
View File
@@ -5,7 +5,7 @@ use hbb_common::{
log,
};
use parking_lot::Mutex;
use std::{path::PathBuf, sync::Arc, time::SystemTime, usize};
use std::{path::PathBuf, sync::Arc, usize};
lazy_static::lazy_static! {
// local files are cached, this value should not be changed when copying files
@@ -30,35 +30,9 @@ enum FileContentsRequest {
},
}
// Cheap fingerprint of one top-level selected entry. A change in size/mtime --
// or a directory in the selection -- forces sync_files() to rebuild (see below).
#[derive(Debug, Default, Clone, PartialEq, Eq)]
struct FileSig {
size: u64,
mtime: Option<SystemTime>,
is_dir: bool,
}
// Stat the top-level selected paths only (no recursion), same order as `files`.
fn fingerprint(files: &[String]) -> Vec<FileSig> {
files
.iter()
.map(|s| match std::fs::metadata(s) {
Ok(mt) => FileSig {
size: mt.len(),
mtime: mt.modified().ok(),
is_dir: mt.is_dir(),
},
Err(_) => FileSig::default(),
})
.collect()
}
#[derive(Default)]
struct ClipFiles {
files: Vec<String>,
// Fingerprint of `files` (same len/order); detects in-place edits on re-copy.
sigs: Vec<FileSig>,
file_list: Vec<LocalFile>,
first_file_index: usize,
files_pdu: Vec<u8>,
@@ -67,17 +41,12 @@ struct ClipFiles {
impl ClipFiles {
fn clear(&mut self) {
self.files.clear();
self.sigs.clear();
self.file_list.clear();
self.first_file_index = usize::MAX;
self.files_pdu.clear();
}
fn sync_files(
&mut self,
clipboard_files: &[String],
sigs: Vec<FileSig>,
) -> Result<(), CliprdrError> {
fn sync_files(&mut self, clipboard_files: &[String]) -> Result<(), CliprdrError> {
let clipboard_paths = clipboard_files
.iter()
.map(|s| PathBuf::from(s))
@@ -89,7 +58,6 @@ impl ClipFiles {
.position(|f| !f.path.is_dir())
.unwrap_or(usize::MAX);
self.files = clipboard_files.to_vec();
self.sigs = sigs;
Ok(())
}
@@ -290,128 +258,14 @@ pub fn read_file_contents(
}
pub fn sync_files(files: &[String]) -> Result<(), CliprdrError> {
// Dedup: skip the rebuild only when paths + sizes + mtimes match and no dir is
// selected (a dir's own mtime doesn't change when a file inside it is edited).
let current = fingerprint(files);
let mut files_lock = CLIP_FILES.lock();
if files_lock.files == files
&& files_lock.sigs == current
&& !current.iter().any(|sig| sig.is_dir)
{
if files_lock.files == files {
return Ok(());
}
files_lock.sync_files(files, current)?;
files_lock.sync_files(files)?;
Ok(files_lock.build_file_list_pdu())
}
pub fn get_file_list_pdu() -> Vec<u8> {
CLIP_FILES.lock().files_pdu.clone()
}
#[cfg(test)]
mod sig_test {
use super::*;
use std::fs;
use std::path::PathBuf;
use std::time::{SystemTime, UNIX_EPOCH};
// Unique temp dir under the system temp dir; removed on drop (no dev-dep).
struct TmpDir(PathBuf);
impl TmpDir {
fn new(tag: &str) -> Self {
let nanos = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_nanos())
.unwrap_or(0);
let mut dir = std::env::temp_dir();
dir.push(format!("rustdesk_sig_test_{}_{}", tag, nanos));
fs::create_dir_all(&dir).unwrap();
TmpDir(dir)
}
fn join(&self, name: &str) -> PathBuf {
self.0.join(name)
}
}
impl Drop for TmpDir {
fn drop(&mut self) {
let _ = fs::remove_dir_all(&self.0);
}
}
fn path_str(p: &PathBuf) -> String {
p.to_string_lossy().to_string()
}
#[test]
fn fingerprint_missing_path_is_default() {
let tmp = TmpDir::new("missing");
let missing = path_str(&tmp.join("does_not_exist.bin"));
let sigs = fingerprint(&[missing]);
assert_eq!(sigs.len(), 1);
// A path that can't be stat'd -> default sig, which forces a rebuild.
assert_eq!(sigs[0], FileSig::default());
assert_eq!(sigs[0].mtime, None);
}
#[test]
fn fingerprint_detects_inplace_edit() {
let tmp = TmpDir::new("edit");
let file = tmp.join("a.bin");
fs::write(&file, b"small").unwrap();
let p = path_str(&file);
let before = fingerprint(&[p.clone()]);
// Same content, same path: fingerprint must be stable.
let again = fingerprint(&[p.clone()]);
assert_eq!(before, again);
assert_eq!(before[0].size, 5);
assert!(!before[0].is_dir);
// Edit in place so the file grows.
fs::write(&file, b"much larger contents than before").unwrap();
let after = fingerprint(&[p]);
assert_ne!(before, after);
assert!(after[0].size > before[0].size);
}
#[test]
fn fingerprint_flags_directory() {
let tmp = TmpDir::new("dir");
let sub = tmp.join("subdir");
fs::create_dir_all(&sub).unwrap();
let sigs = fingerprint(&[path_str(&sub)]);
assert_eq!(sigs.len(), 1);
assert!(sigs[0].is_dir);
}
#[test]
fn recopy_after_edit_refreshes_cached_size() {
let tmp = TmpDir::new("recopy");
let file = tmp.join("doc.bin");
fs::write(&file, b"v1").unwrap(); // 2 bytes
let files = vec![path_str(&file)];
// Drive the public, guarded `sync_files` over the global CLIP_FILES;
// reset first (this is the only test that touches the global).
clear_files();
sync_files(&files).unwrap();
{
let cache = CLIP_FILES.lock();
let idx = cache.first_file_index;
assert_eq!(cache.file_list[idx].size, 2);
}
// In-place edit grows the file; the re-copy must rebuild. Pre-fix the
// path-only guard early-returned and left the cached size stale at 2.
fs::write(&file, b"v2 is bigger").unwrap(); // 12 bytes
sync_files(&files).unwrap();
{
let cache = CLIP_FILES.lock();
let idx = cache.first_file_index;
assert_eq!(cache.file_list[idx].size, 12);
}
clear_files(); // leave the global clean for other tests
}
}
+36 -168
View File
@@ -41,14 +41,6 @@
/* Maximum number of clipboard streams accepted from a remote peer (integer overflow / DoS guard) */
#define WF_CLIPRDR_MAX_STREAMS 16384
/* Registered clipboard formats use IDs 0xC000 through 0xFFFF.
* https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-registerclipboardformatw */
#define WF_CLIPRDR_MAX_FORMATS 0x4000u
/* Registered format names are string atoms; cap the converted WCHAR name.
* https://learn.microsoft.com/en-us/windows/win32/dataxchg/about-atom-tables */
#define WF_CLIPRDR_MAX_FORMAT_NAME_WCHARS 255u
/* Bound the peer-provided UTF-8 scan separately from the converted Windows name. */
#define WF_CLIPRDR_MAX_FORMAT_NAME_UTF8_BYTES (WF_CLIPRDR_MAX_FORMAT_NAME_WCHARS * 4u)
/* Validates the remote descriptor array size after cItems has been read safely. */
static BOOL wf_cliprdr_file_group_descriptor_size_valid(SIZE_T size, UINT count)
@@ -69,25 +61,6 @@ static BOOL wf_cliprdr_file_group_descriptor_size_valid(SIZE_T size, UINT count)
return size >= descriptors_size;
}
static BOOL wf_cliprdr_bounded_strlen(const char *value, size_t max_len, size_t *len)
{
size_t i;
if (!value || !len)
return FALSE;
for (i = 0; i <= max_len; i++)
{
if (value[i] == '\0')
{
*len = i;
return TRUE;
}
}
return FALSE;
}
/**
* Clipboard Formats
*/
@@ -232,7 +205,6 @@ struct _CliprdrStream
FILEDESCRIPTORW m_Dsc;
void *m_pData;
UINT32 m_connID;
UINT32 m_streamId; // unique CLIPRDR streamId; avoids leaking a heap pointer
};
typedef struct _CliprdrStream CliprdrStream;
@@ -286,9 +258,6 @@ struct wf_clipboard
char *req_fdata;
HANDLE req_fevent;
BOOL req_f_received;
UINT32 req_f_conn_id_expected; // connID of the outstanding request
UINT32 req_f_stream_id_expected; // streamId of the outstanding request; responses for another are dropped
LONG req_f_stream_id_seq; // source of unique per-stream ids
size_t nFiles;
size_t file_array_size;
@@ -319,7 +288,7 @@ static UINT32 get_remote_format_id(wfClipboard *clipboard, UINT32 local_format);
static UINT cliprdr_send_data_request(UINT32 connID, wfClipboard *clipboard, UINT32 format);
static UINT cliprdr_send_lock(wfClipboard *clipboard);
static UINT cliprdr_send_unlock(wfClipboard *clipboard);
static UINT cliprdr_send_request_filecontents(wfClipboard *clipboard, UINT32 connID, UINT32 streamId,
static UINT cliprdr_send_request_filecontents(wfClipboard *clipboard, UINT32 connID, const void *streamid,
ULONG index, UINT32 flag, DWORD positionhigh,
DWORD positionlow, ULONG request);
@@ -402,7 +371,7 @@ static ULONG STDMETHODCALLTYPE CliprdrStream_Release(IStream *This)
static HRESULT STDMETHODCALLTYPE CliprdrStream_Read(IStream *This, void *pv, ULONG cb,
ULONG *pcbRead)
{
UINT ret;
int ret;
CliprdrStream *instance = (CliprdrStream *)This;
wfClipboard *clipboard;
@@ -415,23 +384,12 @@ static HRESULT STDMETHODCALLTYPE CliprdrStream_Read(IStream *This, void *pv, ULO
if (instance->m_lOffset.QuadPart >= instance->m_lSize.QuadPart)
return S_FALSE;
ret = cliprdr_send_request_filecontents(clipboard, instance->m_connID, instance->m_streamId, instance->m_lIndex,
ret = cliprdr_send_request_filecontents(clipboard, instance->m_connID, (void *)This, instance->m_lIndex,
FILECONTENTS_RANGE, instance->m_lOffset.HighPart,
instance->m_lOffset.LowPart, cb);
if (ret != CHANNEL_RC_OK)
{
free(clipboard->req_fdata);
clipboard->req_fdata = NULL;
if (ret < 0)
return E_FAIL;
}
if (clipboard->req_fsize > cb)
{
free(clipboard->req_fdata);
clipboard->req_fdata = NULL;
return STG_E_READFAULT;
}
if (clipboard->req_fdata)
{
@@ -643,7 +601,6 @@ static CliprdrStream *CliprdrStream_New(UINT32 connID, ULONG index, void *pData,
instance->m_pData = pData;
instance->m_lOffset.QuadPart = 0;
instance->m_connID = connID;
instance->m_streamId = (UINT32)InterlockedIncrement(&clipboard->req_f_stream_id_seq);
if (instance->m_Dsc.dwFlags & FD_ATTRIBUTES)
{
@@ -654,28 +611,16 @@ static CliprdrStream *CliprdrStream_New(UINT32 connID, ULONG index, void *pData,
if (((instance->m_Dsc.dwFlags & FD_FILESIZE) == 0) && !isDir)
{
/* get content size of this stream */
if (cliprdr_send_request_filecontents(clipboard, instance->m_connID, instance->m_streamId,
if (cliprdr_send_request_filecontents(clipboard, instance->m_connID, (void *)instance,
instance->m_lIndex, FILECONTENTS_SIZE, 0, 0,
8) == CHANNEL_RC_OK)
{
success = TRUE;
}
if (clipboard->req_fdata != NULL && clipboard->req_fsize >= sizeof(LONGLONG))
{
LONGLONG sz = 0;
CopyMemory(&sz, clipboard->req_fdata, sizeof(sz));
if (sz < 0)
success = FALSE;
else
instance->m_lSize.QuadPart = sz;
}
else
{
success = FALSE;
}
if (clipboard->req_fdata)
if (clipboard->req_fdata != NULL)
{
instance->m_lSize.QuadPart = *((LONGLONG *)clipboard->req_fdata);
free(clipboard->req_fdata);
clipboard->req_fdata = NULL;
}
@@ -1461,35 +1406,25 @@ static UINT32 get_remote_format_id(wfClipboard *clipboard, UINT32 local_format)
return local_format;
}
static BOOL map_ensure_capacity(wfClipboard *clipboard, size_t capacity)
static void map_ensure_capacity(wfClipboard *clipboard)
{
size_t old_size;
formatMapping *new_map;
if (!clipboard)
return FALSE;
return;
if (!clipboard->format_mappings)
return FALSE;
if (clipboard->map_size >= clipboard->map_capacity)
{
size_t new_size;
formatMapping *new_map;
new_size = clipboard->map_capacity * 2;
new_map =
(formatMapping *)realloc(clipboard->format_mappings, sizeof(formatMapping) * new_size);
if (capacity <= clipboard->map_capacity)
return TRUE;
if (!new_map)
return;
if (capacity > WF_CLIPRDR_MAX_FORMATS ||
capacity > ((size_t)-1) / sizeof(formatMapping))
return FALSE;
old_size = clipboard->map_capacity;
new_map =
(formatMapping *)realloc(clipboard->format_mappings, sizeof(formatMapping) * capacity);
if (!new_map)
return FALSE;
memset(new_map + old_size, 0, sizeof(formatMapping) * (capacity - old_size));
clipboard->format_mappings = new_map;
clipboard->map_capacity = capacity;
return TRUE;
clipboard->format_mappings = new_map;
clipboard->map_capacity = new_size;
}
}
static BOOL clear_format_map(wfClipboard *clipboard)
@@ -1516,13 +1451,6 @@ static BOOL clear_format_map(wfClipboard *clipboard)
return TRUE;
}
static UINT wf_cliprdr_server_format_list_fail(wfClipboard *clipboard)
{
clear_format_map(clipboard);
clipboard->copied = FALSE;
return ERROR_INTERNAL_ERROR;
}
static UINT cliprdr_send_tempdir(wfClipboard *clipboard)
{
CLIPRDR_TEMP_DIRECTORY tempDirectory;
@@ -1801,12 +1729,12 @@ static UINT cliprdr_send_data_request(UINT32 connID, wfClipboard *clipboard, UIN
return wait_response_event(connID, clipboard, clipboard->formatDataRespEvent, &clipboard->formatDataRespReceived, &clipboard->hmem);
}
static UINT cliprdr_send_request_filecontents(wfClipboard *clipboard, UINT32 connID, UINT32 streamId, ULONG index,
UINT cliprdr_send_request_filecontents(wfClipboard *clipboard, UINT32 connID, const void *streamid, ULONG index,
UINT32 flag, DWORD positionhigh, DWORD positionlow,
ULONG nreq)
{
UINT rc;
CLIPRDR_FILE_CONTENTS_REQUEST fileContentsRequest = { 0 };
CLIPRDR_FILE_CONTENTS_REQUEST fileContentsRequest;
if (!clipboard || !clipboard->context || !clipboard->context->ClientFileContentsRequest)
return ERROR_INTERNAL_ERROR;
@@ -1817,11 +1745,12 @@ static UINT cliprdr_send_request_filecontents(wfClipboard *clipboard, UINT32 con
return rc;
}
clipboard->req_f_received = FALSE;
clipboard->req_f_conn_id_expected = connID;
clipboard->req_f_stream_id_expected = streamId;
fileContentsRequest.connID = connID;
fileContentsRequest.streamId = streamId;
// streamId is `IStream*` pointer, though it is not very good on a 64-bit system.
// But it is OK, because it is only used to check if the stream is the same in
// `wf_cliprdr_server_file_contents_request()` function.
fileContentsRequest.streamId = (UINT32)(ULONG_PTR)streamid;
fileContentsRequest.listIndex = index;
fileContentsRequest.dwFlags = flag;
fileContentsRequest.nPositionLow = positionlow;
@@ -2514,16 +2443,6 @@ static UINT wf_cliprdr_server_format_list(CliprdrClientContext *context,
if (!clear_format_map(clipboard))
return ERROR_INTERNAL_ERROR;
clipboard->copied = FALSE;
if (formatList->numFormats > WF_CLIPRDR_MAX_FORMATS)
return ERROR_INTERNAL_ERROR;
if (formatList->numFormats > 0 && !formatList->formats)
return ERROR_INTERNAL_ERROR;
if (!map_ensure_capacity(clipboard, formatList->numFormats))
return ERROR_INTERNAL_ERROR;
clipboard->copied = TRUE;
@@ -2531,58 +2450,19 @@ static UINT wf_cliprdr_server_format_list(CliprdrClientContext *context,
{
format = &(formatList->formats[i]);
mapping = &(clipboard->format_mappings[i]);
/* Do not validate the peer-provided formatId as a Windows registered format.
* It is only a remote protocol ID used when requesting data from the peer.
* For named formats, RegisterClipboardFormatW creates the local Windows
* clipboard ID below, and that local ID is checked before publishing. */
mapping->remote_format_id = format->formatId;
if (format->formatName)
{
size_t name_len;
int size;
int size = MultiByteToWideChar(CP_UTF8, 0, format->formatName,
strlen(format->formatName), NULL, 0);
mapping->name = calloc(size + 1, sizeof(WCHAR));
if (!wf_cliprdr_bounded_strlen(format->formatName,
WF_CLIPRDR_MAX_FORMAT_NAME_UTF8_BYTES, &name_len))
if (mapping->name)
{
return wf_cliprdr_server_format_list_fail(clipboard);
}
if (name_len == 0)
{
return wf_cliprdr_server_format_list_fail(clipboard);
}
size = MultiByteToWideChar(CP_UTF8, 0, format->formatName, (int)name_len,
NULL, 0);
if (size <= 0)
{
return wf_cliprdr_server_format_list_fail(clipboard);
}
if ((UINT)size > WF_CLIPRDR_MAX_FORMAT_NAME_WCHARS)
{
return wf_cliprdr_server_format_list_fail(clipboard);
}
mapping->name = calloc((size_t)size + 1, sizeof(WCHAR));
if (!mapping->name)
{
return wf_cliprdr_server_format_list_fail(clipboard);
}
if (MultiByteToWideChar(CP_UTF8, 0, format->formatName, (int)name_len,
mapping->name, size) != size)
{
free(mapping->name);
mapping->name = NULL;
return wf_cliprdr_server_format_list_fail(clipboard);
}
mapping->local_format_id = RegisterClipboardFormatW((LPWSTR)mapping->name);
if (mapping->local_format_id == 0)
{
return wf_cliprdr_server_format_list_fail(clipboard);
MultiByteToWideChar(CP_UTF8, 0, format->formatName, strlen(format->formatName),
mapping->name, size);
mapping->local_format_id = RegisterClipboardFormatW((LPWSTR)mapping->name);
}
}
else
@@ -2592,6 +2472,7 @@ static UINT wf_cliprdr_server_format_list(CliprdrClientContext *context,
}
clipboard->map_size++;
map_ensure_capacity(clipboard);
}
if (file_transferring(clipboard))
@@ -3042,7 +2923,6 @@ wf_cliprdr_server_file_contents_request(CliprdrClientContext *context,
BOOL bIsStreamFile = TRUE;
static LPSTREAM pStreamStc = NULL;
static UINT32 uStreamIdStc = 0;
static UINT32 uConnIdStc = 0;
wfClipboard *clipboard;
UINT rc = ERROR_INTERNAL_ERROR;
UINT sRc;
@@ -3116,8 +2996,7 @@ wf_cliprdr_server_file_contents_request(CliprdrClientContext *context,
vFormatEtc.lindex = fileContentsRequest->listIndex;
vFormatEtc.ptd = NULL;
if ((uStreamIdStc != fileContentsRequest->streamId) ||
(uConnIdStc != fileContentsRequest->connID) || !pStreamStc)
if ((uStreamIdStc != fileContentsRequest->streamId) || !pStreamStc)
{
LPENUMFORMATETC pEnumFormatEtc;
ULONG CeltFetched;
@@ -3148,7 +3027,6 @@ wf_cliprdr_server_file_contents_request(CliprdrClientContext *context,
{
pStreamStc = vStgMedium.pstm;
uStreamIdStc = fileContentsRequest->streamId;
uConnIdStc = fileContentsRequest->connID;
bIsStreamFile = TRUE;
}
@@ -3312,9 +3190,6 @@ wf_cliprdr_server_file_contents_response(CliprdrClientContext *context,
rc = ERROR_INTERNAL_ERROR;
break;
}
if (fileContentsResponse->connID != clipboard->req_f_conn_id_expected ||
fileContentsResponse->streamId != clipboard->req_f_stream_id_expected)
return CHANNEL_RC_OK;
clipboard->req_fsize = 0;
clipboard->req_fdata = NULL;
@@ -3325,13 +3200,6 @@ wf_cliprdr_server_file_contents_response(CliprdrClientContext *context,
}
clipboard->req_fsize = fileContentsResponse->cbRequested;
/*
* Keep the zero-size allocation: supported Windows builds use the Microsoft
* CRT, where malloc(0) returns a valid pointer. wait_response_event() also
* uses a non-NULL req_fdata to recognize a successful zero-byte response.
* The Rust FFI derives requestedData and cbRequested from the same Vec, so a
* nonzero length cannot have a NULL data pointer on the normal call path.
*/
clipboard->req_fdata = (char *)malloc(fileContentsResponse->cbRequested);
if (!clipboard->req_fdata)
{
+3 -3
View File
@@ -113,11 +113,11 @@ pub enum MouseButton {
/// Scroll up button
ScrollUp,
/// Scroll down button
/// Left right button
ScrollDown,
/// Scroll left button
/// Left right button
ScrollLeft,
/// Scroll right button
/// Left right button
ScrollRight,
}
+1 -1
View File
@@ -223,7 +223,7 @@ impl KeyboardControllable for Enigo {
// Windows uses uft-16 encoding. We need to check
// for variable length characters. As such some
// characters can be 32 bit long and those are
// encoded in so-called high and low surrogates
// encoded in such called hight and low surrogates
// each 16 bit wide that needs to be send after
// another to the SendInput function without
// being interrupted by "keyup"
+1 -1
View File
@@ -26,7 +26,7 @@ windows = { version = "0.61", features = [
native-windows-gui = {version = "1.0", default-features = false, features = ["animation-timer", "image-decoder"]}
[package.metadata.winres]
LegalCopyright = "Copyright © 2026 Purslane Tech Pte. Ltd. All rights reserved."
LegalCopyright = "Copyright © 2025 Purslane Ltd. All rights reserved."
ProductName = "RustDesk"
OriginalFilename = "rustdesk.exe"
FileDescription = "RustDesk Remote Desktop"
+1 -1
View File
@@ -47,7 +47,7 @@ fn link_vcpkg(mut path: PathBuf, name: &str) -> PathBuf {
format!("{}-{}", target_arch, target_os)
}
} else if target_os == "windows" {
format!("{}-windows-static", target_arch)
"x64-windows-static".to_owned()
} else {
format!("{}-{}", target_arch, target_os)
};
+2 -22
View File
@@ -79,10 +79,6 @@ mod webrtc {
}
}
fn tile_log2(threads: u32) -> std::os::raw::c_uint {
(threads as f64).log2().ceil() as _
}
fn get_super_block_size(width: u32, height: u32, threads: u32) -> aom_superblock_size_t {
use aom_superblock_size::*;
let resolution = width * height;
@@ -164,7 +160,8 @@ mod webrtc {
} else {
AV1E_SET_TILE_COLUMNS
};
call_ctl!(ctx, tile_set, tile_log2(cfg.g_threads));
// Failed on android
call_ctl!(ctx, tile_set, (cfg.g_threads as f64 * 1.0f64).log2().ceil());
call_ctl!(ctx, AV1E_SET_ROW_MT, 1);
call_ctl!(ctx, AV1E_SET_ENABLE_OBMC, 0);
call_ctl!(ctx, AV1E_SET_NOISE_SENSITIVITY, 0);
@@ -200,23 +197,6 @@ mod webrtc {
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use std::os::raw::c_uint;
#[test]
fn tile_log2_uses_c_uint_and_rounds_up() {
let one_thread: c_uint = tile_log2(1);
let three_threads: c_uint = tile_log2(3);
let max_threads: c_uint = tile_log2(64);
assert_eq!(one_thread, 0);
assert_eq!(three_threads, 2);
assert_eq!(max_threads, 6);
}
}
}
impl EncoderApi for AomEncoder {
+17 -74
View File
@@ -52,33 +52,6 @@ lazy_static::lazy_static! {
static ref MAG_BUFFER: Mutex<(bool, Vec<u8>)> = Default::default();
}
fn find_windows(cls: &str, name: &str) -> Result<Vec<HWND>> {
let name_c = CString::new(name)?;
let cls_c = if cls.is_empty() {
None
} else {
Some(CString::new(cls)?)
};
let mut hwnds = Vec::new();
unsafe {
let mut after = NULL as _;
loop {
let hwnd = FindWindowExA(
NULL as _,
after,
cls_c.as_ref().map_or(NULL as _, |c| c.as_ptr()),
name_c.as_ptr(),
);
if hwnd.is_null() {
break;
}
hwnds.push(hwnd);
after = hwnd;
}
}
Ok(hwnds)
}
pub type REFWICPixelFormatGUID = *const GUID;
pub type WICPixelFormatGUID = GUID;
@@ -274,8 +247,6 @@ pub struct CapturerMag {
rect: RECT,
width: usize,
height: usize,
excluded_window_target: Option<(String, String)>,
excluded_windows: Vec<HWND>,
}
impl Drop for CapturerMag {
@@ -290,10 +261,6 @@ impl CapturerMag {
MagInterface::new().is_ok()
}
// This captures through the legacy Windows Magnification API. Do not infer
// multi-monitor capture support from privacy overlay coverage: WebRTC also
// disables its magnifier capturer when SM_CMONITORS != 1.
// https://webrtc.googlesource.com/src/+/1845922d5a1bf9c27deeffb4a8c8daea124434c1/modules/desktop_capture/win/screen_capturer_win_magnifier.cc
pub(crate) fn new(origin: (i32, i32), width: usize, height: usize) -> Result<Self> {
unsafe {
let x = GetSystemMetrics(SM_XVIRTUALSCREEN);
@@ -338,8 +305,6 @@ impl CapturerMag {
},
width,
height,
excluded_window_target: None,
excluded_windows: Vec::new(),
};
unsafe {
@@ -471,41 +436,19 @@ impl CapturerMag {
}
pub(crate) fn exclude(&mut self, cls: &str, name: &str) -> Result<bool> {
let mut hwnds = find_windows(cls, name)?;
hwnds.sort_unstable_by_key(|hwnd| *hwnd as usize);
self.excluded_window_target = Some((cls.to_owned(), name.to_owned()));
if hwnds.is_empty() {
self.excluded_windows.clear();
return Ok(false);
}
self.exclude_windows(&mut hwnds)?;
self.excluded_windows = hwnds;
Ok(true)
}
fn refresh_excluded_windows(&mut self) -> Result<()> {
let Some((cls, name)) = self.excluded_window_target.as_ref() else {
return Ok(());
};
let mut hwnds = find_windows(cls, name)?;
hwnds.sort_unstable_by_key(|hwnd| *hwnd as usize);
// This runs from frame() because refreshed privacy overlays get new
// HWNDs. It is only used on the legacy magnifier backend while privacy
// mode is active; if it shows up as hot-path cost, throttle this check.
// Keep the previous filter list while privacy windows are being recreated.
if hwnds.is_empty() || hwnds == self.excluded_windows {
return Ok(());
}
self.exclude_windows(&mut hwnds)?;
self.excluded_windows = hwnds;
Ok(())
}
fn exclude_windows(&mut self, hwnds: &mut [HWND]) -> Result<bool> {
let count = hwnds.len() as _;
let name_c = CString::new(name)?;
unsafe {
let mut hwnd = if cls.len() == 0 {
FindWindowExA(NULL as _, NULL as _, NULL as _, name_c.as_ptr())
} else {
let cls_c = CString::new(cls).unwrap();
FindWindowExA(NULL as _, NULL as _, cls_c.as_ptr(), name_c.as_ptr())
};
if hwnd.is_null() {
return Ok(false);
}
if let Some(set_window_filter_list_func) =
self.mag_interface.set_window_filter_list_func
{
@@ -513,15 +456,16 @@ impl CapturerMag {
== set_window_filter_list_func(
self.magnifier_window,
MW_FILTERMODE_EXCLUDE,
count,
hwnds.as_mut_ptr(),
1,
&mut hwnd,
)
{
return Err(Error::new(
ErrorKind::Other,
format!(
"Failed MagSetWindowFilterList for {} windows, error {}",
count,
"Failed MagSetWindowFilterList for cls {} name {}, error {}",
cls,
name,
Error::last_os_error()
),
));
@@ -552,7 +496,6 @@ impl CapturerMag {
}
pub(crate) fn frame(&mut self, data: &mut Vec<u8>) -> Result<()> {
self.refresh_excluded_windows()?;
Self::clear_data();
unsafe {
@@ -7,10 +7,6 @@
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|ARM64">
<Configuration>Release</Configuration>
<Platform>ARM64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<Keyword>Win32Proj</Keyword>
@@ -26,12 +22,6 @@
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
@@ -40,9 +30,6 @@
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<ClCompile>
@@ -66,28 +53,6 @@
<ModuleDefinitionFile>CustomActions.def</ModuleDefinitionFile>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>NDEBUG;EXAMPLECADLL_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<PrecompiledHeader>Use</PrecompiledHeader>
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
</ClCompile>
<Link>
<AdditionalDependencies>msi.lib;version.lib;%(AdditionalDependencies)</AdditionalDependencies>
<SubSystem>Windows</SubSystem>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<GenerateDebugInformation>true</GenerateDebugInformation>
<EnableUAC>false</EnableUAC>
<ModuleDefinitionFile>CustomActions.def</ModuleDefinitionFile>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClInclude Include="Common.h" />
<ClInclude Include="framework.h" />
@@ -100,7 +65,6 @@
<ClCompile Include="FirewallRules.cpp" />
<ClCompile Include="pch.cpp">
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Create</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">Create</PrecompiledHeader>
</ClCompile>
<ClCompile Include="ReadConfig.cpp" />
<ClCompile Include="RemotePrinter.cpp" />
+2 -2
View File
@@ -79,7 +79,7 @@ heading 1;}{\s2\ql \li0\ri0\nowidctlpar\wrapdefault\faauto\outlinelevel1\rin0\li
\ab\af1 \ltrch\fcs0 \b\ul\cf2\lang1033\langfe2052\langnp1033\insrsid1917520
\par }\pard \ltrpar\s2\qj \li0\ri0\sb100\sa100\nowidctlpar\wrapdefault\faauto\outlinelevel1\rin0\lin0\itap0\pararsid8979511 {\rtlch\fcs1 \af1\afs18 \ltrch\fcs0 \fs18\lang1033\langfe2052\langnp1033\insrsid8979511\charrsid8979511 \hich\af1\dbch\af31505\loch\f1
\hich\f1 This Privacy Policy (hereinafter the \'93}{\rtlch\fcs1 \ab\af1\afs21 \ltrch\fcs0 \b\fs21\lang1033\langfe2052\langnp1033\insrsid8979511\charrsid1523795 \hich\af1\dbch\af31505\loch\f1 Policy}{\rtlch\fcs1 \af1\afs18 \ltrch\fcs0
\fs18\lang1033\langfe2052\langnp1033\insrsid8979511\charrsid8979511 \loch\af1\dbch\af31505\hich\f1 \'94\loch\f1 \hich\f1 ) governs the terms and conditions under which Purslane Tech Pte. Ltd. (hereinafter \'93}{\rtlch\fcs1 \ab\af1\afs21 \ltrch\fcs0
\fs18\lang1033\langfe2052\langnp1033\insrsid8979511\charrsid8979511 \loch\af1\dbch\af31505\hich\f1 \'94\loch\f1 \hich\f1 ) governs the terms and conditions under which Purslane Ltd. (hereinafter \'93}{\rtlch\fcs1 \ab\af1\afs21 \ltrch\fcs0
\b\fs21\lang1033\langfe2052\langnp1033\insrsid8979511\charrsid1523795 \hich\af1\dbch\af31505\loch\f1 us}{\rtlch\fcs1 \af1\afs18 \ltrch\fcs0 \fs18\lang1033\langfe2052\langnp1033\insrsid8979511\charrsid8979511 \loch\af1\dbch\af31505\hich\f1 \'94\loch\f1
\hich\f1 or \'93}{\rtlch\fcs1 \ab\af1\afs21 \ltrch\fcs0 \b\fs21\lang1033\langfe2052\langnp1033\insrsid8979511\charrsid1523795 \hich\af1\dbch\af31505\loch\f1 we}{\rtlch\fcs1 \af1\afs18 \ltrch\fcs0
\fs18\lang1033\langfe2052\langnp1033\insrsid8979511\charrsid8979511 \loch\af1\dbch\af31505\hich\f1 \'94\loch\f1 \hich\f1
@@ -300,4 +300,4 @@ b060828e6f37ed1567914b284d262452282e3198720e274a939cd08a54f980ae38a38f56e422a3a6
\lsdpriority49 \lsdlocked0 List Table 4 Accent 5;\lsdpriority50 \lsdlocked0 List Table 5 Dark Accent 5;\lsdpriority51 \lsdlocked0 List Table 6 Colorful Accent 5;\lsdpriority52 \lsdlocked0 List Table 7 Colorful Accent 5;
\lsdpriority46 \lsdlocked0 List Table 1 Light Accent 6;\lsdpriority47 \lsdlocked0 List Table 2 Accent 6;\lsdpriority48 \lsdlocked0 List Table 3 Accent 6;\lsdpriority49 \lsdlocked0 List Table 4 Accent 6;
\lsdpriority50 \lsdlocked0 List Table 5 Dark Accent 6;\lsdpriority51 \lsdlocked0 List Table 6 Colorful Accent 6;\lsdpriority52 \lsdlocked0 List Table 7 Colorful Accent 6;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Mention;
\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Smart Hyperlink;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Hashtag;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Unresolved Mention;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Smart Link;}}}
\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Smart Hyperlink;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Hashtag;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Unresolved Mention;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Smart Link;}}}
+1 -1
View File
@@ -3,7 +3,7 @@
<IncludeSearchPaths>
</IncludeSearchPaths>
<Configurations>Release</Configurations>
<Platforms>x64;ARM64</Platforms>
<Platforms>x64</Platforms>
</PropertyGroup>
<ItemGroup>
<Content Include="Includes.wxi" />
-5
View File
@@ -10,17 +10,12 @@ EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Release|x64 = Release|x64
Release|ARM64 = Release|ARM64
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{F403A403-CEFF-4399-B51C-CC646C8E98CF}.Release|x64.ActiveCfg = Release|x64
{F403A403-CEFF-4399-B51C-CC646C8E98CF}.Release|x64.Build.0 = Release|x64
{F403A403-CEFF-4399-B51C-CC646C8E98CF}.Release|ARM64.ActiveCfg = Release|ARM64
{F403A403-CEFF-4399-B51C-CC646C8E98CF}.Release|ARM64.Build.0 = Release|ARM64
{6B3647E0-B4A3-46AE-8757-A22EE51C1DAC}.Release|x64.ActiveCfg = Release|x64
{6B3647E0-B4A3-46AE-8757-A22EE51C1DAC}.Release|x64.Build.0 = Release|x64
{6B3647E0-B4A3-46AE-8757-A22EE51C1DAC}.Release|ARM64.ActiveCfg = Release|ARM64
{6B3647E0-B4A3-46AE-8757-A22EE51C1DAC}.Release|ARM64.Build.0 = Release|ARM64
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
+2 -2
View File
@@ -85,7 +85,7 @@ def make_parser():
"-m",
"--manufacturer",
type=str,
default="Purslane Tech Pte. Ltd.",
default="PURSLANE",
help="The app manufacturer.",
)
return parser
@@ -499,7 +499,7 @@ def update_license_file(app_name):
license_content = f.read()
license_content = license_content.replace("website rustdesk.com and other ", "")
license_content = license_content.replace("RustDesk", app_name)
license_content = re.sub(r"Purslane(?: Tech Pte\.)? Ltd", app_name, license_content, flags=re.IGNORECASE)
license_content = re.sub("Purslane Ltd", app_name, license_content, flags=re.IGNORECASE)
with open(license_file, "w", encoding="utf-8") as f:
f.write(license_content)
+1 -1
View File
@@ -45,7 +45,7 @@ pre_start()
return 0
}
# When logging out from the interactive shell, the execution sequence is:
# When loging out from the interactive shell, the execution sequence is:
#
# IF ~/.bash_logout exists THEN
# execute ~/.bash_logout
+4 -12
View File
@@ -130,18 +130,14 @@ elseif(VCPKG_TARGET_IS_WINDOWS)
--cc=cl \
--enable-gpl \
--enable-d3d11va \
--enable-hwaccel=h264_d3d11va \
--enable-hwaccel=hevc_d3d11va \
--enable-hwaccel=h264_d3d11va2 \
--enable-hwaccel=hevc_d3d11va2 \
")
if(VCPKG_TARGET_ARCHITECTURE STREQUAL "x86" OR VCPKG_TARGET_ARCHITECTURE STREQUAL "x64")
string(APPEND OPTIONS "\
--enable-cuda \
--enable-ffnvcodec \
--enable-hwaccel=h264_nvdec \
--enable-hwaccel=hevc_nvdec \
--enable-hwaccel=h264_d3d11va \
--enable-hwaccel=hevc_d3d11va \
--enable-hwaccel=h264_d3d11va2 \
--enable-hwaccel=hevc_d3d11va2 \
--enable-amf \
--enable-encoder=h264_amf \
--enable-encoder=hevc_amf \
@@ -151,7 +147,6 @@ elseif(VCPKG_TARGET_IS_WINDOWS)
--enable-encoder=h264_qsv \
--enable-encoder=hevc_qsv \
")
endif()
if(VCPKG_TARGET_ARCHITECTURE STREQUAL "x86")
set(LIB_MACHINE_ARG /machine:x86)
@@ -159,9 +154,6 @@ elseif(VCPKG_TARGET_IS_WINDOWS)
elseif(VCPKG_TARGET_ARCHITECTURE STREQUAL "x64")
set(LIB_MACHINE_ARG /machine:x64)
string(APPEND OPTIONS " --arch=x86_64")
elseif(VCPKG_TARGET_ARCHITECTURE STREQUAL "arm64")
set(LIB_MACHINE_ARG /machine:arm64)
string(APPEND OPTIONS " --arch=aarch64 --enable-cross-compile")
else()
message(FATAL_ERROR "Unsupported target architecture")
endif()
+8 -10
View File
@@ -941,23 +941,21 @@ impl Client {
#[cfg(not(target_os = "ios"))]
fn try_stop_clipboard() {
// Disconnected Flutter sessions may keep UI handlers alive, so only connected sessions
// should block clipboard cleanup.
// There's a bug here.
// If session is closed by the peer, `has_sessions_running()` will always return true.
// It's better to check if the active session number.
// But it's not a problem, because the clipboard thread does not consume CPU.
//
// If we want to fix it, we can add a flag to indicate if session is active.
// But I think it's not necessary to introduce complexity at this point.
#[cfg(feature = "flutter")]
if crate::flutter::sessions::has_connected_sessions_running(ConnType::DEFAULT_CONN) {
if crate::flutter::sessions::has_sessions_running(ConnType::DEFAULT_CONN) {
return;
}
#[cfg(not(target_os = "android"))]
clipboard_listener::unsubscribe(Self::CLIENT_CLIPBOARD_NAME);
CLIPBOARD_STATE.lock().unwrap().running = false;
#[cfg(all(feature = "unix-file-copy-paste", target_os = "linux"))]
if let Err(e) = crate::clipboard::try_empty_clipboard_files_sync(
crate::clipboard::ClipboardSide::Client,
0,
) {
log::error!("Failed to empty client clipboard files: {}", e);
}
#[cfg(all(feature = "unix-file-copy-paste", target_os = "linux"))]
clipboard::platform::unix::fuse::uninit_fuse_context(true);
}
-2
View File
@@ -360,8 +360,6 @@ impl<T: InvokeUiSession> Remote<T> {
#[cfg(any(target_os = "windows", feature = "unix-file-copy-paste"))]
if self.handler.is_default() && _set_disconnected_ok {
// Linux client cleanup runs synchronously in try_stop_clipboard() before FUSE is
// unmounted. Keep this async path for other file-clipboard platforms.
crate::clipboard::try_empty_clipboard_files(ClipboardSide::Client, self.client_conn_id);
}
}
+31 -59
View File
@@ -151,50 +151,41 @@ pub fn update_clipboard_files(files: Vec<String>, side: ClipboardSide) {
#[cfg(feature = "unix-file-copy-paste")]
pub fn try_empty_clipboard_files(_side: ClipboardSide, _conn_id: i32) {
std::thread::spawn(move || {
if let Err(e) = try_empty_clipboard_files_sync(_side, _conn_id) {
log::error!("Failed to empty clipboard files: {}", e);
let mut ctx = CLIPBOARD_CTX.lock().unwrap();
if ctx.is_none() {
match ClipboardContext::new() {
Ok(x) => {
*ctx = Some(x);
}
Err(e) => {
log::error!("Failed to create clipboard context: {}", e);
return;
}
}
}
#[allow(unused_mut)]
if let Some(mut ctx) = ctx.as_mut() {
#[cfg(target_os = "linux")]
{
use clipboard::platform::unix;
if unix::fuse::empty_local_files(_side == ClipboardSide::Client, _conn_id) {
ctx.try_empty_clipboard_files(_side);
}
}
#[cfg(target_os = "macos")]
{
ctx.try_empty_clipboard_files(_side);
// No need to make sure the context is enabled.
clipboard::ContextSend::proc(|context| -> ResultType<()> {
context.empty_clipboard(_conn_id).ok();
Ok(())
})
.ok();
}
}
});
}
#[cfg(feature = "unix-file-copy-paste")]
pub fn try_empty_clipboard_files_sync(_side: ClipboardSide, _conn_id: i32) -> ResultType<()> {
let mut ctx = CLIPBOARD_CTX.lock().unwrap();
if ctx.is_none() {
match ClipboardContext::new() {
Ok(x) => {
*ctx = Some(x);
}
Err(e) => {
log::error!("Failed to create clipboard context: {}", e);
bail!("Failed to create clipboard context: {}", e);
}
}
}
#[allow(unused_mut)]
if let Some(mut ctx) = ctx.as_mut() {
#[cfg(target_os = "linux")]
{
use clipboard::platform::unix;
if unix::fuse::empty_local_files(_side == ClipboardSide::Client, _conn_id) {
ctx.try_empty_clipboard_files(_side);
}
}
#[cfg(target_os = "macos")]
{
ctx.try_empty_clipboard_files(_side);
// No need to make sure the context is enabled.
clipboard::ContextSend::proc(|context| -> ResultType<()> {
if !context.empty_clipboard(_conn_id)? {
bail!("Failed to empty clipboard files for conn_id {}", _conn_id);
}
Ok(())
})?;
}
}
Ok(())
}
#[cfg(target_os = "windows")]
pub fn try_empty_clipboard_files(side: ClipboardSide, conn_id: i32) {
log::debug!("try to empty {} cliprdr for conn_id {}", side, conn_id);
@@ -877,7 +868,6 @@ pub mod clipboard_listener {
.unwrap()
.insert(name.clone(), tx);
cleanup_stale_listener(&mut listener_lock);
if listener_lock.handle.is_none() {
log::info!("Start clipboard listener thread");
let handler = Handler {
@@ -903,24 +893,6 @@ pub mod clipboard_listener {
Ok(())
}
fn cleanup_stale_listener(listener: &mut ClipboardListener) {
if !listener
.handle
.as_ref()
.map(|(_, h)| h.is_finished())
.unwrap_or(false)
{
return;
}
if let Some((shutdown, h)) = listener.handle.take() {
log::warn!("Cleaning up stale clipboard listener handle");
if let Err(e) = h.join() {
log::error!("Clipboard listener thread panicked during stale cleanup: {:?}", e);
}
drop(shutdown);
}
}
pub fn unsubscribe(name: &str) {
log::info!("Unsubscribe clipboard listener: {}", name);
let mut listener_lock = CLIPBOARD_LISTENER.lock().unwrap();
+12 -20
View File
@@ -332,16 +332,12 @@ pub mod unix_file_clip {
log::debug!("format data response: msg_flags: {}", msg_flags);
if msg_flags != 0x1 {
log::error!(
"peer reported clipboard format data failure: {}",
msg_flags
);
return vec![];
// return failure message?
}
log::debug!("parsing file descriptors");
match fuse::init_fuse_context(side == ClipboardSide::Client) {
Ok(()) => match fuse::format_data_response_to_urls(
if fuse::init_fuse_context(true).is_ok() {
match fuse::format_data_response_to_urls(
side == ClipboardSide::Client,
format_data,
conn_id,
@@ -352,10 +348,9 @@ pub mod unix_file_clip {
Err(e) => {
log::error!("failed to parse file descriptors: {:?}", e);
}
},
Err(e) => {
log::error!("failed to initialize clipboard FUSE context: {:?}", e);
}
} else {
// send error message to server
}
}
ClipboardFile::FileContentsRequest {
@@ -391,7 +386,6 @@ pub mod unix_file_clip {
ClipboardFile::FileContentsResponse {
msg_flags,
stream_id,
requested_data,
..
} => {
log::debug!(
@@ -399,15 +393,13 @@ pub mod unix_file_clip {
msg_flags,
stream_id,
);
let response = ClipboardFile::FileContentsResponse {
msg_flags,
stream_id,
requested_data,
};
if let Err(e) =
fuse::handle_file_content_response(side == ClipboardSide::Client, response)
{
log::error!("failed to handle file contents response: {:?}", e);
if fuse::init_fuse_context(true).is_ok() {
hbb_common::allow_err!(fuse::handle_file_content_response(
side == ClipboardSide::Client,
clip
));
} else {
// send error message to server
}
}
ClipboardFile::NotifyCallback {
-10
View File
@@ -2297,16 +2297,6 @@ pub mod sessions {
*r#type == conn_type && s.session_handlers.read().unwrap().len() != 0
})
}
#[inline]
#[cfg(not(target_os = "ios"))]
pub fn has_connected_sessions_running(conn_type: ConnType) -> bool {
SESSIONS.read().unwrap().iter().any(|((_, r#type), s)| {
*r#type == conn_type
&& s.session_handlers.read().unwrap().len() != 0
&& s.connection_round_state.lock().unwrap().is_connected()
})
}
}
pub(super) mod async_tasks {
+2 -10
View File
@@ -2852,16 +2852,8 @@ pub fn main_get_common(key: String) -> String {
crate::platform::windows::is_msi_installed(),
crate::common::is_custom_client(),
) {
(Ok(true), false) => match crate::platform::windows::release_arch_suffix() {
Some(arch) => format!("rustdesk-{_version}-{arch}.msi"),
None => "error:unsupported".to_owned(),
},
(Ok(true), true) | (Ok(false), _) => {
match crate::platform::windows::release_arch_suffix() {
Some(arch) => format!("rustdesk-{_version}-{arch}.exe"),
None => "error:unsupported".to_owned(),
}
}
(Ok(true), false) => format!("rustdesk-{_version}-x86_64.msi"),
(Ok(true), true) | (Ok(false), _) => format!("rustdesk-{_version}-x86_64.exe"),
(Err(e), _) => {
log::error!("Failed to check if is msi: {}", e);
format!("error:update-failed-check-msi-tip")
-43
View File
@@ -1245,49 +1245,11 @@ pub fn legacy_keyboard_mode(event: &Event, mut key_event: KeyEvent) -> Vec<KeyEv
#[inline]
pub fn map_keyboard_mode(_peer: &str, event: &Event, key_event: KeyEvent) -> Vec<KeyEvent> {
if let Some(evt) = windows_peer_special_key(_peer, event) {
return vec![evt];
}
_map_keyboard_mode(_peer, event, key_event)
.map(|e| vec![e])
.unwrap_or_default()
}
fn windows_peer_special_key(peer: &str, event: &Event) -> Option<KeyEvent> {
if peer != OS_LOWER_WINDOWS {
return None;
}
let (key, down) = match event.event_type {
EventType::KeyPress(key) => (key, true),
EventType::KeyRelease(key) => (key, false),
_ => return None,
};
// Handle only `Pause` for Windows peers for now.
// Windows has no normal scan code for `Pause`, so send it as a legacy control key.
#[cfg(target_os = "windows")]
let is_pause = {
// The Windows scan code can look like `NumLock`; VK_PAUSE distinguishes it.
let pause_vk_code = rdev::win_code_from_key(Key::Pause);
key == Key::Pause || pause_vk_code == Some(event.platform_code as _)
};
#[cfg(not(target_os = "windows"))]
let is_pause = key == Key::Pause;
if !is_pause {
return None;
}
let mut key_event = KeyEvent::new();
key_event.mode = KeyboardMode::Legacy.into();
key_event.down = down;
key_event.set_control_key(ControlKey::Pause);
let (alt, ctrl, shift, command) = client::get_modifiers_state(false, false, false, false);
client::legacy_modifiers(&mut key_event, alt, ctrl, shift, command);
Some(key_event)
}
fn _map_keyboard_mode(_peer: &str, event: &Event, mut key_event: KeyEvent) -> Option<KeyEvent> {
match event.event_type {
EventType::KeyPress(..) => {
@@ -1459,11 +1421,6 @@ fn is_press(event: &Event) -> bool {
pub fn translate_keyboard_mode(peer: &str, event: &Event, key_event: KeyEvent) -> Vec<KeyEvent> {
let mut events: Vec<KeyEvent> = Vec::new();
if let Some(evt) = windows_peer_special_key(peer, event) {
events.push(evt);
return events;
}
if let Some(unicode_info) = &event.unicode {
if unicode_info.is_dead {
#[cfg(target_os = "macos")]
+7 -67
View File
@@ -103,29 +103,15 @@ pub const LANGS: &[(&str, &str)] = &[
("gu", "ગુજરાતી"),
];
pub(crate) fn cjk_ui_unavailable() -> bool {
cfg!(all(
target_os = "linux",
target_arch = "aarch64",
feature = "flutter"
))
#[cfg(not(any(target_os = "android", target_os = "ios")))]
pub fn translate(name: String) -> String {
let locale = sys_locale::get_locale().unwrap_or_default();
translate_locale(name, &locale)
}
pub(crate) fn is_cjk_lang(lang_or_locale: &str) -> bool {
let lang = lang_or_locale
.split(|c| c == '-' || c == '_')
.next()
.unwrap_or_default()
.to_lowercase();
matches!(lang.as_str(), "zh" | "ja" | "ko")
}
fn resolve_lang(saved_lang: &str, locale: &str, cjk_fallback: bool) -> String {
pub fn translate_locale(name: String, locale: &str) -> String {
let locale = locale.to_lowercase();
let mut lang = saved_lang.to_lowercase();
if cjk_fallback && is_cjk_lang(&lang) {
return "en".to_owned();
}
let mut lang = hbb_common::config::LocalConfig::get_option("lang").to_lowercase();
if lang.is_empty() {
// zh_CN on Linux, zh-Hans-CN on mac, zh_CN_#Hans on Android
if locale.starts_with("zh") {
@@ -145,25 +131,7 @@ fn resolve_lang(saved_lang: &str, locale: &str, cjk_fallback: bool) -> String {
.unwrap_or_default()
.to_owned();
}
if cjk_fallback && is_cjk_lang(&lang) {
"en".to_owned()
} else {
lang
}
}
#[cfg(not(any(target_os = "android", target_os = "ios")))]
pub fn translate(name: String) -> String {
let locale = sys_locale::get_locale().unwrap_or_default();
translate_locale(name, &locale)
}
pub fn translate_locale(name: String, locale: &str) -> String {
let lang = resolve_lang(
&hbb_common::config::LocalConfig::get_option("lang"),
locale,
cjk_ui_unavailable(),
);
let lang = lang.to_lowercase();
let m = match lang.as_str() {
"fr" => fr::T.deref(),
"zh-cn" => cn::T.deref(),
@@ -307,32 +275,4 @@ mod test {
("{} times {4} makes {8}".to_string(), Some("2".to_string()))
);
}
#[test]
fn test_resolve_lang_forces_english_for_saved_cjk_when_target_disables_cjk() {
use super::resolve_lang as f;
assert_eq!(f("zh-cn", "en-US", true), "en");
assert_eq!(f("zh-tw", "en-US", true), "en");
assert_eq!(f("ja", "en-US", true), "en");
assert_eq!(f("ko", "en-US", true), "en");
}
#[test]
fn test_resolve_lang_forces_english_for_cjk_locale_when_target_disables_cjk() {
use super::resolve_lang as f;
assert_eq!(f("", "zh_CN", true), "en");
assert_eq!(f("", "ja-JP", true), "en");
assert_eq!(f("", "ko_KR", true), "en");
}
#[test]
fn test_resolve_lang_preserves_cjk_when_target_allows_cjk() {
use super::resolve_lang as f;
assert_eq!(f("zh-cn", "en-US", false), "zh-cn");
assert_eq!(f("", "zh_TW", false), "zh-tw");
assert_eq!(f("", "ja-JP", false), "ja");
}
}
-5
View File
@@ -758,10 +758,5 @@ 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", "الإظهار على شريط الأدوات المُصغّر"),
("All monitors", "جميع الشاشات"),
("#{} monitor", "الشاشة رقم {}"),
].iter().cloned().collect();
}
-5
View File
@@ -758,10 +758,5 @@ 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", "Паказваць на згорнутай панэлі інструментаў"),
("All monitors", "Усе манітори"),
("#{} monitor", "Манітор {}"),
].iter().cloned().collect();
}
-5
View File
@@ -758,10 +758,5 @@ 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", "Показване в минимизираната лента с инструменти"),
("All monitors", "Всички монитори"),
("#{} monitor", "Монитор {}"),
].iter().cloned().collect();
}
-5
View File
@@ -758,10 +758,5 @@ 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 deines principal"),
("Show on the minimized toolbar", "Mostra a la barra deines minimitzada"),
("All monitors", "Tots els monitors"),
("#{} monitor", "Monitor {}"),
].iter().cloned().collect();
}
-5
View File
@@ -758,10 +758,5 @@ 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", "在最小化工具栏上显示"),
("All monitors", "所有显示器"),
("#{} monitor", "{}号显示器"),
].iter().cloned().collect();
}
-5
View File
@@ -758,10 +758,5 @@ 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ů"),
("All monitors", "Všechny monitory"),
("#{} monitor", "Monitor č. {}"),
].iter().cloned().collect();
}
-5
View File
@@ -758,10 +758,5 @@ 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"),
("All monitors", "Alle skærme"),
("#{} monitor", "Skærm {}"),
].iter().cloned().collect();
}
-5
View File
@@ -758,10 +758,5 @@ 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"),
("All monitors", "Alle Bildschirme"),
("#{} monitor", "Bildschirm {}"),
].iter().cloned().collect();
}
-5
View File
@@ -758,10 +758,5 @@ 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", "Εμφάνιση στην ελαχιστοποιημένη γραμμή εργαλείων"),
("All monitors", "Όλες οι οθόνες"),
("#{} monitor", "Οθόνη {}"),
].iter().cloned().collect();
}
-5
View File
@@ -758,10 +758,5 @@ 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"),
("All monitors", "Ĉiuj monitoroj"),
("#{} monitor", "Monitoro {}"),
].iter().cloned().collect();
}
-5
View File
@@ -758,10 +758,5 @@ 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"),
("All monitors", "Todos los monitores"),
("#{} monitor", "Monitor {}"),
].iter().cloned().collect();
}
-5
View File
@@ -758,10 +758,5 @@ 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"),
("All monitors", "Kõik kuvarid"),
("#{} monitor", "Kuvar {}"),
].iter().cloned().collect();
}
-5
View File
@@ -758,10 +758,5 @@ 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"),
("All monitors", "Monitore guztiak"),
("#{} monitor", "{}. monitorea"),
].iter().cloned().collect();
}
-5
View File
@@ -758,10 +758,5 @@ 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", "نمایش در نوار ابزار کوچک‌شده"),
("All monitors", "همه نمایشگرها"),
("#{} monitor", "نمایشگر {}"),
].iter().cloned().collect();
}
-5
View File
@@ -758,10 +758,5 @@ 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ä"),
("All monitors", "Kaikki näytöt"),
("#{} monitor", "Näyttö {}"),
].iter().cloned().collect();
}
-5
View File
@@ -758,10 +758,5 @@ 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 doutils principale"),
("Show on the minimized toolbar", "Afficher dans la barre doutils réduite"),
("All monitors", "Tous les moniteurs"),
("#{} monitor", "Moniteur {}"),
].iter().cloned().collect();
}
-5
View File
@@ -758,10 +758,5 @@ 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", "ჩვენება ჩაკეცილ ხელსაწყოთა ზოლზე"),
("All monitors", "ყველა მონიტორი"),
("#{} monitor", "მონიტორი {}"),
].iter().cloned().collect();
}
-5
View File
@@ -758,10 +758,5 @@ 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", "ન્યૂનતમ કરેલા ટૂલબાર પર બતાવો"),
("All monitors", "બધા મોનિટર"),
("#{} monitor", "મોનિટર {}"),
].iter().cloned().collect();
}
-5
View File
@@ -758,10 +758,5 @@ 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", "הצגה בסרגל הכלים הממוזער"),
("All monitors", "כל המסכים"),
("#{} monitor", "מסך {}"),
].iter().cloned().collect();
}
-5
View File
@@ -758,10 +758,5 @@ 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", "न्यूनतम किए गए टूलबार पर दिखाएं"),
("All monitors", "सभी मॉनिटर"),
("#{} monitor", "मॉनिटर {}"),
].iter().cloned().collect();
}
-5
View File
@@ -758,10 +758,5 @@ 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"),
("All monitors", "Svi monitori"),
("#{} monitor", "Monitor {}"),
].iter().cloned().collect();
}
-5
View File
@@ -758,10 +758,5 @@ 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"),
("All monitors", "Minden monitor"),
("#{} monitor", "{}. monitor"),
].iter().cloned().collect();
}
-5
View File
@@ -758,10 +758,5 @@ 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"),
("All monitors", "Semua monitor"),
("#{} monitor", "Monitor {}"),
].iter().cloned().collect();
}
-5
View File
@@ -758,10 +758,5 @@ 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", "Visualizza nella barra strumenti principale il pulsante per il cambio schermo"),
("Show on the minimized toolbar", "Visualizza nella barra strumenti ridotta a icona"),
("All monitors", "Tutti gli schermi"),
("#{} monitor", "Schermo {}"),
].iter().cloned().collect();
}
+20 -25
View File
@@ -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 デスクトップポータルがクラッシュしたか、利用できない可能性があります。`systemctl --user restart xdg-desktop-portal` で再起動してみてください。"),
("xdp-portal-unavailable", "Wayland の画面キャプチャに失敗しました。XDG Desktop Portal がクラッシュしたか、利用できない可能性があります。`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,10 +758,5 @@ 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", "最小化したツールバーに表示"),
("All monitors", "すべてのディスプレイ"),
("#{} monitor", "ディスプレイ {}"),
].iter().cloned().collect();
}
+7 -12
View File
@@ -44,7 +44,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("id_change_tip", "a-z, A-Z, 0-9, -(대시) 및 _(밑줄) 문자만 허용됩니다. 첫 글자는 a-z, A-Z여야 합니다. 길이는 6에서 16 사이여야 합니다."),
("Website", "웹사이트"),
("About", "정보"),
("Slogan_tip", "이 혼란스러운 세상에서 마음을 담아 만들었습니다!"),
("Slogan_tip", "이 혼란스러운 세상에서 마음을 담아 만들었습니다! - 한국어 번역: 비너스걸"),
("Privacy Statement", "개인정보 보호정책"),
("Mute", "음소거"),
("Build Date", "빌드 날짜"),
@@ -379,7 +379,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Screen Share", "화면 공유"),
("ubuntu-21-04-required", "Wayland는 Ubuntu 21.04 이상 버전이 필요합니다."),
("wayland-requires-higher-linux-version", "Wayland는 상위 버전의 Linux 배포판이 필요합니다. X11 데스크탑을 사용하거나 OS를 변경하세요."),
("xdp-portal-unavailable", ""),
("xdp-portal-unavailable", "Wayland 화면 캡처에 실패했습니다. XDG Desktop Portal이 충돌했거나 사용할 수 없는 상태일 수 있습니다. `systemctl --user restart xdg-desktop-portal` 명령으로 다시 시작해 보세요."),
("JumpLink", "점프 링크"),
("Please Select the screen to be shared(Operate on the peer side).", "공유할 화면을 선택하세요 (피어 측에서 작동)"),
("Show RustDesk", "RustDesk 표시"),
@@ -749,19 +749,14 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Deploy", "배포"),
("Custom ID (optional)", "사용자 지정 ID (선택 사항)"),
("server_requires_deployment_tip", "서버에서 이 장치를 명시적으로 배포하도록 요구합니다. 지금 배포하시겠습니까?"),
("The server does not require explicit deployment.", "서버에서 명시적 배포를 요구하지 않습니다."),
("The server does not require explicit deployment.", "서버에서 명시적 배포를 요구하지 않습니다."),
("Unknown response.", "알 수 없는 응답입니다."),
("wayland-keyboard-input-disabled-tip", "키보드 입력을 허용하시겠습니까?"),
("wayland-keyboard-input-consent-tip", "이 원격 컴퓨터에서 입력하는 내용 (비밀번호 포함)은 해당 컴퓨터의 다른 앱에서 읽을 수 있습니다."),
("wayland-keyboard-input-consent-tip", "이 원격 컴퓨터에서 입력하는 내용(비밀번호 포함)은 해당 컴퓨터의 다른 앱 읽을 수 있습니다."),
("wayland-keyboard-input-applies-to-tip", "이 선택이 적용되는 대상:"),
("wayland-soft-keyboard-input-label", "소프트 키보드 입력"),
("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", "최소화된 도구 모음에 표시"),
("All monitors", "모든 모니터"),
("#{} monitor", "#{} 모니터"),
("wayland-keyboard-input-reset-choice-tip", "키보드 입력 선택 초기화"),
("remember-wayland-keyboard-choice-tip", "이 원격 컴퓨터에 대해 다시 묻지 않"),
("Why this happens", " 현상이 발생하는 이유"),
].iter().cloned().collect();
}
-5
View File
@@ -758,10 +758,5 @@ 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", "Кішірейтілген құралдар тақтасында көрсету"),
("All monitors", "Барлық мониторлар"),
("#{} monitor", "Монитор {}"),
].iter().cloned().collect();
}
-5
View File
@@ -758,10 +758,5 @@ 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"),
("All monitors", "Visi monitoriai"),
("#{} monitor", "Monitorius {}"),
].iter().cloned().collect();
}
-5
View File
@@ -758,10 +758,5 @@ 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ā"),
("All monitors", "Visi monitori"),
("#{} monitor", "Monitors {}"),
].iter().cloned().collect();
}
-5
View File
@@ -758,10 +758,5 @@ 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", "ചെറുതാക്കിയ ടൂൾബാറിൽ കാണിക്കുക"),
("All monitors", "എല്ലാ മോണിറ്ററുകളും"),
("#{} monitor", "മോണിറ്റർ {}"),
].iter().cloned().collect();
}
-5
View File
@@ -758,10 +758,5 @@ 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"),
("All monitors", "Alle skjermer"),
("#{} monitor", "Skjerm {}"),
].iter().cloned().collect();
}
-5
View File
@@ -758,10 +758,5 @@ 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"),
("All monitors", "Alle monitoren"),
("#{} monitor", "Monitor {}"),
].iter().cloned().collect();
}
-5
View File
@@ -758,10 +758,5 @@ 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"),
("All monitors", "Wszystkie ekrany"),
("#{} monitor", "Ekran {}"),
].iter().cloned().collect();
}
-5
View File
@@ -758,10 +758,5 @@ 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"),
("All monitors", "Todos os monitores"),
("#{} monitor", "Monitor {}"),
].iter().cloned().collect();
}
+13 -18
View File
@@ -16,18 +16,18 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Control Remote Desktop", "Controle um Computador Remoto"),
("Transfer file", "Transferir arquivos"),
("Connect", "Conectar"),
("Recent sessions", "Sessões recentes"),
("Address book", "Lista de endereços"),
("Recent sessions", "Sessões Recentes"),
("Address book", "Lista de Endereços"),
("Confirmation", "Confirmação"),
("TCP tunneling", "Tunelamento TCP"),
("Remove", "Remover"),
("Refresh random password", "Gerar nova senha aleatória"),
("Set your own password", "Definir sua própria senha"),
("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"),
("IP Whitelisting", "Lista de IPs Permitidos"),
("IP Whitelisting", "Lista de IPs Confiáveis"),
("ID/Relay Server", "Servidor ID/Relay"),
("Import server config", "Importar Configuração do Servidor"),
("Export Server Config", "Exportar Configuração do Servidor"),
@@ -160,7 +160,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Accept and Install", "Aceitar e Instalar"),
("End-user license agreement", "Acordo de licença do usuário final"),
("Generating ...", "Gerando ..."),
("Your installation is lower version.", "Sua instalação está com uma versão desatualizada."),
("Your installation is lower version.", "Instalação desatualizada"),
("not_close_tcp_tip", "Não feche esta janela enquanto estiver utilizando o túnel"),
("Listening ...", "Escutando ..."),
("Remote Host", "Host Remoto"),
@@ -320,12 +320,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Exit Fullscreen", "Sair da Tela Cheia"),
("Fullscreen", "Tela Cheia"),
("Mobile Actions", "Ações móveis"),
("Select Monitor", "Selecionar tela"),
("Control Actions", "Ações de controle"),
("Select Monitor", "Selecionar monitor"),
("Control Actions", "Controlar ações"),
("Display Settings", "Configurações de exibição"),
("Ratio", "Proporção"),
("Image Quality", "Qualidade de imagem"),
("Scroll Style", "Estilo de rolagem"),
("Scroll Style", "Estilo de Rolagem"),
("Show Toolbar", "Mostrar barra de ferramentas"),
("Hide Toolbar", "Ocultar barra de ferramentas"),
("Direct Connection", "Conexão Direta"),
@@ -353,7 +353,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Disconnect all devices?", "Desconectar todos os dispositivos?"),
("Clear", "Limpar"),
("Audio Input Device", "Dispositivo de entrada de áudio"),
("Use IP Whitelisting", "Utilizar lista de IPs permitidos"),
("Use IP Whitelisting", "Utilizar lista de IPs confiáveis"),
("Network", "Rede"),
("Pin Toolbar", "Fixar barra de ferramentas"),
("Unpin Toolbar", "Desafixar barra de ferramentas"),
@@ -463,7 +463,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Empty Password", "Senha Vazia"),
("Me", "Eu"),
("identical_file_tip", "Este arquivo é idêntico ao do parceiro."),
("show_monitors_tip", "Mostrar telas na barra de ferramentas"),
("show_monitors_tip", "Mostrar monitores na barra de ferramentas"),
("View Mode", "Modo de visualização"),
("login_linux_tip", "Você precisa fazer login na conta Linux remota para habilitar uma sessão de desktop X"),
("verify_rustdesk_password_tip", "Verifique a senha do RustDesk"),
@@ -674,7 +674,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("dont-show-again-tip", "Não mostrar novamente"),
("Take screenshot", "Capturar tela"),
("Taking screenshot", "Capturando tela"),
("screenshot-merged-screen-not-supported-tip", "A captura de tela de múltiplas telas não é suportada no momento. Por favor, alterne para uma única tela e tente novamente."),
("screenshot-merged-screen-not-supported-tip", "Mesclar a captura de tela de múltiplos monitores não é suportada no momento. Por favor, alterne para um único monitor e tente novamente."),
("screenshot-action-tip", "Por favor, selecione como deseja continuar com a captura de tela."),
("Save as", "Salvar como"),
("Copy to clipboard", "Copiar para área de transferência"),
@@ -694,7 +694,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Enable UDP hole punching", "Habilitar UDP hole punching"),
("View camera", "Visualizar câmera"),
("Enable camera", "Habilitar câmera"),
("No cameras", "Nenhuma 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"),
@@ -758,10 +758,5 @@ 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 botão de troca de tela na barra de ferramentas"),
("Show on the minimized toolbar", "Mostrar na barra de ferramentas minimizada"),
("All monitors", "Todas as telas"),
("#{} monitor", "Tela {}"),
].iter().cloned().collect();
}
-5
View File
@@ -758,10 +758,5 @@ 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ă"),
("All monitors", "Toate monitoarele"),
("#{} monitor", "Monitor {}"),
].iter().cloned().collect();
}
-5
View File
@@ -758,10 +758,5 @@ 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", "Показывать на свёрнутой панели инструментов"),
("All monitors", "Все мониторы"),
("#{} monitor", "Монитор {}"),
].iter().cloned().collect();
}
-5
View File
@@ -758,10 +758,5 @@ 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"),
("All monitors", "Totu sos ischermos"),
("#{} monitor", "Ischermu {}"),
].iter().cloned().collect();
}
-5
View File
@@ -758,10 +758,5 @@ 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"),
("All monitors", "Všetky monitory"),
("#{} monitor", "Monitor {}"),
].iter().cloned().collect();
}
-5
View File
@@ -758,10 +758,5 @@ 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"),
("All monitors", "Vsi zasloni"),
("#{} monitor", "Zaslon {}"),
].iter().cloned().collect();
}
-5
View File
@@ -758,10 +758,5 @@ 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"),
("All monitors", "Të gjithë monitorët"),
("#{} monitor", "Monitori {}"),
].iter().cloned().collect();
}
-5
View File
@@ -758,10 +758,5 @@ 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", "Прикажи на умањеној траци са алаткама"),
("All monitors", "Svi monitori"),
("#{} monitor", "Monitor {}"),
].iter().cloned().collect();
}
-5
View File
@@ -758,10 +758,5 @@ 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"),
("All monitors", "Alla skärmar"),
("#{} monitor", "Skärm {}"),
].iter().cloned().collect();
}
-5
View File
@@ -758,10 +758,5 @@ 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", "சிறிதாக்கப்பட்ட கருவிப்பட்டையில் காட்டு"),
("All monitors", "அனைத்து மானிட்டர்களும்"),
("#{} monitor", "மானிட்டர் {}"),
].iter().cloned().collect();
}
-5
View File
@@ -758,10 +758,5 @@ 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", ""),
("All monitors", ""),
("#{} monitor", ""),
].iter().cloned().collect();
}
-5
View File
@@ -758,10 +758,5 @@ 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", "แสดงบนแถบเครื่องมือที่ย่อเล็กสุด"),
("All monitors", "จอภาพทั้งหมด"),
("#{} monitor", "จอภาพ {}"),
].iter().cloned().collect();
}
-5
View File
@@ -758,10 +758,5 @@ 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"),
("All monitors", "Tüm monitörler"),
("#{} monitor", "Monitör {}"),
].iter().cloned().collect();
}
-5
View File
@@ -758,10 +758,5 @@ 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", "在最小化工具列上顯示"),
("All monitors", "所有顯示器"),
("#{} monitor", "{}號顯示器"),
].iter().cloned().collect();
}

Some files were not shown because too many files have changed in this diff Show More