mirror of
https://github.com/rustdesk/rustdesk.git
synced 2026-02-22 17:48:34 +08:00
Compare commits
98 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3176391693 | ||
|
|
5277300943 | ||
|
|
878e1ff290 | ||
|
|
8d453010a4 | ||
|
|
e2f6030590 | ||
|
|
bf3f8706f8 | ||
|
|
5c9b4abab2 | ||
|
|
9fb4862a45 | ||
|
|
65df6897a6 | ||
|
|
529810f2f4 | ||
|
|
df0ff4f134 | ||
|
|
6c949a9602 | ||
|
|
f933f46283 | ||
|
|
4080907d2b | ||
|
|
ed5cd21cb6 | ||
|
|
aa8278e1d5 | ||
|
|
0f526fce6c | ||
|
|
15d471e520 | ||
|
|
c47e94813d | ||
|
|
c979cbcac7 | ||
|
|
6b2a1dfd84 | ||
|
|
7948d3144a | ||
|
|
d499098c4f | ||
|
|
42be442385 | ||
|
|
e2ec6a5be8 | ||
|
|
438cef8cf9 | ||
|
|
7bacf7cdc9 | ||
|
|
c5e76972aa | ||
|
|
7ca8e0d437 | ||
|
|
a98852e279 | ||
|
|
d0e9c6dc57 | ||
|
|
ac70f380a6 | ||
|
|
34cf9d6181 | ||
|
|
db4296533a | ||
|
|
6381f43f01 | ||
|
|
f4fb31d7a1 | ||
|
|
9e22f9639a | ||
|
|
9b854d3034 | ||
|
|
2c88a44a53 | ||
|
|
0c2b86c8e7 | ||
|
|
74752bbd2f | ||
|
|
ad396b4155 | ||
|
|
5ff1740b5b | ||
|
|
e0ab3f0c92 | ||
|
|
9b77e91d79 | ||
|
|
d187121645 | ||
|
|
a22f2108c6 | ||
|
|
bf24869c6a | ||
|
|
4e9a370ff6 | ||
|
|
1aed6f3c2e | ||
|
|
6367d50d76 | ||
|
|
f33ed27419 | ||
|
|
870c8cb158 | ||
|
|
0b9d7925b5 | ||
|
|
16b625f8b4 | ||
|
|
16d301a783 | ||
|
|
212bbaf44c | ||
|
|
1d6037003a | ||
|
|
6f4b23b40b | ||
|
|
4e82766ba4 | ||
|
|
dc86db5206 | ||
|
|
5a75ea723b | ||
|
|
d59f216c26 | ||
|
|
160edcc1cd | ||
|
|
59d597de8a | ||
|
|
806351b6c1 | ||
|
|
e7909a0dbd | ||
|
|
d6d44be1b7 | ||
|
|
53efaf125c | ||
|
|
1fb0123ed7 | ||
|
|
a0659a277a | ||
|
|
77064cc2f8 | ||
|
|
1954790808 | ||
|
|
4263643200 | ||
|
|
43ec57c769 | ||
|
|
302dad2016 | ||
|
|
fdb8b498cb | ||
|
|
f6af59b044 | ||
|
|
ad1ed132d1 | ||
|
|
466d456760 | ||
|
|
6bc3b38b56 | ||
|
|
39b91911cb | ||
|
|
e85989e9d9 | ||
|
|
e7f672899b | ||
|
|
9538eba64e | ||
|
|
b37b271fce | ||
|
|
77be752ff1 | ||
|
|
725a47268e | ||
|
|
2ba215a6d7 | ||
|
|
6533a1b98d | ||
|
|
1f2f5a41d4 | ||
|
|
4e7680e322 | ||
|
|
f32591c3d1 | ||
|
|
6ec217263d | ||
|
|
8899b90725 | ||
|
|
7ece7e730a | ||
|
|
d55b98b187 | ||
|
|
d9674a2d77 |
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
@@ -5,7 +5,7 @@ env:
|
||||
# CICD_INTERMEDIATES_DIR: "_cicd-intermediates"
|
||||
VCPKG_BINARY_SOURCES: "clear;x-gha,readwrite"
|
||||
# for multiarch gcc compatibility
|
||||
VCPKG_COMMIT_ID: "6f29f12e82a8293156836ad81cc9bf5af41fe836"
|
||||
VCPKG_COMMIT_ID: "120deac3062162151622ca4860575a33844ba10b"
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
153
.github/workflows/flutter-build.yml
vendored
153
.github/workflows/flutter-build.yml
vendored
@@ -31,14 +31,15 @@ env:
|
||||
FLUTTER_ELINUX_VERSION: "3.16.9"
|
||||
TAG_NAME: "${{ inputs.upload-tag }}"
|
||||
VCPKG_BINARY_SOURCES: "clear;x-gha,readwrite"
|
||||
# vcpkg version: 2025.01.13
|
||||
# vcpkg version: 2025.08.27
|
||||
# If we change the `VCPKG COMMIT_ID`, please remember:
|
||||
# 1. Call `$VCPKG_ROOT/vcpkg x-update-baseline` to update the baseline in `vcpkg.json`.
|
||||
# Or we may face build issue like
|
||||
# https://github.com/rustdesk/rustdesk/actions/runs/14414119794/job/40427970174
|
||||
# 2. Update the `VCPKG_COMMIT_ID` in `ci.yml` and `playground.yml`.
|
||||
VCPKG_COMMIT_ID: "6f29f12e82a8293156836ad81cc9bf5af41fe836"
|
||||
VERSION: "1.4.1"
|
||||
VCPKG_COMMIT_ID: "120deac3062162151622ca4860575a33844ba10b"
|
||||
ARMV7_VCPKG_COMMIT_ID: "6f29f12e82a8293156836ad81cc9bf5af41fe836" # 2025.01.13, got "/opt/artifacts/vcpkg/vcpkg: No such file or directory" with latest version
|
||||
VERSION: "1.4.2"
|
||||
NDK_VERSION: "r27c"
|
||||
#signing keys env variable checks
|
||||
ANDROID_SIGNING_KEY: "${{ secrets.ANDROID_SIGNING_KEY }}"
|
||||
@@ -424,80 +425,6 @@ jobs:
|
||||
files: |
|
||||
./SignOutput/rustdesk-*.exe
|
||||
|
||||
build-for-macOS-arm64-selfhost:
|
||||
# use build-for-macOS instead
|
||||
if: false
|
||||
runs-on: [self-hosted, macOS, ARM64]
|
||||
needs: [generate-bridge]
|
||||
steps:
|
||||
- name: Export GitHub Actions cache environment variables
|
||||
uses: actions/github-script@v6
|
||||
with:
|
||||
script: |
|
||||
core.exportVariable('ACTIONS_CACHE_URL', process.env.ACTIONS_CACHE_URL || '');
|
||||
core.exportVariable('ACTIONS_RUNTIME_TOKEN', process.env.ACTIONS_RUNTIME_TOKEN || '');
|
||||
|
||||
- name: Checkout source code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Restore bridge files
|
||||
uses: actions/download-artifact@master
|
||||
with:
|
||||
name: bridge-artifact
|
||||
path: ./
|
||||
|
||||
- name: Build rustdesk
|
||||
run: |
|
||||
./build.py --flutter --hwcodec --unix-file-copy-paste
|
||||
|
||||
- name: create unsigned dmg
|
||||
if: env.UPLOAD_ARTIFACT == 'true'
|
||||
run: |
|
||||
CREATE_DMG="$(command -v create-dmg)"
|
||||
CREATE_DMG="$(readlink -f "$CREATE_DMG")"
|
||||
sed -i -e 's/MAXIMUM_UNMOUNTING_ATTEMPTS=3/MAXIMUM_UNMOUNTING_ATTEMPTS=7/' "$CREATE_DMG"
|
||||
create-dmg --icon "RustDesk.app" 200 190 --hide-extension "RustDesk.app" --window-size 800 400 --app-drop-link 600 185 rustdesk-${{ env.VERSION }}-arm64.dmg ./flutter/build/macos/Build/Products/Release/RustDesk.app
|
||||
|
||||
- name: Upload unsigned macOS app
|
||||
if: env.UPLOAD_ARTIFACT == 'true'
|
||||
uses: actions/upload-artifact@master
|
||||
with:
|
||||
name: rustdesk-unsigned-macos-arm64
|
||||
path: rustdesk-${{ env.VERSION }}-arm64.dmg # can not upload the directory directly or tar.gz file, which destroy the link structure, causing the codesign failed
|
||||
|
||||
- name: Codesign app and create signed dmg
|
||||
if: env.MACOS_P12_BASE64 != null && env.UPLOAD_ARTIFACT == 'true'
|
||||
run: |
|
||||
# Patch create-dmg to give more attempts to unmount image
|
||||
CREATE_DMG="$(command -v create-dmg)"
|
||||
CREATE_DMG="$(readlink -f "$CREATE_DMG")"
|
||||
sed -i -e 's/MAXIMUM_UNMOUNTING_ATTEMPTS=3/MAXIMUM_UNMOUNTING_ATTEMPTS=7/' "$CREATE_DMG"
|
||||
# start sign the rustdesk.app and dmg
|
||||
rm -rf *.dmg || true
|
||||
codesign --force --options runtime -s ${{ secrets.MACOS_CODESIGN_IDENTITY }} --deep --strict ./flutter/build/macos/Build/Products/Release/RustDesk.app -vvv
|
||||
create-dmg --icon "RustDesk.app" 200 190 --hide-extension "RustDesk.app" --window-size 800 400 --app-drop-link 600 185 rustdesk-${{ env.VERSION }}.dmg ./flutter/build/macos/Build/Products/Release/RustDesk.app
|
||||
codesign --force --options runtime -s ${{ secrets.MACOS_CODESIGN_IDENTITY }} --deep --strict rustdesk-${{ env.VERSION }}.dmg -vvv
|
||||
# notarize the rustdesk-${{ env.VERSION }}.dmg
|
||||
rcodesign notary-submit --api-key-path ~/.p12/api-key.json --staple rustdesk-${{ env.VERSION }}.dmg
|
||||
|
||||
- name: Rename rustdesk
|
||||
if: env.UPLOAD_ARTIFACT == 'true'
|
||||
run: |
|
||||
for name in rustdesk*??.dmg; do
|
||||
mv "$name" "${name%%.dmg}-aarch64.dmg"
|
||||
done
|
||||
|
||||
- name: Publish DMG package
|
||||
if: env.UPLOAD_ARTIFACT == 'true'
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
prerelease: true
|
||||
tag_name: ${{ env.TAG_NAME }}
|
||||
files: |
|
||||
rustdesk*-aarch64.dmg
|
||||
|
||||
build-rustdesk-ios:
|
||||
if: ${{ inputs.upload-artifact }}
|
||||
name: build rustdesk ios ipa
|
||||
@@ -617,63 +544,6 @@ jobs:
|
||||
# files: |
|
||||
# flutter/build/ios/ipa/*.ipa
|
||||
|
||||
build-rustdesk-ios-selfhost:
|
||||
#if: ${{ inputs.upload-artifact }}
|
||||
if: false
|
||||
runs-on: [self-hosted, macOS, ARM64]
|
||||
needs: [generate-bridge]
|
||||
strategy:
|
||||
fail-fast: false
|
||||
steps:
|
||||
- name: Export GitHub Actions cache environment variables
|
||||
uses: actions/github-script@v6
|
||||
with:
|
||||
script: |
|
||||
core.exportVariable('ACTIONS_CACHE_URL', process.env.ACTIONS_CACHE_URL || '');
|
||||
core.exportVariable('ACTIONS_RUNTIME_TOKEN', process.env.ACTIONS_RUNTIME_TOKEN || '');
|
||||
|
||||
- name: Checkout source code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
# $VCPKG_ROOT/vcpkg install --triplet arm64-ios --x-install-root="$VCPKG_ROOT/installed"
|
||||
|
||||
- name: Restore bridge files
|
||||
uses: actions/download-artifact@master
|
||||
with:
|
||||
name: bridge-artifact
|
||||
path: ./
|
||||
|
||||
- name: Build rustdesk lib
|
||||
run: |
|
||||
cargo build --features flutter,hwcodec --release --target aarch64-apple-ios --lib
|
||||
|
||||
- name: Build rustdesk
|
||||
# ios sdk not installed on this machine, I will install it later after I am back home
|
||||
if: false
|
||||
shell: bash
|
||||
run: |
|
||||
pushd flutter
|
||||
# flutter build ipa --release --obfuscate --split-debug-info=./split-debug-info --no-codesign
|
||||
# for easy debugging
|
||||
flutter build ipa --release --no-codesign
|
||||
|
||||
# - name: Upload Artifacts
|
||||
# # if: env.ANDROID_SIGNING_KEY != null && env.UPLOAD_ARTIFACT == 'true'
|
||||
# uses: actions/upload-artifact@master
|
||||
# with:
|
||||
# name: rustdesk-${{ env.VERSION }}-${{ matrix.job.arch }}.apk
|
||||
# path: flutter/build/ios/ipa/*.ipa
|
||||
|
||||
# - name: Publish ipa package
|
||||
# # if: env.ANDROID_SIGNING_KEY != null && env.UPLOAD_ARTIFACT == 'true'
|
||||
# uses: softprops/action-gh-release@v1
|
||||
# with:
|
||||
# prerelease: true
|
||||
# tag_name: ${{ env.TAG_NAME }}
|
||||
# files: |
|
||||
# flutter/build/ios/ipa/*.ipa
|
||||
|
||||
build-for-macOS:
|
||||
name: ${{ matrix.job.target }}
|
||||
@@ -692,7 +562,7 @@ jobs:
|
||||
}
|
||||
- {
|
||||
target: aarch64-apple-darwin,
|
||||
os: macos-latest,
|
||||
os: macos-14,
|
||||
# extra-build-args: "--disable-flutter-texture-render", # disable this for mac, because we see a lot of users reporting flickering both on arm and x64, and we can not confirm if texture rendering has better performance if htere is no vram, https://github.com/rustdesk/rustdesk/issues/6296
|
||||
extra-build-args: "--screencapturekit",
|
||||
arch: aarch64,
|
||||
@@ -746,7 +616,7 @@ jobs:
|
||||
|
||||
- name: Install build runtime
|
||||
run: |
|
||||
brew install llvm create-dmg nasm cmake gcc wget ninja
|
||||
brew install llvm create-dmg nasm
|
||||
# pkg-config is handled in a separate step, because it may be already installed by `macos-latest`(14.7.1) runner
|
||||
if command -v pkg-config &>/dev/null; then
|
||||
echo "pkg-config is already installed"
|
||||
@@ -1768,6 +1638,14 @@ jobs:
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Modify vcpkg.json for armv7
|
||||
if: matrix.job.vcpkg-triplet == 'arm-linux'
|
||||
run: |
|
||||
# Replace the baseline in vcpkg.json with ARMV7_VCPKG_COMMIT_ID for armv7 builds
|
||||
sed -i 's/"baseline": ".*"/"baseline": "${{ env.ARMV7_VCPKG_COMMIT_ID }}"/' vcpkg.json
|
||||
echo "Modified vcpkg.json for armv7 build:"
|
||||
grep -A 2 -B 2 '"baseline"' vcpkg.json
|
||||
|
||||
- name: Free Space
|
||||
run: |
|
||||
df -h
|
||||
@@ -1853,11 +1731,12 @@ jobs:
|
||||
rm -rf vcpkg
|
||||
git clone https://github.com/microsoft/vcpkg
|
||||
pushd vcpkg
|
||||
git reset --hard ${{ env.VCPKG_COMMIT_ID }}
|
||||
# build vcpkg helper executable with gcc-8 for arm-linux but use prebuilt one on x64-linux
|
||||
if [ "${{ matrix.job.vcpkg-triplet }}" = "arm-linux" ]; then
|
||||
git reset --hard ${{ env.ARMV7_VCPKG_COMMIT_ID }}
|
||||
CC=/usr/bin/gcc-8 CXX=/usr/bin/g++-8 sh bootstrap-vcpkg.sh -disableMetrics
|
||||
else
|
||||
git reset --hard ${{ env.VCPKG_COMMIT_ID }}
|
||||
sh bootstrap-vcpkg.sh -disableMetrics
|
||||
fi
|
||||
popd
|
||||
|
||||
6
.github/workflows/playground.yml
vendored
6
.github/workflows/playground.yml
vendored
@@ -16,8 +16,8 @@ env:
|
||||
FLUTTER_ELINUX_VERSION: "3.16.9"
|
||||
TAG_NAME: "nightly"
|
||||
VCPKG_BINARY_SOURCES: "clear;x-gha,readwrite"
|
||||
VCPKG_COMMIT_ID: "6f29f12e82a8293156836ad81cc9bf5af41fe836"
|
||||
VERSION: "1.4.1"
|
||||
VCPKG_COMMIT_ID: "120deac3062162151622ca4860575a33844ba10b"
|
||||
VERSION: "1.4.2"
|
||||
NDK_VERSION: "r26d"
|
||||
#signing keys env variable checks
|
||||
ANDROID_SIGNING_KEY: "${{ secrets.ANDROID_SIGNING_KEY }}"
|
||||
@@ -126,7 +126,7 @@ jobs:
|
||||
|
||||
- name: Install build runtime
|
||||
run: |
|
||||
brew install llvm create-dmg nasm cmake gcc wget ninja pkg-config
|
||||
brew install llvm create-dmg nasm pkg-config
|
||||
|
||||
- name: Install flutter
|
||||
uses: subosito/flutter-action@v2
|
||||
|
||||
4
.github/workflows/winget.yml
vendored
4
.github/workflows/winget.yml
vendored
@@ -10,6 +10,6 @@ jobs:
|
||||
- uses: vedantmgoyal9/winget-releaser@main
|
||||
with:
|
||||
identifier: RustDesk.RustDesk
|
||||
version: "1.4.1"
|
||||
release-tag: "1.4.1"
|
||||
version: "1.4.2"
|
||||
release-tag: "1.4.2"
|
||||
token: ${{ secrets.WINGET_TOKEN }}
|
||||
|
||||
794
Cargo.lock
generated
794
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
12
Cargo.toml
12
Cargo.toml
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "rustdesk"
|
||||
version = "1.4.1"
|
||||
version = "1.4.2"
|
||||
authors = ["rustdesk <info@rustdesk.com>"]
|
||||
edition = "2021"
|
||||
build= "build.rs"
|
||||
@@ -144,6 +144,10 @@ core-graphics = "0.22"
|
||||
include_dir = "0.7"
|
||||
fruitbasket = "0.10"
|
||||
objc_id = "0.1"
|
||||
# If we use piet "0.7" here, we must also update core-graphics to "0.24".
|
||||
piet = "0.6"
|
||||
piet-coregraphics = "0.6"
|
||||
foreign-types = "0.3"
|
||||
|
||||
[target.'cfg(any(target_os = "macos", target_os = "linux", target_os = "windows"))'.dependencies]
|
||||
tray-icon = { git = "https://github.com/tauri-apps/tray-icon" }
|
||||
@@ -155,6 +159,11 @@ keepawake = { git = "https://github.com/rustdesk-org/keepawake-rs" }
|
||||
|
||||
[target.'cfg(any(target_os = "windows", target_os = "linux"))'.dependencies]
|
||||
wallpaper = { git = "https://github.com/rustdesk-org/wallpaper.rs" }
|
||||
tiny-skia = "0.11"
|
||||
softbuffer = "0.4"
|
||||
fontdb = "0.23"
|
||||
bytemuck = "1.23"
|
||||
ttf-parser = "0.25"
|
||||
|
||||
[target.'cfg(any(target_os = "macos", target_os = "windows"))'.dependencies]
|
||||
# https://github.com/rustdesk/rustdesk-server-pro/issues/189, using native-tls for better tls support
|
||||
@@ -181,6 +190,7 @@ nix = { version = "0.29", features = ["term", "process"]}
|
||||
gtk = "0.18"
|
||||
termios = "0.3"
|
||||
terminfo = "0.8"
|
||||
winit = "0.30"
|
||||
|
||||
[target.'cfg(target_os = "android")'.dependencies]
|
||||
android_logger = "0.13"
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
|
||||
Chat with us: [Discord](https://discord.gg/nDceKgxnkV) | [Twitter](https://twitter.com/rustdesk) | [Reddit](https://www.reddit.com/r/rustdesk) | [YouTube](https://www.youtube.com/@rustdesk)
|
||||
|
||||
[](https://ko-fi.com/I2I04VU09)
|
||||
[](https://rustdesk.com/pricing.html)
|
||||
|
||||
Yet another remote desktop solution, written in Rust. Works out of the box with no configuration required. You have full control of your data, with no concerns about security. You can use our rendezvous/relay server, [set up your own](https://rustdesk.com/server), or [write your own rendezvous/relay server](https://github.com/rustdesk/rustdesk-server-demo).
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ AppDir:
|
||||
id: rustdesk
|
||||
name: rustdesk
|
||||
icon: rustdesk
|
||||
version: 1.4.1
|
||||
version: 1.4.2
|
||||
exec: usr/share/rustdesk/rustdesk
|
||||
exec_args: $@
|
||||
apt:
|
||||
|
||||
@@ -18,7 +18,7 @@ AppDir:
|
||||
id: rustdesk
|
||||
name: rustdesk
|
||||
icon: rustdesk
|
||||
version: 1.4.1
|
||||
version: 1.4.2
|
||||
exec: usr/share/rustdesk/rustdesk
|
||||
exec_args: $@
|
||||
apt:
|
||||
|
||||
7
build.rs
7
build.rs
@@ -68,11 +68,8 @@ fn install_android_deps() {
|
||||
}
|
||||
path.push(target);
|
||||
println!(
|
||||
"{}",
|
||||
format!(
|
||||
"cargo:rustc-link-search={}",
|
||||
path.join("lib").to_str().unwrap()
|
||||
)
|
||||
"cargo:rustc-link-search={}",
|
||||
path.join("lib").to_str().unwrap()
|
||||
);
|
||||
println!("cargo:rustc-link-lib=ndk_compat");
|
||||
println!("cargo:rustc-link-lib=oboe");
|
||||
|
||||
137
docs/CODE_OF_CONDUCT-DE.md
Normal file
137
docs/CODE_OF_CONDUCT-DE.md
Normal file
@@ -0,0 +1,137 @@
|
||||
|
||||
# Verhaltenskodex (Code of Conduct) für Mitwirkende
|
||||
|
||||
## Unsere Verpflichtung
|
||||
|
||||
Wir als Mitglieder, Mitwirkende und Führungskräfte verpflichten uns,
|
||||
die Teilnahme unserer Community zu einer Erfahrung zu machen,
|
||||
die für alle frei von Belästigungen ist, unabhängig von Alter, Körpergröße,
|
||||
sichtbarer oder unsichtbarer Behinderung, ethnischer Zugehörigkeit,
|
||||
Geschlechtsmerkmalen, Geschlechtsidentität und -ausdruck, Erfahrungsniveau,
|
||||
Bildung, sozioökonomischem Status, Nationalität, persönlichem Erscheinungsbild,
|
||||
Rasse, Religion oder sexueller Identität und Orientierung.
|
||||
|
||||
Wir verpflichten uns, so zu handeln und zu interagieren, dass wir zu einer offenen,
|
||||
einladenden, vielfältigen, integrativen und lebendigen Gemeinschaft beitragen.
|
||||
|
||||
## Unsere Standards
|
||||
|
||||
Beispiele für Verhaltensweisen, die zu einem positiven Umfeld für unsere
|
||||
Gemeinschaft beitragen, sind:
|
||||
|
||||
* Empathie und Freundlichkeit gegenüber anderen Menschen zu zeigen
|
||||
* Respektvoll gegenüber anderen Meinungen, Sichtweisen und Erfahrungen zu sein
|
||||
* Das Vergeben von sowie das großzügige Empfangen von konstruktivem Feedback
|
||||
* Verantwortung übernehmen, sich bei den Betroffenen entschuldigen
|
||||
und aus den Erfahrungen lernen
|
||||
* Nicht darauf zu achten, was das Beste für sich selbst,
|
||||
sondern zu Achten, was das Beste für die gesamte Community ist
|
||||
|
||||
Beispiele für nicht akzeptables Verhalten sind:
|
||||
|
||||
* Die Verwendung sexualisierter bzw. anstößiger Sprache oder Bilder
|
||||
sowie sexuelle Aufmerksamkeit oder Annäherungsversuche jeglicher Art
|
||||
* Trolling, beleidigende oder herabwürdigende Kommentare
|
||||
sowie persönliche oder politische Angriffe
|
||||
* Öffentliche sowie private Belästigung
|
||||
* Das Teilen privater Informationen anderer Leute ohne deren explizite Zustimmung,
|
||||
wie bspw. die physische oder die E-Mail-Adresse
|
||||
* Anderes Verhalten, das in einem professionellen Umfeld begründeter Weise als
|
||||
unangemessen angesehen werden könnte
|
||||
|
||||
## Durchsetzungsbefugnisse
|
||||
|
||||
Die Leiter der Community sind dafür verantwortlich, unsere Standards für
|
||||
akzeptables Verhalten zu klären und durchzusetzen und werden angemessene
|
||||
und faire Korrekturmaßnahmen ergreifen, wenn sie ein Verhalten als unangemessen,
|
||||
bedrohlich, beleidigend oder schädlich erachten.
|
||||
|
||||
Die Leiter der Community haben das Recht und die Pflicht, Kommentare, Commits,
|
||||
Code, Wiki-Bearbeitungen, Issues und andere Beiträge, die nicht mit dem
|
||||
Verhaltenskodex vereinbar sind, zu entfernen, zu bearbeiten oder abzulehnen.
|
||||
Sie werden, falls angebracht, die Gründe für Moderationsentscheidungen mitteilen.
|
||||
|
||||
## Geltungsbereich
|
||||
|
||||
Dieser Verhaltenskodex gilt in allen Community-Bereichen und auch dann, wenn
|
||||
eine Person die Community offiziell in öffentlichen Bereichen vertritt.
|
||||
Beispiele für die Vertretung unserer Community sind die Verwendung einer
|
||||
offiziellen E-Mail-Adresse, das Posten über einen offiziellen
|
||||
Social-Media-Account oder die Tätigkeit als ernannter
|
||||
Vertreter bei einer Online- oder Präsenzveranstaltung.
|
||||
|
||||
## Geltendmachung
|
||||
|
||||
Fälle von missbräuchlichem, belästigendem oder anderweitig inakzeptablem Verhalten können
|
||||
den für die Durchsetzung zuständigen Community-Leitern
|
||||
unter [info@rustdesk.com](mailto:info@rustdesk.com) gemeldet werden.
|
||||
Jeder Fall wird umgehend und fair geprüft und untersucht.
|
||||
|
||||
## Richtlinien zur Geltendmachung
|
||||
|
||||
Die Community-Leiter werden die folgenden Community-Auswirkungsrichtlinien befolgen,
|
||||
um die Konsequenzen für jede Handlung zu bestimmen, die sie als Verstoß gegen diesen
|
||||
Verhaltenskodex ansehen:
|
||||
|
||||
### 1. Korrektur
|
||||
|
||||
**Auswirkungen auf die Community**: Verwendung unangemessener Sprache oder anderes
|
||||
Verhalten, welches als unprofessionell oder in der Community unerwünscht angesehen wird.
|
||||
|
||||
**Konsequenz**: Eine private, schriftliche Verwarnung durch die Leiter der Community,
|
||||
in der die Art des Verstoßes klar dargelegt und erklärt wird, warum das
|
||||
Verhalten unangemessen war. Eine öffentliche Entschuldigung kann verlangt werden.
|
||||
|
||||
### 2. Warnung
|
||||
|
||||
**Auswirkungen auf die Community**: Ein Verstoß durch einen einzelnen Vorfall
|
||||
oder eine Reihe von Handlungen.
|
||||
|
||||
**Konsequenz**: Eine Verwarnung mit Konsequenzen für das weitere Verhalten. Keine
|
||||
Interaktion mit den beteiligten Personen, einschließlich unaufgeforderter Interaktion mit
|
||||
denjenigen, die den Verhaltenskodex durchsetzen, für einen bestimmten Zeitraum. Dies
|
||||
schließt die Vermeidung von Interaktionen in Gemeinschaftsräumen sowie externen Kanälen
|
||||
wie sozialen Medien ein. Ein Verstoß gegen diese Bedingungen kann zu einer vorübergehenden oder
|
||||
dauerhaften Sperrung führen.
|
||||
|
||||
### 3. Temporärer Sperrung
|
||||
|
||||
|
||||
**Auswirkungen auf die Community**: Ein schwerwiegender Verstoß gegen die Community-Standards,
|
||||
einschließlich anhaltend unangemessenem Verhalten.
|
||||
|
||||
**Konsequenz**: Eine vorübergehende Sperrung jeglicher Art von Interaktion oder öffentlicher
|
||||
Kommunikation mit der Community für einen bestimmten Zeitraum. Während dieses Zeitraums sind
|
||||
keine öffentlichen oder privaten Interaktionen mit den betroffenen Personen,
|
||||
einschließlich unaufgeforderter Interaktionen mit denjenigen,
|
||||
die den Verhaltenskodex durchsetzen, erlaubt.
|
||||
Ein Verstoß gegen diese Bedingungen kann zu einer dauerhaften Sperrung führen.
|
||||
|
||||
### 4. Dauerhafte Sperrung
|
||||
|
||||
**Auswirkungen auf die Community**: Wiederholte Verstöße gegen die Community-Standards,
|
||||
einschließlich anhaltend unangemessenem Verhalten, Belästigung einer
|
||||
Person oder Aggression gegenüber oder Herabwürdigung von Personengruppen.
|
||||
|
||||
**Konsequenz**: Ein dauerhafter Ausschluss von jeglicher öffentlicher
|
||||
Interaktion innerhalb der Community.
|
||||
|
||||
## Quellenangabe
|
||||
|
||||
Dieser Verhaltenskodex ist eine Adaption des [Contributor Covenant][homepage],
|
||||
Version 2.0, verfügbar unter
|
||||
[https://www.contributor-covenant.org/version/2/0/code_of_conduct.html][v2.0].
|
||||
|
||||
Die Richtlinien zu den Auswirkungen auf die Gemeinschaft wurden inspiriert von
|
||||
[Mozilla's code of conduct enforcement ladder][Mozilla CoC].
|
||||
|
||||
Für Antworten auf häufig gestellte Fragen zu diesem Verhaltenskodex siehe die
|
||||
häufig gestellten Fragen (FAQ) unter
|
||||
[https://www.contributor-covenant.org/faq][FAQ]. Übersetzungen sind verfügbar
|
||||
unter [https://www.contributor-covenant.org/translations][translations].
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
[v2.0]: https://www.contributor-covenant.org/version/2/0/code_of_conduct.html
|
||||
[Mozilla CoC]: https://github.com/mozilla/diversity
|
||||
[FAQ]: https://www.contributor-covenant.org/faq
|
||||
[translations]: https://www.contributor-covenant.org/translations
|
||||
133
docs/CODE_OF_CONDUCT-KR.md
Normal file
133
docs/CODE_OF_CONDUCT-KR.md
Normal file
@@ -0,0 +1,133 @@
|
||||
|
||||
# 기여자 계약 행동 강령
|
||||
|
||||
## 우리의 서약
|
||||
|
||||
회원, 기여자, 리더로서 우리는 나이, 신체 크기, 눈에
|
||||
보이거나 보이지 않는 장애, 민족, 성 특성, 성 정체성 및
|
||||
표현, 경험 수준, 교육, 사회 경제적 지위, 국적, 외모,
|
||||
인종, 종교, 성적 정체성 및 지향에 관계없이 모든 사람이
|
||||
괴롭힘 없이 커뮤니티에 참여할 수 있도록 할 것을
|
||||
서약합니다.
|
||||
|
||||
우리는 개방적이고 환영하며 다양하고 포용적이며 건강한 커뮤니티에
|
||||
기여하는 방식으로 행동하고 교류할 것을 약속합니다.
|
||||
|
||||
## 우리의 표준
|
||||
|
||||
커뮤니티의 긍정적인 환경에 기여하는 행동의 예는
|
||||
다음과 같습니다:
|
||||
|
||||
* 다른 사람들에게 공감과 친절을 보여주기
|
||||
* 다양한 의견, 관점, 경험을 존중하기
|
||||
* 건설적인 피드백을 제공하고 우아하게 받아들이기
|
||||
* 우리의 실수로 인해 영향을 받은 사람들에게 책임을 받아들이고 사과하며
|
||||
그 경험을 통해 배우기
|
||||
* 우리 개인뿐만 아니라 전체 커뮤니티에 가장 좋은 것이 무엇인지
|
||||
집중하기
|
||||
|
||||
용납할 수 없는 행동의 예는 다음과 같습니다:
|
||||
|
||||
* 성적인 언어 또는 이미지의 사용, 모든 종류의 성적 관심 또는
|
||||
접근 행위
|
||||
* 트롤링, 모욕적이거나 경멸적인 댓글, 개인적 또는 정치적 공격
|
||||
* 공개적 또는 사적인 괴롭힘
|
||||
* 명시적인 허가 없이 타인의 실제 주소 또는 이메일 주소와 같은
|
||||
개인정보를 게시하는 행위
|
||||
* 직업적 환경에서 합리적으로 부적절하다고 간주될 수 있는
|
||||
기타 행위
|
||||
|
||||
## 시행 책임
|
||||
|
||||
커뮤니티 리더는 허용되는 행동의 기준을 명확히 하고 시행할
|
||||
책임이 있으며 부적절하거나 위협적이거나 모욕적이거나
|
||||
유해하다고 판단되는 행동에 대해 적절하고 공정한 시정 조치를
|
||||
취합니다.
|
||||
|
||||
커뮤니티 리더는 본 행동 강령에 부합하지 않는 댓글, 커밋,
|
||||
코드, 위키 편집, 이슈 및 기타 기여를 삭제, 편집 또는 거부할
|
||||
권한과 책임이 있으며, 적절한 경우 중재 결정의 이유를
|
||||
전달합니다.
|
||||
|
||||
## 범위
|
||||
|
||||
본 행동 강령은 모든 커뮤니티 공간에서 적용되며, 개인이 공개
|
||||
공간에서 커뮤니티를 공식적으로 대표하는 경우에도 적용됩니다.
|
||||
커뮤니티를 대표하는 예로는 공식 이메일 주소 사용, 공식 소셜 미디어
|
||||
계정을 통한 게시, 온라인 또는 오프라인 이벤트에서 지정된 대표자로
|
||||
활동하는 것 등이 있습니다.
|
||||
|
||||
## 시행
|
||||
|
||||
모욕적, 괴롭힘 또는 기타 용납할 수 없는 행동은
|
||||
[info@rustdesk.com](mailto:info@rustdesk.com)으로 법 집행을 담당하는 커뮤니티 리더에게
|
||||
신고하실 수 있습니다.
|
||||
모든 불만 사항은 신속하고 공정하게 검토 및 조사됩니다.
|
||||
|
||||
모든 커뮤니티 리더는 모든 사건 신고자의 사생활과 보안을 존중할 의무가
|
||||
있습니다.
|
||||
|
||||
## 시행 지침
|
||||
|
||||
커뮤니티 리더는 이 행동 강령을 위반한 것으로 간주되는 모든 행동에 대한
|
||||
결과를 결정할 때 다음 커뮤니티 영향 지침을 따릅니다:
|
||||
|
||||
### 1. 수정
|
||||
|
||||
**커뮤니티 영향**: 커뮤니티에서 비전문적이거나 환영받지 못하는
|
||||
것으로 간주되는 부적절한 언어 사용이나 기타 행위입니다.
|
||||
|
||||
**결과**: 커뮤니티 리더의 비공개 서면 경고. 위반 사항의 성격과
|
||||
해당 행동이 부적절했던 이유를 명확히 설명해야 합니다.
|
||||
공개 사과를 요청할 수도 있습니다.
|
||||
|
||||
### 2. 경고
|
||||
|
||||
**커뮤니티 영향**: 단일 사건 또는 일련의 행위를 통한
|
||||
위반입니다.
|
||||
|
||||
**결과**: 지속적인 행동에 대한 경고 및 결과. 행동 강령 시행 담당자와의
|
||||
원치 않는 상호작용을 포함하여 관련자와의 상호작용은 일정
|
||||
기간 동안 금지됩니다. 여기에는 공동 공간 및 소셜 미디어와
|
||||
같은 외부 채널에서의 상호작용 금지가 포함됩니다. 이러한
|
||||
조건을 위반할 경우 일시적 또는 영구적으로 이용이 금지될 수
|
||||
있습니다.
|
||||
|
||||
### 3. 일시 금지
|
||||
|
||||
**커뮤니티 영향**: 지속적인 부적절한 행동을 포함하여
|
||||
커뮤니티 기준을 심각하게 위반한 경우입니다.
|
||||
|
||||
**결과**: 일정 기간 동안 커뮤니티와의 모든 상호작용이나 공개적인 소통이
|
||||
일시적으로 금지됩니다. 이 기간 동안에는 행동 강령을 시행하는
|
||||
사람들과의 원치 않는 상호작용을 포함하여 관련자들과의 공개적 또는
|
||||
사적인 상호작용이 허용되지 않습니다.
|
||||
이러한 조건을 위반할 경우 영구적으로 이용이 금지될 수 있습니다.
|
||||
|
||||
### 4. 영구 금지
|
||||
|
||||
**커뮤니티 영향**: 지속적인 부적절한 행동, 특정 개인에 대한 괴롭힘,
|
||||
특정 계층에 대한 공격성 또는 비하 등 공동체 기준을 위반하는
|
||||
행동을 보이는 경우입니다.
|
||||
|
||||
**결과**: 공동체 내 모든 종류의 공개적인 상호작용이 영구적으로
|
||||
금지됩니다.
|
||||
|
||||
## 귀속
|
||||
|
||||
본 행동 강령은 [Contributor Covenant][homepage] 버전 2.0을 바탕으로 작성되었으며
|
||||
[https://www.contributor-covenant.org/version/2/0/code_of_conduct.html][v2.0]에서
|
||||
확인하실 수 있습니다.
|
||||
|
||||
커뮤니티 영향 지침은
|
||||
[Mozilla's code of conduct enforcement ladder][Mozilla CoC]에서 영감을 받았습니다.
|
||||
|
||||
본 행동 강령에 대한 일반적인 질문은 [https://www.contributor-covenant.org/faq][FAQ]에서 FAQ를
|
||||
참조하세요. 번역은 [https://www.contributor-covenant.org/translations][translations]에서
|
||||
확인하실 수 있습니다.
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
[v2.0]: https://www.contributor-covenant.org/version/2/0/code_of_conduct.html
|
||||
[Mozilla CoC]: https://github.com/mozilla/diversity
|
||||
[FAQ]: https://www.contributor-covenant.org/faq
|
||||
[translations]: https://www.contributor-covenant.org/translations
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
[Discord](https://discord.gg/nDceKgxnkV) | [Twitter](https://twitter.com/rustdesk) | [Reddit](https://www.reddit.com/r/rustdesk) | [YouTube](https://www.youtube.com/@rustdesk) :تواصل معنا عبر
|
||||
|
||||
[](https://ko-fi.com/I2I04VU09)
|
||||
[](https://rustdesk.com/pricing.html)
|
||||
|
||||
.Rustبرنامج آخر لسطح المكتب عن بعد، مكتوب بـ
|
||||
يعمل خارج الصندوق، لا حاجة إلى إعدادات. لديك سيطرة كاملة على بياناتك، دون مخاوف بشأن الأمن. يمكنك استخدام خادم
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
Popovídejte si s námi: [Discord](https://discord.gg/nDceKgxnkV) | [Twitter](https://twitter.com/rustdesk) | [Reddit](https://www.reddit.com/r/rustdesk) | [YouTube](https://www.youtube.com/@rustdesk)
|
||||
|
||||
|
||||
[](https://ko-fi.com/I2I04VU09)
|
||||
[](https://rustdesk.com/pricing.html)
|
||||
|
||||
Zase další software pro přístup k ploše na dálku, naprogramovaný v jazyce Rust. Funguje hned tak, jak je – není třeba žádného nastavování. Svá data máte ve svých rukách, bez obav o zabezpečení. Je možné používat námi poskytovaný propojovací/předávací (relay) server, [vytvořit si svůj vlastní](https://rustdesk.com/server), nebo [si dokonce svůj vlastní naprogramovat](https://github.com/rustdesk/rustdesk-server-demo), budete-li chtít.
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
Chat med os: [Discord](https://discord.gg/nDceKgxnkV) | [Twitter](https://twitter.com/rustdesk) | [Reddit](https://www.reddit.com/r/rustdesk) | [YouTube](https://www.youtube.com/@rustdesk)
|
||||
|
||||
[](https://ko-fi.com/I2I04VU09)
|
||||
[](https://rustdesk.com/pricing.html)
|
||||
|
||||
Endnu en fjernskrivebordssoftware, skrevet i Rust. Fungerer ud af æsken, ingen konfiguration påkrævet. Du har fuld kontrol over dine data uden bekymringer om sikkerhed. Du kan bruge vores rendezvous/relay-server, [opsætte din egen](https://rustdesk.com/server), eller [skrive din egen rendezvous/relay-server](https://github.com/rustdesk/rustdesk- server-demo).
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
Reden Sie mit uns auf: [Discord](https://discord.gg/nDceKgxnkV) | [Twitter](https://twitter.com/rustdesk) | [Reddit](https://www.reddit.com/r/rustdesk) | [YouTube](https://www.youtube.com/@rustdesk)
|
||||
|
||||
[](https://ko-fi.com/I2I04VU09)
|
||||
[](https://rustdesk.com/pricing.html)
|
||||
|
||||
RustDesk ist eine in Rust geschriebene Remote-Desktop-Software, die out of the box ohne besondere Konfiguration funktioniert. Sie haben die volle Kontrolle über Ihre Daten und müssen sich keine Sorgen um die Sicherheit machen. Sie können unseren Rendezvous/Relay-Server nutzen, [einen eigenen Server aufsetzen](https://rustdesk.com/server) oder [einen eigenen Server programmieren](https://github.com/rustdesk/rustdesk-server-demo).
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
Babili kun ni: [Discord](https://discord.gg/nDceKgxnkV) | [Twitter](https://twitter.com/rustdesk) | [Reddit](https://www.reddit.com/r/rustdesk) | [YouTube](https://www.youtube.com/@rustdesk)
|
||||
|
||||
[](https://ko-fi.com/I2I04VU09)
|
||||
[](https://rustdesk.com/pricing.html)
|
||||
|
||||
Denove alia fora labortabla programo, skribita en Rust. Ĝi funkcias elskatole, ne bezonas konfiguraĵon. Vi havas la tutan kontrolon sur viaj datumoj, sen zorgo pri sekureco. Vi povas uzi nian servilon rendezvous/relajsan, [agordi vian propran](https://rustdesk.com/server), aŭ [skribi vian propran servilon rendezvous/relajsan](https://github.com/rustdesk/rustdesk-server-demo).
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
|
||||
Chatea con nosotros: [Discord](https://discord.gg/nDceKgxnkV) | [Twitter](https://twitter.com/rustdesk) | [Reddit](https://www.reddit.com/r/rustdesk) | [YouTube](https://www.youtube.com/@rustdesk)
|
||||
|
||||
[](https://ko-fi.com/I2I04VU09)
|
||||
[](https://rustdesk.com/pricing.html)
|
||||
|
||||
Otro software de escritorio remoto, escrito en Rust. Funciona de forma inmediata, sin necesidad de configuración. Tienes el control total de tus datos, sin preocupaciones sobre la seguridad. Puedes utilizar nuestro servidor de rendezvous/relay, [instalar el tuyo](https://rustdesk.com/server), o [escribir tu propio servidor rendezvous/relay](https://github.com/rustdesk/rustdesk-server-demo).
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
با ما گفتگو کنید: [Reddit](https://www.reddit.com/r/rustdesk) | [Twitter](https://twitter.com/rustdesk) | [Discord](https://discord.gg/nDceKgxnkV) | [YouTube](https://www.youtube.com/@rustdesk)
|
||||
|
||||
|
||||
[](https://ko-fi.com/I2I04VU09)
|
||||
[](https://rustdesk.com/pricing.html)
|
||||
|
||||
راستدسک (RustDesk) نرمافزاری برای کارکردن با رایانهی رومیزی از راه دور است و با زبان برنامهنویسی Rust نوشته شده است. نیاز به تنظیمات چندانی ندارد و شما را قادر می سازد تا بدون نگرانی از امنیت اطلاعات خود بر آنها کنترل کامل داشته باشید.
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
Juttele meidän kanssa: [Discord](https://discord.gg/nDceKgxnkV) | [Twitter](https://twitter.com/rustdesk) | [Reddit](https://www.reddit.com/r/rustdesk) | [YouTube](https://www.youtube.com/@rustdesk)
|
||||
|
||||
[](https://ko-fi.com/I2I04VU09)
|
||||
[](https://rustdesk.com/pricing.html)
|
||||
|
||||
Vielä yksi etätyöpöytäohjelmisto, ohjelmoitu Rust-kielellä. Toimii suoraan pakkauksesta, ei tarvitse asetusta. Hallitset täysin tietojasi, ei tarvitse murehtia turvallisuutta. Voit käyttää meidän rendezvous/relay-palvelinta, [aseta omasi](https://rustdesk.com/server), tai [kirjoittaa oma rendezvous/relay-palvelin](https://github.com/rustdesk/rustdesk-server-demo).
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
Chattez avec nous : [Discord](https://discord.gg/nDceKgxnkV) | [Twitter](https://twitter.com/rustdesk) | [Reddit](https://www.reddit.com/r/rustdesk) | [YouTube](https://www.youtube.com/@rustdesk)
|
||||
|
||||
[](https://ko-fi.com/I2I04VU09)
|
||||
[](https://rustdesk.com/pricing.html)
|
||||
|
||||
Encore un autre logiciel de bureau à distance, écrit en Rust. Fonctionne directement, aucune configuration n'est nécessaire. Vous avez le contrôle total de vos données, sans aucun souci de sécurité. Vous pouvez utiliser notre serveur de rendez-vous/relais, [configurer le vôtre](https://rustdesk.com/server), ou [écrire votre propre serveur de rendez-vous/relais](https://github.com/rustdesk/rustdesk-server-demo).
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
Επικοινωνήστε μαζί μας μέσω: [Discord](https://discord.gg/nDceKgxnkV) | [Twitter](https://twitter.com/rustdesk) | [Reddit](https://www.reddit.com/r/rustdesk) | [YouTube](https://www.youtube.com/@rustdesk)
|
||||
|
||||
[](https://ko-fi.com/I2I04VU09)
|
||||
[](https://rustdesk.com/pricing.html)
|
||||
|
||||
Ένα λογισμικό απομακρυσμένης επιφάνειας εργασίας, γραμμένο σε γλώσσα Rust. Δεν χρειάζεται κάποια παραμετροποίηση, λειτουργεί αμέσως μετά την εγκατάσταση. Έχετε τον πλήρη έλεγχο των δεδομένων σας, χωρίς να ανησυχείτε για την ασφάλειά τους. Μπορείτε να χρησιμοποιήσετε τους προκαθορισμένους διακομιστές rendezvous/αναμετάδοσης, [να εγκαταστήσετε τον δικό σας διακομιστή](https://rustdesk.com/server), ή [να αναπτύξετε ένα δικό σας διακομιστή rendezvous/αναμετάδοσης](https://github.com/rustdesk/rustdesk-server-demo).
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
Beszélgess velünk: [Discord](https://discord.gg/nDceKgxnkV) | [Twitter](https://twitter.com/rustdesk) | [Reddit](https://www.reddit.com/r/rustdesk) | [YouTube](https://www.youtube.com/@rustdesk)
|
||||
|
||||
[](https://ko-fi.com/I2I04VU09)
|
||||
[](https://rustdesk.com/pricing.html)
|
||||
|
||||
A RustDesk egy távoli elérésű asztali szoftver, Rust-ban írva. Működik mindenféle konfiguráció nélkül, feltelepítéssel, vagy anélkül. Az adataidat teljesen te kezeled, nincs szükség aggódásra a harmadik felek miatt. Használhatod a RustDesk punblikus randevú/relay szervereit, [hostolhatsz sajátot](https://rustdesk.com/server), vagy akár [írhatsz is egyet](https://github.com/rustdesk/rustdesk-server-demo).
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
Mari mengobrol bersama kami: [Discord](https://discord.gg/nDceKgxnkV) | [Twitter](https://twitter.com/rustdesk) | [Reddit](https://www.reddit.com/r/rustdesk) | [YouTube](https://www.youtube.com/@rustdesk)
|
||||
|
||||
[](https://ko-fi.com/I2I04VU09)
|
||||
[](https://rustdesk.com/pricing.html)
|
||||
|
||||
[](https://console.algora.io/org/rustdesk/bounties?status=open)
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
Chatta con noi su: [Discord](https://discord.gg/nDceKgxnkV) | [Twitter](https://twitter.com/rustdesk) | [Reddit](https://www.reddit.com/r/rustdesk) | [YouTube](https://www.youtube.com/@rustdesk)
|
||||
|
||||
[](https://ko-fi.com/I2I04VU09)
|
||||
[](https://rustdesk.com/pricing.html)
|
||||
|
||||
[](https://console.algora.io/org/rustdesk/bounties?status=open)
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
私たちと話す: [Discord](https://discord.gg/nDceKgxnkV) | [Twitter](https://twitter.com/rustdesk) | [Reddit](https://www.reddit.com/r/rustdesk) | [YouTube](https://www.youtube.com/@rustdesk)
|
||||
|
||||
[](https://ko-fi.com/I2I04VU09)
|
||||
[](https://rustdesk.com/pricing.html)
|
||||
|
||||
Rustで書かれた、設定不要ですぐに使えるリモートデスクトップソフトウェアです。自分のデータを完全にコントロールでき、セキュリティの心配もありません。私たちのランデブー/リレーサーバを使うことも、[自分でサーバーをセットアップする](https://rustdesk.com/server) ことも、 [自分でランデブー/リレーサーバを作成する](https://github.com/rustdesk/rustdesk-server-demo)こともできます。
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<p align="center">
|
||||
<img src="../res/logo-header.svg" alt="RustDesk - Your remote desktop"><br>
|
||||
<a href="#raw-steps-to-build">빌드</a> •
|
||||
<a href="#how-to-build-with-docker">Docker</a> •
|
||||
<a href="#file-structure">구조</a> •
|
||||
<a href="#snapshot">스냇샷</a><br>
|
||||
[<a href="../README.md">English</a>] | [<a href="README-UA.md">Українська</a>] | [<a href="README-CS.md">česky</a>] | [<a href="README-ZH.md">中文</a>] | [<a href="README-HU.md">Magyar</a>] | [<a href="README-FA.md">فارسی</a>] | [<a href="README-FR.md">Français</a>] | [<a href="README-DE.md">Deutsch</a>] | [<a href="README-PL.md">Polski</a>] | [<a href="README-ID.md">Indonesian</a>] | [<a href="README-FI.md">Suomi</a>] | [<a href="README-ML.md">മലയാളം</a>] | [<a href="README-JP.md">日本語</a>] | [<a href="README-NL.md">Nederlands</a>] | [<a href="README-IT.md">Italiano</a>] | [<a href="README-RU.md">Русский</a>] | [<a href="README-PTBR.md">Português (Brasil)</a>] | [<a href="README-EO.md">Esperanto</a>] | [<a href="README-KR.md">한국어</a>] | [<a href="README-AR.md">العربي</a>] | [<a href="README-VN.md">Tiếng Việt</a>] | [<a href="README-GR.md">Ελληνικά</a>]<br>
|
||||
<a href="#빌드를 위한 원시 단계">빌드</a> •
|
||||
<a href="#Docker로 빌드하는 방법">Docker</a> •
|
||||
<a href="#파일 구조">구조</a> •
|
||||
<a href="#스크린샷">스냇샷</a><br>
|
||||
[<a href="../README.md">English</a>] | [<a href="README-UA.md">Українська</a>] | [<a href="README-CS.md">česky</a>] | [<a href="README-ZH.md">中文</a>] | [<a href="README-HU.md">Magyar</a>] | [<a href="README-ES.md">Español</a>] | [<a href="README-FA.md">فارسی</a>] | [<a href="README-FR.md">Français</a>] | [<a href="README-DE.md">Deutsch</a>] | [<a href="README-PL.md">Polski</a>] | [<a href="README-ID.md">Indonesian</a>] | [<a href="README-FI.md">Suomi</a>] | [<a href="README-ML.md">മലയാളം</a>] | [<a href="README-JP.md">日本語</a>] | [<a href="README-NL.md">Nederlands</a>] | [<a href="README-IT.md">Italiano</a>] | [<a href="README-RU.md">Русский</a>] | [<a href="README-PTBR.md">Português (Brasil)</a>] | [<a href="README-EO.md">Esperanto</a>] | [<a href="README-KR.md">한국어</a>] | [<a href="README-AR.md">العربي</a>] | [<a href="README-VN.md">Tiếng Việt</a>] | [<a href="README-DA.md">Dansk</a>] | [<a href="README-GR.md">Ελληνικά</a>] | [<a href="README-TR.md">Türkçe</a>] | [<a href="README-NO.md">Norsk</a>]<br>
|
||||
<b>이 README, <a href="https://github.com/rustdesk/rustdesk/tree/master/src/lang">RustDesk UI</a> 및 <a href="https://github.com/rustdesk/doc.rustdesk.com">RustDesk 문서</a>를 귀하의 모국어로 번역하는 데 도움이 필요합니다</b>
|
||||
</p>
|
||||
|
||||
@@ -15,9 +15,9 @@
|
||||
|
||||
우리와 채팅: [Discord](https://discord.gg/nDceKgxnkV) | [Twitter](https://twitter.com/rustdesk) | [Reddit](https://www.reddit.com/r/rustdesk) | [YouTube](https://www.youtube.com/@rustdesk)
|
||||
|
||||
[](https://ko-fi.com/I2I04VU09)
|
||||
[](https://rustdesk.com/pricing.html)
|
||||
|
||||
Rust로 작성된 또 다른 원격 데스크톱 소프트웨어입니다. 구성할 필요 없이 바로 사용할 수 있습니다. 보안에 대한 걱정 없이 데이터를 완벽하게 제어할 수 있습니다. 저희의 rendezvous/relay server 서버를 사용하거나, [직접 설정](https://rustdesk.com/server), 또는 [직접 rendezvous/relay 서버를 작성할 수 있습니다](https://github.com/rustdesk/rustdesk-server-demo).
|
||||
또 하나의 원격 데스크톱 솔루션으로, Rust로 작성되었습니다. 별도의 설정 없이 바로 사용할 수 있습니다. 데이터에 대한 완전한 통제권을 가지며 보안에 대한 걱정이 없습니다. 저희 랑데부/릴레이 서버를 사용하거나, [직접 설정](https://rustdesk.com/server)하거나, [자신만의 랑데부/릴레이 서버를 작성](https://github.com/rustdesk/rustdesk-server-demo)할 수 있습니다.
|
||||
|
||||

|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
ഞങ്ങളുമായി ചാറ്റ് ചെയ്യുക: [Discord](https://discord.gg/nDceKgxnkV) | [Twitter](https://twitter.com/rustdesk) | [Reddit](https://www.reddit.com/r/rustdesk) | [YouTube](https://www.youtube.com/@rustdesk)
|
||||
|
||||
[](https://ko-fi.com/I2I04VU09)
|
||||
[](https://rustdesk.com/pricing.html)
|
||||
|
||||
റസ്റ്റിൽ എഴുതിയ മറ്റൊരു റിമോട്ട് ഡെസ്ക്ടോപ്പ് സോഫ്റ്റ്വെയർ. ബോക്സിന് പുറത്ത് പ്രവർത്തിക്കുന്നു, കോൺഫിഗറേഷൻ ആവശ്യമില്ല. സുരക്ഷയെക്കുറിച്ച് ആശങ്കകളൊന്നുമില്ലാതെ, നിങ്ങളുടെ ഡാറ്റയുടെ പൂർണ്ണ നിയന്ത്രണം നിങ്ങൾക്കുണ്ട്. നിങ്ങൾക്ക് ഞങ്ങളുടെ rendezvous/relay സെർവർ ഉപയോഗിക്കാം, [സ്വന്തമായി സജ്ജീകരിക്കുക](https://rustdesk.com/server), അല്ലെങ്കിൽ [നിങ്ങളുടെ സ്വന്തം rendezvous/relay സെർവർ എഴുതുക](https://github.com/rustdesk/rustdesk-server-demo).
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
Chat met ons: [Discord](https://discord.gg/nDceKgxnkV) | [Twitter](https://twitter.com/rustdesk) | [Reddit](https://www.reddit.com/r/rustdesk) | [YouTube](https://www.youtube.com/@rustdesk)
|
||||
|
||||
[](https://ko-fi.com/I2I04VU09)
|
||||
[](https://rustdesk.com/pricing.html)
|
||||
|
||||
Alweer een andere programma voor -bureaublad op afstand-, geschreven in Rust. Werkt -out of the box-, geen configuratie nodig. U heeft volledige controle over uw gegevens, en hoeft zich geen zorgen te maken over de beveiliging. U kunt onze rendez-vous/relay server gebruiken, [je eigen server opzetten](https://rustdesk.com/blog/id-relay-set), of [je eigen rendez-vous/relay-server schrijven](https://github.com/rustdesk/rustdesk-server-demo).
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
Snakk med oss: [Discord](https://discord.gg/nDceKgxnkV) | [Twitter](https://twitter.com/rustdesk) | [Reddit](https://www.reddit.com/r/rustdesk) | [YouTube](https://www.youtube.com/@rustdesk)
|
||||
|
||||
[](https://ko-fi.com/I2I04VU09)
|
||||
[](https://rustdesk.com/pricing.html)
|
||||
|
||||
Enda en annen fjernstyrt desktop programvare, skrevet i Rust. Virker rett ut av pakken, ingen konfigurasjon nødvendig. Du har full kontroll over din data, uten beskymring for sikkerhet. Du kan bruke vår rendezvous_mediator/relay server, [sett opp din egen](https://rustdesk.com/server), eller [skriv din egen rendezvous_mediator/relay server](https://github.com/rustdesk/rustdesk-server-demo).
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
Porozmawiaj z nami na: [Discord](https://discord.gg/nDceKgxnkV) | [Twitter](https://twitter.com/rustdesk) | [Reddit](https://www.reddit.com/r/rustdesk) | [YouTube](https://www.youtube.com/@rustdesk)
|
||||
|
||||
[](https://ko-fi.com/I2I04VU09)
|
||||
[](https://rustdesk.com/pricing.html)
|
||||
|
||||
Kolejny program do zdalnego pulpitu, napisany w Rust. Działa od samego początku, nie wymaga konfiguracji. Masz pełną kontrolę nad swoimi danymi, bez obaw o bezpieczeństwo. Możesz skorzystać z naszego darmowego serwera publicznego, [skonfigurować własny](https://rustdesk.com/server), lub [napisać własny serwer](https://github.com/rustdesk/rustdesk-server-demo).
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
Converse conosco: [Discord](https://discord.gg/nDceKgxnkV) | [Twitter](https://twitter.com/rustdesk) | [Reddit](https://www.reddit.com/r/rustdesk) | [YouTube](https://www.youtube.com/@rustdesk)
|
||||
|
||||
[](https://ko-fi.com/I2I04VU09)
|
||||
[](https://rustdesk.com/pricing.html)
|
||||
|
||||
Mais um software de desktop remoto, escrito em Rust. Funciona por padrão, sem necessidade de configuração. Você tem completo controle de seus dados, sem se preocupar com segurança. Você pode usar nossos servidores de rendezvous/relay, [configurar seu próprio](https://rustdesk.com/server), ou [escrever seu próprio servidor de rendezvous/relay](https://github.com/rustdesk/rustdesk-server-demo).
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
|
||||
Общение с нами: [Discord](https://discord.gg/nDceKgxnkV) | [Twitter](https://twitter.com/rustdesk) | [Reddit](https://www.reddit.com/r/rustdesk) | [YouTube](https://www.youtube.com/@rustdesk)
|
||||
|
||||
[](https://ko-fi.com/I2I04VU09)
|
||||
[](https://rustdesk.com/pricing.html)
|
||||
|
||||
Ещё одно программное обеспечение для удаленного рабочего стола, написанное на Rust. Работает из коробки, настройки не требует. Вы полностью контролируете свои данные, не беспокоясь о безопасности. Вы можете использовать наш сервер ретрансляции, [настроить свой собственный](https://rustdesk.com/server), или [написать свой](https://github.com/rustdesk/rustdesk-server-demo).
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
|
||||
Bizimle sohbet edin: [Discord](https://discord.gg/nDceKgxnkV) | [Twitter](https://twitter.com/rustdesk) | [Reddit](https://www.reddit.com/r/rustdesk) | [YouTube](https://www.youtube.com/@rustdesk)
|
||||
|
||||
[](https://ko-fi.com/I2I04VU09)
|
||||
[](https://rustdesk.com/pricing.html)
|
||||
|
||||
Başka bir uzak masaüstü yazılımı daha, Rust dilinde yazılmış. Hemen kullanıma hazır, hiçbir yapılandırma gerektirmez. Verilerinizin tam kontrolünü elinizde tutarsınız ve güvenlikle ilgili endişeleriniz olmaz. Kendi buluş/iletme sunucumuzu kullanabilirsiniz, [kendi sunucunuzu kurabilirsiniz](https://rustdesk.com/server) veya [kendi buluş/iletme sunucunuzu yazabilirsiniz](https://github.com/rustdesk/rustdesk-server-demo).
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
Спілкування з нами: [Discord](https://discord.gg/nDceKgxnkV) | [Twitter](https://twitter.com/rustdesk) | [Reddit](https://www.reddit.com/r/rustdesk) | [YouTube](https://www.youtube.com/@rustdesk)
|
||||
|
||||
[](https://ko-fi.com/I2I04VU09)
|
||||
[](https://rustdesk.com/pricing.html)
|
||||
|
||||
Ще один застосунок для віддаленого керування стільницею, написаний на Rust. Працює з коробки, не потребує налаштування. Ви повністю контролюєте свої дані, не турбуючись про безпеку. Ви можете використовувати наш сервер ретрансляції, [налаштувати свій власний](https://rustdesk.com/server), або [написати свій власний сервер ретрансляції](https://github.com/rustdesk/rustdesk-server-demo).
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
|
||||
Hãy trao đổi với chúng tôi qua: [Discord](https://discord.gg/nDceKgxnkV) | [Twitter](https://twitter.com/rustdesk) | [Reddit](https://www.reddit.com/r/rustdesk) | [YouTube](https://www.youtube.com/@rustdesk)
|
||||
|
||||
[](https://ko-fi.com/I2I04VU09)
|
||||
[](https://rustdesk.com/pricing.html)
|
||||
|
||||
RustDesk là một phần mềm điểu khiển máy tính từ xa mã nguồn mở, được viết bằng Rust. Nó hoạt động ngay sau khi cài đặt, không yêu cầu cấu hình phức tạp. Bạn có toàn quyền kiểm soát với dữ liệu của mình mà không cần phải lo lắng về vấn đề bảo mật. Bạn có thể sử dụng máy chủ rendezvous/relay của chúng tôi hoặc [tự cài đặt máy chủ của riêng mình](https://rustdesk.com/server) hay thậm chí [tự tạo máy chủ rendezvous/relay cho riêng bạn](https://github.com/rustdesk/rustdesk-server-demo).
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
|
||||
与我们交流: [知乎](https://www.zhihu.com/people/rustdesk) | [Discord](https://discord.gg/nDceKgxnkV) | [Reddit](https://www.reddit.com/r/rustdesk) | [YouTube](https://www.youtube.com/@rustdesk)
|
||||
|
||||
[](https://ko-fi.com/I2I04VU09)
|
||||
[](https://rustdesk.com/pricing.html)
|
||||
|
||||
远程桌面软件,开箱即用,无需任何配置。您完全掌控数据,不用担心安全问题。您可以使用我们的注册/中继服务器,
|
||||
或者[自己设置](https://rustdesk.com/server),
|
||||
|
||||
@@ -42,6 +42,7 @@ import 'package:flutter_hbb/native/win32.dart'
|
||||
if (dart.library.html) 'package:flutter_hbb/web/win32.dart';
|
||||
import 'package:flutter_hbb/native/common.dart'
|
||||
if (dart.library.html) 'package:flutter_hbb/web/common.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
|
||||
final globalKey = GlobalKey<NavigatorState>();
|
||||
final navigationBarKey = GlobalKey();
|
||||
@@ -2753,7 +2754,7 @@ class ServerConfig {
|
||||
} catch (err) {
|
||||
final input = msg.split('').reversed.join('');
|
||||
final bytes = base64Decode(base64.normalize(input));
|
||||
json = jsonDecode(utf8.decode(bytes));
|
||||
json = jsonDecode(utf8.decode(bytes, allowMalformed: true));
|
||||
}
|
||||
idServer = json['host'] ?? '';
|
||||
relayServer = json['relay'] ?? '';
|
||||
@@ -3910,3 +3911,35 @@ String get appName {
|
||||
}
|
||||
return _appName;
|
||||
}
|
||||
|
||||
String getConnectionText(bool secure, bool direct, String streamType) {
|
||||
String connectionText;
|
||||
if (secure && direct) {
|
||||
connectionText = translate("Direct and encrypted connection");
|
||||
} else if (secure && !direct) {
|
||||
connectionText = translate("Relayed and encrypted connection");
|
||||
} else if (!secure && direct) {
|
||||
connectionText = translate("Direct and unencrypted connection");
|
||||
} else {
|
||||
connectionText = translate("Relayed and unencrypted connection");
|
||||
}
|
||||
if (streamType == 'Relay') {
|
||||
streamType = 'TCP';
|
||||
}
|
||||
if (streamType.isEmpty) {
|
||||
return connectionText;
|
||||
} else {
|
||||
return '$connectionText ($streamType)';
|
||||
}
|
||||
}
|
||||
|
||||
String decode_http_response(http.Response resp) {
|
||||
try {
|
||||
// https://github.com/rustdesk/rustdesk-server-pro/discussions/758
|
||||
return utf8.decode(resp.bodyBytes, allowMalformed: true);
|
||||
} catch (e) {
|
||||
debugPrint('Failed to decode response as UTF-8: $e');
|
||||
// Fallback to bodyString which handles encoding automatically
|
||||
return resp.body;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -248,15 +248,17 @@ class AbProfile {
|
||||
String name;
|
||||
String owner;
|
||||
String? note;
|
||||
dynamic info;
|
||||
int rule;
|
||||
|
||||
AbProfile(this.guid, this.name, this.owner, this.note, this.rule);
|
||||
AbProfile(this.guid, this.name, this.owner, this.note, this.rule, this.info);
|
||||
|
||||
AbProfile.fromJson(Map<String, dynamic> json)
|
||||
: guid = json['guid'] ?? '',
|
||||
name = json['name'] ?? '',
|
||||
owner = json['owner'] ?? '',
|
||||
note = json['note'] ?? '',
|
||||
info = json['info'],
|
||||
rule = json['rule'] ?? 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -77,9 +77,11 @@ class CurrentDisplayState {
|
||||
class ConnectionType {
|
||||
final Rx<String> _secure = kInvalidValueStr.obs;
|
||||
final Rx<String> _direct = kInvalidValueStr.obs;
|
||||
final Rx<String> _stream_type = kInvalidValueStr.obs;
|
||||
|
||||
Rx<String> get secure => _secure;
|
||||
Rx<String> get direct => _direct;
|
||||
Rx<String> get stream_type => _stream_type;
|
||||
|
||||
static String get strSecure => 'secure';
|
||||
static String get strInsecure => 'insecure';
|
||||
@@ -94,9 +96,14 @@ class ConnectionType {
|
||||
_direct.value = v ? strDirect : strIndirect;
|
||||
}
|
||||
|
||||
void setStreamType(String v) {
|
||||
_stream_type.value = v;
|
||||
}
|
||||
|
||||
bool isValid() {
|
||||
return _secure.value != kInvalidValueStr &&
|
||||
_direct.value != kInvalidValueStr;
|
||||
_direct.value != kInvalidValueStr &&
|
||||
_stream_type.value != kInvalidValueStr;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1491,6 +1491,13 @@ void connectInPeerTab(BuildContext context, Peer peer, PeerTabIndex tab,
|
||||
password = peer.password;
|
||||
isSharedPassword = true;
|
||||
}
|
||||
if (password.isEmpty) {
|
||||
final abPassword = gFFI.abModel.getdefaultSharedPassword();
|
||||
if (abPassword != null) {
|
||||
password = abPassword;
|
||||
isSharedPassword = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
connect(context, peer.id,
|
||||
|
||||
@@ -172,6 +172,7 @@ const kHideUsernameOnCard = "hide-username-on-card";
|
||||
const String kOptionHideHelpCards = "hide-help-cards";
|
||||
|
||||
const String kOptionToggleViewOnly = "view-only";
|
||||
const String kOptionToggleShowMyCursor = "show-my-cursor";
|
||||
|
||||
const String kOptionDisableFloatingWindow = "disable-floating-window";
|
||||
|
||||
|
||||
@@ -434,7 +434,7 @@ class _DesktopHomePageState extends State<DesktopHomePage>
|
||||
!isCardClosed &&
|
||||
bind.mainUriPrefixSync().contains('rustdesk')) {
|
||||
final isToUpdate = (isWindows || isMacOS) && bind.mainIsInstalled();
|
||||
String btnText = isToUpdate ? 'Click to update' : 'Click to download';
|
||||
String btnText = isToUpdate ? 'Update' : 'Download';
|
||||
GestureTapCallback onPressed = () async {
|
||||
final Uri url = Uri.parse('https://rustdesk.com/download');
|
||||
await launchUrl(url);
|
||||
|
||||
@@ -146,16 +146,8 @@ class _ConnectionTabPageState extends State<ConnectionTabPage> {
|
||||
connectionType.secure.value == ConnectionType.strSecure;
|
||||
bool direct =
|
||||
connectionType.direct.value == ConnectionType.strDirect;
|
||||
String msgConn;
|
||||
if (secure && direct) {
|
||||
msgConn = translate("Direct and encrypted connection");
|
||||
} else if (secure && !direct) {
|
||||
msgConn = translate("Relayed and encrypted connection");
|
||||
} else if (!secure && direct) {
|
||||
msgConn = translate("Direct and unencrypted connection");
|
||||
} else {
|
||||
msgConn = translate("Relayed and unencrypted connection");
|
||||
}
|
||||
String msgConn = getConnectionText(
|
||||
secure, direct, connectionType.stream_type.value);
|
||||
var msgFingerprint = '${translate('Fingerprint')}:\n';
|
||||
var fingerprint = FingerprintState.find(key).value;
|
||||
if (fingerprint.isEmpty) {
|
||||
|
||||
@@ -145,16 +145,8 @@ class _ViewCameraTabPageState extends State<ViewCameraTabPage> {
|
||||
connectionType.secure.value == ConnectionType.strSecure;
|
||||
bool direct =
|
||||
connectionType.direct.value == ConnectionType.strDirect;
|
||||
String msgConn;
|
||||
if (secure && direct) {
|
||||
msgConn = translate("Direct and encrypted connection");
|
||||
} else if (secure && !direct) {
|
||||
msgConn = translate("Relayed and encrypted connection");
|
||||
} else if (!secure && direct) {
|
||||
msgConn = translate("Direct and unencrypted connection");
|
||||
} else {
|
||||
msgConn = translate("Relayed and unencrypted connection");
|
||||
}
|
||||
String msgConn = getConnectionText(
|
||||
secure, direct, connectionType.stream_type.value);
|
||||
var msgFingerprint = '${translate('Fingerprint')}:\n';
|
||||
var fingerprint = FingerprintState.find(key).value;
|
||||
if (fingerprint.isEmpty) {
|
||||
|
||||
@@ -1593,6 +1593,9 @@ class _KeyboardMenu extends StatelessWidget {
|
||||
inputSource(),
|
||||
Divider(),
|
||||
viewMode(),
|
||||
if ([kPeerPlatformWindows, kPeerPlatformMacOS, kPeerPlatformLinux]
|
||||
.contains(pi.platform))
|
||||
showMyCursor(),
|
||||
Divider(),
|
||||
...toolbarToggles(),
|
||||
...mouseSpeed(),
|
||||
@@ -1749,12 +1752,43 @@ class _KeyboardMenu extends StatelessWidget {
|
||||
final viewOnly = await bind.sessionGetToggleOption(
|
||||
sessionId: ffi.sessionId, arg: kOptionToggleViewOnly);
|
||||
ffiModel.setViewOnly(id, viewOnly ?? value);
|
||||
final showMyCursor = await bind.sessionGetToggleOption(
|
||||
sessionId: ffi.sessionId, arg: kOptionToggleShowMyCursor);
|
||||
ffiModel.setShowMyCursor(showMyCursor ?? value);
|
||||
}
|
||||
: null,
|
||||
ffi: ffi,
|
||||
child: Text(translate('View Mode')));
|
||||
}
|
||||
|
||||
showMyCursor() {
|
||||
final ffiModel = ffi.ffiModel;
|
||||
return CkbMenuButton(
|
||||
value: ffiModel.showMyCursor,
|
||||
onChanged: (value) async {
|
||||
if (value == null) return;
|
||||
await bind.sessionToggleOption(
|
||||
sessionId: ffi.sessionId, value: kOptionToggleShowMyCursor);
|
||||
final showMyCursor = await bind.sessionGetToggleOption(
|
||||
sessionId: ffi.sessionId,
|
||||
arg: kOptionToggleShowMyCursor) ??
|
||||
value;
|
||||
ffiModel.setShowMyCursor(showMyCursor);
|
||||
|
||||
// Also set view only if showMyCursor is enabled and viewOnly is not enabled.
|
||||
if (showMyCursor && !ffiModel.viewOnly) {
|
||||
await bind.sessionToggleOption(
|
||||
sessionId: ffi.sessionId, value: kOptionToggleViewOnly);
|
||||
final viewOnly = await bind.sessionGetToggleOption(
|
||||
sessionId: ffi.sessionId, arg: kOptionToggleViewOnly);
|
||||
ffiModel.setViewOnly(id, viewOnly ?? value);
|
||||
}
|
||||
},
|
||||
ffi: ffi,
|
||||
child: Text(translate('Show my cursor')))
|
||||
.paddingOnly(left: 26.0);
|
||||
}
|
||||
|
||||
mobileActions() {
|
||||
if (pi.platform != kPeerPlatformAndroid) return [];
|
||||
final enabled = versionCmp(pi.version, '1.2.7') >= 0;
|
||||
|
||||
@@ -7,7 +7,10 @@ import 'package:flutter_hbb/models/platform_model.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
final _isExtracting = false.obs;
|
||||
|
||||
void handleUpdate(String releasePageUrl) {
|
||||
_isExtracting.value = false;
|
||||
String downloadUrl = releasePageUrl.replaceAll('tag', 'download');
|
||||
String version = downloadUrl.substring(downloadUrl.lastIndexOf('/') + 1);
|
||||
final String downloadFile =
|
||||
@@ -25,13 +28,15 @@ void handleUpdate(String releasePageUrl) {
|
||||
gFFI.dialogManager.dismissAll();
|
||||
gFFI.dialogManager.show((setState, close, context) {
|
||||
return CustomAlertDialog(
|
||||
title: Text(translate('Downloading {$appName}')),
|
||||
title: Obx(() => Text(translate(_isExtracting.isTrue
|
||||
? 'Preparing for installation ...'
|
||||
: 'Downloading {$appName}'))),
|
||||
content:
|
||||
UpdateProgress(releasePageUrl, downloadUrl, downloadId, onCanceled)
|
||||
.marginSymmetric(horizontal: 8)
|
||||
.paddingOnly(top: 12),
|
||||
actions: [
|
||||
dialogButton(translate('Cancel'), onPressed: () async {
|
||||
if (_isExtracting.isFalse) dialogButton(translate('Cancel'), onPressed: () async {
|
||||
onCanceled.value();
|
||||
await bind.mainSetCommon(
|
||||
key: 'cancel-downloader', value: downloadId.value);
|
||||
@@ -71,6 +76,7 @@ class UpdateProgressState extends State<UpdateProgress> {
|
||||
int _downloadedSize = 0;
|
||||
int _getDataFailedCount = 0;
|
||||
final String _eventKeyDownloadNewVersion = 'download-new-version';
|
||||
final String _eventKeyExtractUpdateDmg = 'extract-update-dmg';
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -82,6 +88,11 @@ class UpdateProgressState extends State<UpdateProgress> {
|
||||
_eventKeyDownloadNewVersion, handleDownloadNewVersion,
|
||||
replace: true);
|
||||
bind.mainSetCommon(key: 'download-new-version', value: widget.downloadUrl);
|
||||
if (isMacOS) {
|
||||
platformFFI.registerEventHandler(_eventKeyExtractUpdateDmg,
|
||||
_eventKeyExtractUpdateDmg, handleExtractUpdateDmg,
|
||||
replace: true);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -89,6 +100,10 @@ class UpdateProgressState extends State<UpdateProgress> {
|
||||
cancelQueryTimer();
|
||||
platformFFI.unregisterEventHandler(
|
||||
_eventKeyDownloadNewVersion, _eventKeyDownloadNewVersion);
|
||||
if (isMacOS) {
|
||||
platformFFI.unregisterEventHandler(
|
||||
_eventKeyExtractUpdateDmg, _eventKeyExtractUpdateDmg);
|
||||
}
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@@ -113,10 +128,13 @@ class UpdateProgressState extends State<UpdateProgress> {
|
||||
}
|
||||
}
|
||||
|
||||
void _onError(String error) {
|
||||
// `isExtractDmg` is true when handling extract-update-dmg event.
|
||||
// It's a rare case that the dmg file is corrupted and cannot be extracted.
|
||||
void _onError(String error, {bool isExtractDmg = false}) {
|
||||
cancelQueryTimer();
|
||||
|
||||
debugPrint('Download new version error: $error');
|
||||
debugPrint(
|
||||
'${isExtractDmg ? "Extract" : "Download"} new version error: $error');
|
||||
final msgBoxType = 'custom-nocancel-nook-hasclose';
|
||||
final msgBoxTitle = 'Error';
|
||||
final msgBoxText = 'download-new-version-failed-tip';
|
||||
@@ -138,7 +156,7 @@ class UpdateProgressState extends State<UpdateProgress> {
|
||||
|
||||
final List<Widget> buttons = [
|
||||
dialogButton('Download', onPressed: jumplink),
|
||||
dialogButton('Retry', onPressed: retry),
|
||||
if (!isExtractDmg) dialogButton('Retry', onPressed: retry),
|
||||
dialogButton('Close', onPressed: close),
|
||||
];
|
||||
dialogManager.dismissAll();
|
||||
@@ -194,19 +212,13 @@ class UpdateProgressState extends State<UpdateProgress> {
|
||||
_onError('The download file size is 0.');
|
||||
} else {
|
||||
setState(() {});
|
||||
msgBox(
|
||||
gFFI.sessionId,
|
||||
'custom-nocancel',
|
||||
'{$appName} Update',
|
||||
'{$appName}-to-update-tip',
|
||||
'',
|
||||
gFFI.dialogManager,
|
||||
onSubmit: () {
|
||||
debugPrint('Downloaded, update to new version now');
|
||||
bind.mainSetCommon(key: 'update-me', value: widget.downloadUrl);
|
||||
},
|
||||
submitTimeout: 5,
|
||||
);
|
||||
if (isMacOS) {
|
||||
bind.mainSetCommon(
|
||||
key: 'extract-update-dmg', value: widget.downloadUrl);
|
||||
_isExtracting.value = true;
|
||||
} else {
|
||||
updateMsgBox();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
setState(() {});
|
||||
@@ -214,17 +226,38 @@ class UpdateProgressState extends State<UpdateProgress> {
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return onDownloading(context);
|
||||
void updateMsgBox() {
|
||||
msgBox(
|
||||
gFFI.sessionId,
|
||||
'custom-nocancel',
|
||||
'{$appName} Update',
|
||||
'{$appName}-to-update-tip',
|
||||
'',
|
||||
gFFI.dialogManager,
|
||||
onSubmit: () {
|
||||
debugPrint('Downloaded, update to new version now');
|
||||
bind.mainSetCommon(key: 'update-me', value: widget.downloadUrl);
|
||||
},
|
||||
submitTimeout: 5,
|
||||
);
|
||||
}
|
||||
|
||||
Widget onDownloading(BuildContext context) {
|
||||
final value = _totalSize == null
|
||||
Future<void> handleExtractUpdateDmg(Map<String, dynamic> evt) async {
|
||||
_isExtracting.value = false;
|
||||
if (evt.containsKey('err') && (evt['err'] as String).isNotEmpty) {
|
||||
_onError(evt['err'] as String, isExtractDmg: true);
|
||||
} else {
|
||||
updateMsgBox();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
getValue() => _totalSize == null
|
||||
? 0.0
|
||||
: (_totalSize == 0 ? 1.0 : _downloadedSize / _totalSize!);
|
||||
return LinearProgressIndicator(
|
||||
value: value,
|
||||
value: _isExtracting.isTrue ? null : getValue(),
|
||||
minHeight: 20,
|
||||
borderRadius: BorderRadius.circular(5),
|
||||
backgroundColor: Colors.grey[300],
|
||||
|
||||
@@ -147,9 +147,15 @@ void runMainApp(bool startService) async {
|
||||
gFFI.userModel.refreshCurrentUser();
|
||||
runApp(App());
|
||||
|
||||
bool? alwaysOnTop;
|
||||
if (isDesktop) {
|
||||
alwaysOnTop =
|
||||
bind.mainGetBuildinOption(key: "main-window-always-on-top") == 'Y';
|
||||
}
|
||||
|
||||
// Set window option.
|
||||
WindowOptions windowOptions =
|
||||
getHiddenTitleBarWindowOptions(isMainWindow: true);
|
||||
WindowOptions windowOptions = getHiddenTitleBarWindowOptions(
|
||||
isMainWindow: true, alwaysOnTop: alwaysOnTop);
|
||||
windowManager.waitUntilReadyToShow(windowOptions, () async {
|
||||
// Restore the location of the main window before window hide or show.
|
||||
await restoreWindowPosition(WindowType.Main);
|
||||
|
||||
@@ -40,7 +40,12 @@ void _disableAndroidSoftKeyboard({bool? isKeyboardVisible}) {
|
||||
}
|
||||
|
||||
class RemotePage extends StatefulWidget {
|
||||
RemotePage({Key? key, required this.id, this.password, this.isSharedPassword, this.forceRelay})
|
||||
RemotePage(
|
||||
{Key? key,
|
||||
required this.id,
|
||||
this.password,
|
||||
this.isSharedPassword,
|
||||
this.forceRelay})
|
||||
: super(key: key);
|
||||
|
||||
final String id;
|
||||
@@ -1105,7 +1110,7 @@ void showOptions(
|
||||
BuildContext context, String id, OverlayDialogManager dialogManager) async {
|
||||
var displays = <Widget>[];
|
||||
final pi = gFFI.ffiModel.pi;
|
||||
final image = gFFI.ffiModel.getConnectionImage();
|
||||
final image = gFFI.ffiModel.getConnectionImageText();
|
||||
if (image != null) {
|
||||
displays.add(Padding(padding: const EdgeInsets.only(top: 8), child: image));
|
||||
}
|
||||
|
||||
@@ -39,7 +39,11 @@ void _disableAndroidSoftKeyboard({bool? isKeyboardVisible}) {
|
||||
|
||||
class ViewCameraPage extends StatefulWidget {
|
||||
ViewCameraPage(
|
||||
{Key? key, required this.id, this.password, this.isSharedPassword, this.forceRelay})
|
||||
{Key? key,
|
||||
required this.id,
|
||||
this.password,
|
||||
this.isSharedPassword,
|
||||
this.forceRelay})
|
||||
: super(key: key);
|
||||
|
||||
final String id;
|
||||
@@ -579,7 +583,7 @@ void showOptions(
|
||||
BuildContext context, String id, OverlayDialogManager dialogManager) async {
|
||||
var displays = <Widget>[];
|
||||
final pi = gFFI.ffiModel.pi;
|
||||
final image = gFFI.ffiModel.getConnectionImage();
|
||||
final image = gFFI.ffiModel.getConnectionImageText();
|
||||
if (image != null) {
|
||||
displays.add(Padding(padding: const EdgeInsets.only(top: 8), child: image));
|
||||
}
|
||||
|
||||
@@ -140,7 +140,7 @@ class AbModel {
|
||||
debugPrint("pull ab list");
|
||||
List<AbProfile> abProfiles = List.empty(growable: true);
|
||||
abProfiles.add(AbProfile(_personalAbGuid!, _personalAddressBookName,
|
||||
gFFI.userModel.userName.value, null, ShareRule.read.value));
|
||||
gFFI.userModel.userName.value, null, ShareRule.read.value, null));
|
||||
// get all address book name
|
||||
await _getSharedAbProfiles(abProfiles);
|
||||
addressbooks.removeWhere((key, value) =>
|
||||
@@ -208,7 +208,7 @@ class AbModel {
|
||||
return false;
|
||||
}
|
||||
Map<String, dynamic> json =
|
||||
_jsonDecodeRespMap(utf8.decode(resp.bodyBytes), resp.statusCode);
|
||||
_jsonDecodeRespMap(decode_http_response(resp), resp.statusCode);
|
||||
if (json.containsKey('error')) {
|
||||
throw json['error'];
|
||||
}
|
||||
@@ -234,7 +234,7 @@ class AbModel {
|
||||
return false;
|
||||
}
|
||||
Map<String, dynamic> json =
|
||||
_jsonDecodeRespMap(utf8.decode(resp.bodyBytes), resp.statusCode);
|
||||
_jsonDecodeRespMap(decode_http_response(resp), resp.statusCode);
|
||||
if (json.containsKey('error')) {
|
||||
throw json['error'];
|
||||
}
|
||||
@@ -271,7 +271,7 @@ class AbModel {
|
||||
headers['Content-Type'] = "application/json";
|
||||
final resp = await http.post(uri, headers: headers);
|
||||
Map<String, dynamic> json =
|
||||
_jsonDecodeRespMap(utf8.decode(resp.bodyBytes), resp.statusCode);
|
||||
_jsonDecodeRespMap(decode_http_response(resp), resp.statusCode);
|
||||
if (json.containsKey('error')) {
|
||||
throw json['error'];
|
||||
}
|
||||
@@ -609,7 +609,7 @@ class AbModel {
|
||||
if (name == null || guid == null) {
|
||||
continue;
|
||||
}
|
||||
ab = Ab(AbProfile(guid, name, '', '', ShareRule.read.value),
|
||||
ab = Ab(AbProfile(guid, name, '', '', ShareRule.read.value, null),
|
||||
name == _personalAddressBookName);
|
||||
}
|
||||
addressbooks[name] = ab;
|
||||
@@ -767,6 +767,28 @@ class AbModel {
|
||||
_peerIdUpdateListeners.remove(key);
|
||||
}
|
||||
|
||||
String? getdefaultSharedPassword() {
|
||||
if (current.isPersonal()) {
|
||||
return null;
|
||||
}
|
||||
final profile = current.sharedProfile();
|
||||
if (profile == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
if (profile.info is Map) {
|
||||
final password = (profile.info as Map)['password'];
|
||||
if (password is String && password.isNotEmpty) {
|
||||
return password;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
} catch (e) {
|
||||
debugPrint("getdefaultSharedPassword: $e");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// #endregion
|
||||
}
|
||||
|
||||
@@ -925,7 +947,7 @@ class LegacyAb extends BaseAb {
|
||||
peers.clear();
|
||||
} else if (resp.body.isNotEmpty) {
|
||||
Map<String, dynamic> json =
|
||||
_jsonDecodeRespMap(utf8.decode(resp.bodyBytes), resp.statusCode);
|
||||
_jsonDecodeRespMap(decode_http_response(resp), resp.statusCode);
|
||||
if (json.containsKey('error')) {
|
||||
throw json['error'];
|
||||
} else if (json.containsKey('data')) {
|
||||
@@ -983,7 +1005,7 @@ class LegacyAb extends BaseAb {
|
||||
ret = true;
|
||||
} else {
|
||||
Map<String, dynamic> json =
|
||||
_jsonDecodeRespMap(utf8.decode(resp.bodyBytes), resp.statusCode);
|
||||
_jsonDecodeRespMap(decode_http_response(resp), resp.statusCode);
|
||||
if (json.containsKey('error')) {
|
||||
throw json['error'];
|
||||
} else if (resp.statusCode == 200) {
|
||||
@@ -1359,7 +1381,7 @@ class Ab extends BaseAb {
|
||||
final resp = await http.post(uri, headers: headers);
|
||||
statusCode = resp.statusCode;
|
||||
Map<String, dynamic> json =
|
||||
_jsonDecodeRespMap(utf8.decode(resp.bodyBytes), resp.statusCode);
|
||||
_jsonDecodeRespMap(decode_http_response(resp), resp.statusCode);
|
||||
if (json.containsKey('error')) {
|
||||
throw json['error'];
|
||||
}
|
||||
@@ -1416,7 +1438,7 @@ class Ab extends BaseAb {
|
||||
final resp = await http.post(uri, headers: headers);
|
||||
statusCode = resp.statusCode;
|
||||
List<dynamic> json =
|
||||
_jsonDecodeRespList(utf8.decode(resp.bodyBytes), resp.statusCode);
|
||||
_jsonDecodeRespList(decode_http_response(resp), resp.statusCode);
|
||||
if (resp.statusCode != 200) {
|
||||
throw 'HTTP ${resp.statusCode}';
|
||||
}
|
||||
|
||||
@@ -30,15 +30,17 @@ enum SortBy {
|
||||
class JobID {
|
||||
int _count = 0;
|
||||
int next() {
|
||||
String v = bind.mainGetCommonSync(key: 'transfer-job-id');
|
||||
try {
|
||||
return int.parse(v);
|
||||
if (!isWeb) {
|
||||
String v = bind.mainGetCommonSync(key: 'transfer-job-id');
|
||||
return int.parse(v);
|
||||
}
|
||||
} catch (e) {
|
||||
// unreachable. But we still handle it to make it safe.
|
||||
// If we return -1, we have to check it in the caller.
|
||||
_count++;
|
||||
return _count;
|
||||
debugPrint("Failed to get transfer job id: $e");
|
||||
}
|
||||
// Finally increase the count if on the web or if failed to get the id.
|
||||
_count++;
|
||||
return _count;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -122,7 +122,7 @@ class GroupModel {
|
||||
final resp = await http.get(uri, headers: getHttpHeaders());
|
||||
_statusCode = resp.statusCode;
|
||||
Map<String, dynamic> json =
|
||||
_jsonDecodeResp(utf8.decode(resp.bodyBytes), resp.statusCode);
|
||||
_jsonDecodeResp(decode_http_response(resp), resp.statusCode);
|
||||
if (json.containsKey('error')) {
|
||||
throw json['error'];
|
||||
}
|
||||
@@ -180,7 +180,7 @@ class GroupModel {
|
||||
final resp = await http.get(uri, headers: getHttpHeaders());
|
||||
_statusCode = resp.statusCode;
|
||||
Map<String, dynamic> json =
|
||||
_jsonDecodeResp(utf8.decode(resp.bodyBytes), resp.statusCode);
|
||||
_jsonDecodeResp(decode_http_response(resp), resp.statusCode);
|
||||
if (json.containsKey('error')) {
|
||||
if (json['error'] == 'Admin required!' ||
|
||||
json['error']
|
||||
@@ -246,7 +246,7 @@ class GroupModel {
|
||||
_statusCode = resp.statusCode;
|
||||
|
||||
Map<String, dynamic> json =
|
||||
_jsonDecodeResp(utf8.decode(resp.bodyBytes), resp.statusCode);
|
||||
_jsonDecodeResp(decode_http_response(resp), resp.statusCode);
|
||||
if (json.containsKey('error')) {
|
||||
throw json['error'];
|
||||
}
|
||||
|
||||
@@ -371,6 +371,7 @@ class InputModel {
|
||||
String get id => parent.target?.id ?? '';
|
||||
String? get peerPlatform => parent.target?.ffiModel.pi.platform;
|
||||
bool get isViewOnly => parent.target!.ffiModel.viewOnly;
|
||||
bool get showMyCursor => parent.target!.ffiModel.showMyCursor;
|
||||
double get devicePixelRatio => parent.target!.canvasModel.devicePixelRatio;
|
||||
bool get isViewCamera => parent.target!.connType == ConnType.viewCamera;
|
||||
int get trackpadSpeed => _trackpadSpeed;
|
||||
@@ -876,7 +877,7 @@ class InputModel {
|
||||
|
||||
void onPointHoverImage(PointerHoverEvent e) {
|
||||
_stopFling = true;
|
||||
if (isViewOnly) return;
|
||||
if (isViewOnly && !showMyCursor) return;
|
||||
if (e.kind != ui.PointerDeviceKind.mouse) return;
|
||||
if (!isPhysicalMouse.value) {
|
||||
isPhysicalMouse.value = true;
|
||||
@@ -1037,7 +1038,7 @@ class InputModel {
|
||||
if (isDesktop) _queryOtherWindowCoords = true;
|
||||
_remoteWindowCoords = [];
|
||||
_windowRect = null;
|
||||
if (isViewOnly) return;
|
||||
if (isViewOnly && !showMyCursor) return;
|
||||
if (isViewCamera) return;
|
||||
if (e.kind != ui.PointerDeviceKind.mouse) {
|
||||
if (isPhysicalMouse.value) {
|
||||
@@ -1051,7 +1052,7 @@ class InputModel {
|
||||
|
||||
void onPointUpImage(PointerUpEvent e) {
|
||||
if (isDesktop) _queryOtherWindowCoords = false;
|
||||
if (isViewOnly) return;
|
||||
if (isViewOnly && !showMyCursor) return;
|
||||
if (isViewCamera) return;
|
||||
if (e.kind != ui.PointerDeviceKind.mouse) return;
|
||||
if (isPhysicalMouse.value) {
|
||||
@@ -1060,7 +1061,7 @@ class InputModel {
|
||||
}
|
||||
|
||||
void onPointMoveImage(PointerMoveEvent e) {
|
||||
if (isViewOnly) return;
|
||||
if (isViewOnly && !showMyCursor) return;
|
||||
if (isViewCamera) return;
|
||||
if (e.kind != ui.PointerDeviceKind.mouse) return;
|
||||
if (_queryOtherWindowCoords) {
|
||||
@@ -1312,8 +1313,12 @@ class InputModel {
|
||||
isMove = false;
|
||||
canvas = coords.canvas;
|
||||
rect = coords.remoteRect;
|
||||
x -= coords.relativeOffset.dx / devicePixelRatio;
|
||||
y -= coords.relativeOffset.dy / devicePixelRatio;
|
||||
x -= isWindows
|
||||
? coords.relativeOffset.dx / devicePixelRatio
|
||||
: coords.relativeOffset.dx;
|
||||
y -= isWindows
|
||||
? coords.relativeOffset.dy / devicePixelRatio
|
||||
: coords.relativeOffset.dy;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1338,15 +1343,21 @@ class InputModel {
|
||||
}
|
||||
|
||||
bool _isInCurrentWindow(double x, double y) {
|
||||
final w = _windowRect!.width / devicePixelRatio;
|
||||
final h = _windowRect!.width / devicePixelRatio;
|
||||
var w = _windowRect!.width;
|
||||
var h = _windowRect!.height;
|
||||
if (isWindows) {
|
||||
w /= devicePixelRatio;
|
||||
h /= devicePixelRatio;
|
||||
}
|
||||
return x >= 0 && y >= 0 && x <= w && y <= h;
|
||||
}
|
||||
|
||||
static RemoteWindowCoords? findRemoteCoords(double x, double y,
|
||||
List<RemoteWindowCoords> remoteWindowCoords, double devicePixelRatio) {
|
||||
x *= devicePixelRatio;
|
||||
y *= devicePixelRatio;
|
||||
if (isWindows) {
|
||||
x *= devicePixelRatio;
|
||||
y *= devicePixelRatio;
|
||||
}
|
||||
for (final c in remoteWindowCoords) {
|
||||
if (x >= c.relativeOffset.dx &&
|
||||
y >= c.relativeOffset.dy &&
|
||||
|
||||
@@ -61,6 +61,7 @@ class CachedPeerData {
|
||||
|
||||
bool secure = false;
|
||||
bool direct = false;
|
||||
String streamType = '';
|
||||
|
||||
CachedPeerData();
|
||||
|
||||
@@ -74,6 +75,7 @@ class CachedPeerData {
|
||||
'permissions': permissions,
|
||||
'secure': secure,
|
||||
'direct': direct,
|
||||
'streamType': streamType,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -92,6 +94,7 @@ class CachedPeerData {
|
||||
});
|
||||
data.secure = map['secure'];
|
||||
data.direct = map['direct'];
|
||||
data.streamType = map['streamType'];
|
||||
return data;
|
||||
} catch (e) {
|
||||
debugPrint('Failed to parse CachedPeerData: $e');
|
||||
@@ -113,6 +116,7 @@ class FfiModel with ChangeNotifier {
|
||||
Timer? _timer;
|
||||
var _reconnects = 1;
|
||||
bool _viewOnly = false;
|
||||
bool _showMyCursor = false;
|
||||
WeakReference<FFI> parent;
|
||||
late final SessionID sessionId;
|
||||
|
||||
@@ -151,6 +155,7 @@ class FfiModel with ChangeNotifier {
|
||||
bool get isPeerMobile => isPeerAndroid;
|
||||
|
||||
bool get viewOnly => _viewOnly;
|
||||
bool get showMyCursor => _showMyCursor;
|
||||
|
||||
set inputBlocked(v) {
|
||||
_inputBlocked = v;
|
||||
@@ -223,27 +228,45 @@ class FfiModel with ChangeNotifier {
|
||||
timerScreenshot?.cancel();
|
||||
}
|
||||
|
||||
setConnectionType(String peerId, bool secure, bool direct) {
|
||||
setConnectionType(
|
||||
String peerId, bool secure, bool direct, String streamType) {
|
||||
cachedPeerData.secure = secure;
|
||||
cachedPeerData.direct = direct;
|
||||
cachedPeerData.streamType = streamType;
|
||||
_secure = secure;
|
||||
_direct = direct;
|
||||
try {
|
||||
var connectionType = ConnectionTypeState.find(peerId);
|
||||
connectionType.setSecure(secure);
|
||||
connectionType.setDirect(direct);
|
||||
connectionType.setStreamType(streamType);
|
||||
} catch (e) {
|
||||
//
|
||||
}
|
||||
}
|
||||
|
||||
Widget? getConnectionImage() {
|
||||
Widget? getConnectionImageText() {
|
||||
if (secure == null || direct == null) {
|
||||
return null;
|
||||
} else {
|
||||
final icon =
|
||||
'${secure == true ? 'secure' : 'insecure'}${direct == true ? '' : '_relay'}';
|
||||
return SvgPicture.asset('assets/$icon.svg', width: 48, height: 48);
|
||||
final iconWidget =
|
||||
SvgPicture.asset('assets/$icon.svg', width: 48, height: 48);
|
||||
String connectionText =
|
||||
getConnectionText(secure!, direct!, cachedPeerData.streamType);
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
iconWidget,
|
||||
SizedBox(height: 4),
|
||||
Text(
|
||||
connectionText,
|
||||
style: TextStyle(fontSize: 12),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -260,7 +283,7 @@ class FfiModel with ChangeNotifier {
|
||||
'link': '',
|
||||
}, sessionId, peerId);
|
||||
updatePrivacyMode(data.updatePrivacyMode, sessionId, peerId);
|
||||
setConnectionType(peerId, data.secure, data.direct);
|
||||
setConnectionType(peerId, data.secure, data.direct, data.streamType);
|
||||
await handlePeerInfo(data.peerInfo, peerId, true);
|
||||
for (final element in data.cursorDataList) {
|
||||
updateLastCursorId(element);
|
||||
@@ -289,8 +312,8 @@ class FfiModel with ChangeNotifier {
|
||||
} else if (name == 'sync_platform_additions') {
|
||||
handlePlatformAdditions(evt, sessionId, peerId);
|
||||
} else if (name == 'connection_ready') {
|
||||
setConnectionType(
|
||||
peerId, evt['secure'] == 'true', evt['direct'] == 'true');
|
||||
setConnectionType(peerId, evt['secure'] == 'true',
|
||||
evt['direct'] == 'true', evt['stream_type'] ?? '');
|
||||
} else if (name == 'switch_display') {
|
||||
// switch display is kept for backward compatibility
|
||||
handleSwitchDisplay(evt, sessionId, peerId);
|
||||
@@ -1123,6 +1146,8 @@ class FfiModel with ChangeNotifier {
|
||||
peerId,
|
||||
bind.sessionGetToggleOptionSync(
|
||||
sessionId: sessionId, arg: kOptionToggleViewOnly));
|
||||
setShowMyCursor(bind.sessionGetToggleOptionSync(
|
||||
sessionId: sessionId, arg: kOptionToggleShowMyCursor));
|
||||
}
|
||||
if (connType == ConnType.defaultConn || connType == ConnType.viewCamera) {
|
||||
final platformAdditions = evt['platform_additions'];
|
||||
@@ -1473,6 +1498,13 @@ class FfiModel with ChangeNotifier {
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
void setShowMyCursor(bool value) {
|
||||
if (_showMyCursor != value) {
|
||||
_showMyCursor = value;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ImageModel with ChangeNotifier {
|
||||
|
||||
@@ -156,7 +156,10 @@ class PlatformFFI {
|
||||
// only support for android
|
||||
_homeDir = (await ExternalPath.getExternalStorageDirectories())[0];
|
||||
} else if (isIOS) {
|
||||
_homeDir = _ffiBind.mainGetDataDirIos();
|
||||
// The previous code was `_homeDir = (await getDownloadsDirectory())?.path ?? '';`,
|
||||
// which provided the `downloads` path in the sandbox.
|
||||
// It is unclear why we now use the `data` directory in the sandbox instead.
|
||||
_homeDir = _ffiBind.mainGetDataDirIos(appDir: _dir);
|
||||
} else {
|
||||
// no need to set home dir
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ class PeerTabModel with ChangeNotifier {
|
||||
List<bool> isEnabled = List.from([
|
||||
true,
|
||||
true,
|
||||
!isWeb,
|
||||
!isWeb && bind.mainGetLocalOption(key: "disable-discovery-panel") != "Y",
|
||||
!(bind.isDisableAb() || bind.isDisableAccount()),
|
||||
!(bind.isDisableGroupPanel() || bind.isDisableAccount()),
|
||||
]);
|
||||
|
||||
@@ -304,14 +304,14 @@ class TerminalModel with ChangeNotifier {
|
||||
// Try to decode as base64 first
|
||||
try {
|
||||
final bytes = base64Decode(data);
|
||||
text = utf8.decode(bytes);
|
||||
text = utf8.decode(bytes, allowMalformed: true);
|
||||
} catch (e) {
|
||||
// If base64 decode fails, treat as plain text
|
||||
text = data;
|
||||
}
|
||||
} else if (data is List) {
|
||||
// Handle if data comes as byte array
|
||||
text = utf8.decode(List<int>.from(data));
|
||||
text = utf8.decode(List<int>.from(data), allowMalformed: true);
|
||||
} else {
|
||||
debugPrint('[TerminalModel] Unknown data type: ${data.runtimeType}');
|
||||
return;
|
||||
|
||||
@@ -66,7 +66,7 @@ class UserModel {
|
||||
reset(resetOther: status == 401);
|
||||
return;
|
||||
}
|
||||
final data = json.decode(utf8.decode(response.bodyBytes));
|
||||
final data = json.decode(decode_http_response(response));
|
||||
final error = data['error'];
|
||||
if (error != null) {
|
||||
throw error;
|
||||
@@ -160,7 +160,7 @@ class UserModel {
|
||||
|
||||
final Map<String, dynamic> body;
|
||||
try {
|
||||
body = jsonDecode(utf8.decode(resp.bodyBytes));
|
||||
body = jsonDecode(decode_http_response(resp));
|
||||
} catch (e) {
|
||||
debugPrint("login: jsonDecode resp body failed: ${e.toString()}");
|
||||
if (resp.statusCode != 200) {
|
||||
|
||||
@@ -433,7 +433,7 @@
|
||||
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "-";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
DEVELOPMENT_TEAM = HZF9JMC8YN;
|
||||
ENABLE_HARDENED_RUNTIME = YES;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
@@ -579,7 +579,7 @@
|
||||
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "-";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
DEVELOPMENT_TEAM = HZF9JMC8YN;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
@@ -609,7 +609,7 @@
|
||||
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "-";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
DEVELOPMENT_TEAM = HZF9JMC8YN;
|
||||
ENABLE_HARDENED_RUNTIME = YES;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
|
||||
@@ -16,7 +16,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev
|
||||
# Read more about iOS versioning at
|
||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||
# 1.1.9-1 works for android, but for ios it becomes 1.1.91, need to set it to 1.1.9-a.1 for iOS, will get 1.1.9.1, but iOS store not allow 4 numbers
|
||||
version: 1.4.1+59
|
||||
version: 1.4.2+60
|
||||
|
||||
environment:
|
||||
sdk: '^3.1.0'
|
||||
|
||||
@@ -170,6 +170,8 @@ extern "C"
|
||||
|
||||
typedef UINT (*pcNotifyClipboardMsg)(UINT32 connID, const NOTIFICATION_MESSAGE *msg);
|
||||
|
||||
typedef UINT (*pcHandleClipboardFiles)(UINT32 connID, size_t nFiles, WCHAR **fileNames);
|
||||
|
||||
typedef UINT (*pcCliprdrClientFormatList)(CliprdrClientContext *context,
|
||||
const CLIPRDR_FORMAT_LIST *formatList);
|
||||
typedef UINT (*pcCliprdrServerFormatList)(CliprdrClientContext *context,
|
||||
@@ -217,6 +219,7 @@ extern "C"
|
||||
pcCliprdrMonitorReady MonitorReady;
|
||||
pcCliprdrTempDirectory TempDirectory;
|
||||
pcNotifyClipboardMsg NotifyClipboardMsg;
|
||||
pcHandleClipboardFiles HandleClipboardFiles;
|
||||
pcCliprdrClientFormatList ClientFormatList;
|
||||
pcCliprdrServerFormatList ServerFormatList;
|
||||
pcCliprdrClientFormatListResponse ClientFormatListResponse;
|
||||
|
||||
@@ -132,6 +132,9 @@ pub enum ClipboardFile {
|
||||
requested_data: Vec<u8>,
|
||||
},
|
||||
TryEmpty,
|
||||
Files {
|
||||
files: Vec<(String, u64)>,
|
||||
},
|
||||
}
|
||||
|
||||
struct MsgChannel {
|
||||
|
||||
@@ -5,7 +5,7 @@ use hbb_common::{
|
||||
log,
|
||||
};
|
||||
use parking_lot::Mutex;
|
||||
use std::{path::PathBuf, sync::Arc};
|
||||
use std::{path::PathBuf, sync::Arc, usize};
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
// local files are cached, this value should not be changed when copying files
|
||||
@@ -34,6 +34,7 @@ enum FileContentsRequest {
|
||||
struct ClipFiles {
|
||||
files: Vec<String>,
|
||||
file_list: Vec<LocalFile>,
|
||||
first_file_index: usize,
|
||||
files_pdu: Vec<u8>,
|
||||
}
|
||||
|
||||
@@ -41,6 +42,7 @@ impl ClipFiles {
|
||||
fn clear(&mut self) {
|
||||
self.files.clear();
|
||||
self.file_list.clear();
|
||||
self.first_file_index = usize::MAX;
|
||||
self.files_pdu.clear();
|
||||
}
|
||||
|
||||
@@ -50,6 +52,11 @@ impl ClipFiles {
|
||||
.map(|s| PathBuf::from(s))
|
||||
.collect::<Vec<_>>();
|
||||
self.file_list = construct_file_list(&clipboard_paths)?;
|
||||
self.first_file_index = self
|
||||
.file_list
|
||||
.iter()
|
||||
.position(|f| !f.path.is_dir())
|
||||
.unwrap_or(usize::MAX);
|
||||
self.files = clipboard_files.to_vec();
|
||||
Ok(())
|
||||
}
|
||||
@@ -63,6 +70,33 @@ impl ClipFiles {
|
||||
self.files_pdu = data.to_vec()
|
||||
}
|
||||
|
||||
fn get_files_for_audit(&self, request: &FileContentsRequest) -> Option<ClipboardFile> {
|
||||
if let FileContentsRequest::Range {
|
||||
file_idx, offset, ..
|
||||
} = request
|
||||
{
|
||||
if *file_idx == self.first_file_index && *offset == 0 {
|
||||
let files: Vec<(String, u64)> = self
|
||||
.file_list
|
||||
.iter()
|
||||
.filter_map(|f| {
|
||||
if f.path.is_file() {
|
||||
Some((f.path.to_string_lossy().to_string(), f.size))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect::<_>();
|
||||
if files.is_empty() {
|
||||
return None;
|
||||
} else {
|
||||
return Some(ClipboardFile::Files { files });
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn serve_file_contents(
|
||||
&mut self,
|
||||
conn_id: i32,
|
||||
@@ -192,7 +226,7 @@ pub fn read_file_contents(
|
||||
n_position_low: i32,
|
||||
n_position_high: i32,
|
||||
cb_requested: i32,
|
||||
) -> Result<ClipboardFile, CliprdrError> {
|
||||
) -> Vec<Result<ClipboardFile, CliprdrError>> {
|
||||
let fcr = if dw_flags == 0x1 {
|
||||
FileContentsRequest::Size {
|
||||
stream_id,
|
||||
@@ -209,12 +243,18 @@ pub fn read_file_contents(
|
||||
length,
|
||||
}
|
||||
} else {
|
||||
return Err(CliprdrError::InvalidRequest {
|
||||
return vec![Err(CliprdrError::InvalidRequest {
|
||||
description: format!("got invalid FileContentsRequest, dw_flats: {dw_flags}"),
|
||||
});
|
||||
})];
|
||||
};
|
||||
|
||||
CLIP_FILES.lock().serve_file_contents(conn_id, fcr)
|
||||
let mut clip_files = CLIP_FILES.lock();
|
||||
let mut res = vec![];
|
||||
if let Some(files_res) = clip_files.get_files_for_audit(&fcr) {
|
||||
res.push(Ok(files_res));
|
||||
}
|
||||
res.push(clip_files.serve_file_contents(conn_id, fcr));
|
||||
res
|
||||
}
|
||||
|
||||
pub fn sync_files(files: &[String]) -> Result<(), CliprdrError> {
|
||||
|
||||
@@ -381,6 +381,9 @@ pub type pcCliprdrTempDirectory = ::std::option::Option<
|
||||
pub type pcNotifyClipboardMsg = ::std::option::Option<
|
||||
unsafe extern "C" fn(connID: UINT32, msg: *const NOTIFICATION_MESSAGE) -> UINT,
|
||||
>;
|
||||
pub type pcHandleClipboardFiles = ::std::option::Option<
|
||||
unsafe extern "C" fn(connID: UINT32, nFiles: size_t, fileNames: *mut *mut WCHAR) -> UINT,
|
||||
>;
|
||||
pub type pcCliprdrClientFormatList = ::std::option::Option<
|
||||
unsafe extern "C" fn(
|
||||
context: *mut CliprdrClientContext,
|
||||
@@ -492,6 +495,7 @@ pub struct _cliprdr_client_context {
|
||||
pub MonitorReady: pcCliprdrMonitorReady,
|
||||
pub TempDirectory: pcCliprdrTempDirectory,
|
||||
pub NotifyClipboardMsg: pcNotifyClipboardMsg,
|
||||
pub HandleClipboardFiles: pcHandleClipboardFiles,
|
||||
pub ClientFormatList: pcCliprdrClientFormatList,
|
||||
pub ServerFormatList: pcCliprdrServerFormatList,
|
||||
pub ClientFormatListResponse: pcCliprdrClientFormatListResponse,
|
||||
@@ -529,6 +533,7 @@ impl CliprdrClientContext {
|
||||
enable_others: bool,
|
||||
response_wait_timeout_secs: u32,
|
||||
notify_callback: pcNotifyClipboardMsg,
|
||||
handle_clipboard_files: pcHandleClipboardFiles,
|
||||
client_format_list: pcCliprdrClientFormatList,
|
||||
client_format_list_response: pcCliprdrClientFormatListResponse,
|
||||
client_format_data_request: pcCliprdrClientFormatDataRequest,
|
||||
@@ -547,6 +552,7 @@ impl CliprdrClientContext {
|
||||
MonitorReady: None,
|
||||
TempDirectory: None,
|
||||
NotifyClipboardMsg: notify_callback,
|
||||
HandleClipboardFiles: handle_clipboard_files,
|
||||
ClientFormatList: client_format_list,
|
||||
ServerFormatList: None,
|
||||
ClientFormatListResponse: client_format_list_response,
|
||||
@@ -758,6 +764,9 @@ pub fn server_clip_file(
|
||||
ret
|
||||
);
|
||||
}
|
||||
ClipboardFile::Files { .. } => {
|
||||
// unreachable
|
||||
}
|
||||
}
|
||||
ret
|
||||
}
|
||||
@@ -967,6 +976,7 @@ pub fn create_cliprdr_context(
|
||||
enable_others,
|
||||
response_wait_timeout_secs,
|
||||
Some(notify_callback),
|
||||
Some(handle_clipboard_files),
|
||||
Some(client_format_list),
|
||||
Some(client_format_list_response),
|
||||
Some(client_format_data_request),
|
||||
@@ -1021,6 +1031,61 @@ extern "C" fn notify_callback(conn_id: UINT32, msg: *const NOTIFICATION_MESSAGE)
|
||||
0
|
||||
}
|
||||
|
||||
extern "C" fn handle_clipboard_files(
|
||||
conn_id: UINT32,
|
||||
n_files: size_t,
|
||||
file_names: *mut *mut WCHAR,
|
||||
) -> UINT {
|
||||
if n_files == 0 {
|
||||
return 0;
|
||||
}
|
||||
|
||||
let data = unsafe {
|
||||
let mut files = Vec::new();
|
||||
use std::ffi::OsString;
|
||||
use std::os::windows::ffi::OsStringExt;
|
||||
for i in 0..n_files {
|
||||
let file_name_ptr = *file_names.offset(i as isize);
|
||||
if !file_name_ptr.is_null() {
|
||||
let mut len = 0;
|
||||
while *file_name_ptr.offset(len) != 0 {
|
||||
len += 1;
|
||||
}
|
||||
let slice = std::slice::from_raw_parts(file_name_ptr, len as usize);
|
||||
let os_string = OsString::from_wide(slice);
|
||||
match os_string.to_str() {
|
||||
Some(n) => match std::fs::metadata(n) {
|
||||
Ok(meta) => {
|
||||
if meta.is_file() {
|
||||
files.push((n.to_owned(), meta.len()));
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
log::warn!(
|
||||
"handle_clipboard_files: Failed to get metadata for file '{}': {}",
|
||||
n,
|
||||
e
|
||||
);
|
||||
}
|
||||
},
|
||||
None => {
|
||||
log::warn!("handle_clipboard_files: Failed to convert file name to UTF-8");
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
if files.is_empty() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
ClipboardFile::Files { files }
|
||||
};
|
||||
// no need to handle result here
|
||||
allow_err!(send_data(conn_id as _, data));
|
||||
|
||||
0
|
||||
}
|
||||
|
||||
extern "C" fn client_format_list(
|
||||
_context: *mut CliprdrClientContext,
|
||||
clip_format_list: *const CLIPRDR_FORMAT_LIST,
|
||||
|
||||
@@ -239,6 +239,7 @@ struct wf_clipboard
|
||||
size_t nFiles;
|
||||
size_t file_array_size;
|
||||
WCHAR **file_names;
|
||||
size_t first_file_index;
|
||||
FILEDESCRIPTORW **fileDescriptor;
|
||||
|
||||
BOOL legacyApi;
|
||||
@@ -2024,6 +2025,7 @@ static void clear_file_array(wfClipboard *clipboard)
|
||||
|
||||
clipboard->file_array_size = 0;
|
||||
clipboard->nFiles = 0;
|
||||
clipboard->first_file_index = (size_t)-1;
|
||||
}
|
||||
|
||||
static BOOL wf_cliprdr_get_file_contents(WCHAR *file_name, BYTE *buffer, LONG positionLow,
|
||||
@@ -2179,6 +2181,11 @@ static BOOL wf_cliprdr_add_to_file_arrays(wfClipboard *clipboard, WCHAR *full_fi
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if ((clipboard->fileDescriptor[clipboard->nFiles]->dwFileAttributes &
|
||||
FILE_ATTRIBUTE_DIRECTORY) == 0) {
|
||||
clipboard->first_file_index = clipboard->nFiles;
|
||||
}
|
||||
|
||||
clipboard->nFiles++;
|
||||
return TRUE;
|
||||
}
|
||||
@@ -2968,6 +2975,14 @@ wf_cliprdr_server_file_contents_request(CliprdrClientContext *context,
|
||||
{
|
||||
LARGE_INTEGER dlibMove;
|
||||
ULARGE_INTEGER dlibNewPosition;
|
||||
|
||||
if (clipboard->nFiles > 0 &&
|
||||
fileContentsRequest->listIndex == (UINT32)clipboard->first_file_index &&
|
||||
fileContentsRequest->nPositionLow == 0 &&
|
||||
fileContentsRequest->nPositionHigh == 0) {
|
||||
clipboard->context->HandleClipboardFiles(fileContentsRequest->connID, clipboard->nFiles, clipboard->file_names);
|
||||
}
|
||||
|
||||
dlibMove.HighPart = fileContentsRequest->nPositionHigh;
|
||||
dlibMove.LowPart = fileContentsRequest->nPositionLow;
|
||||
hRet = IStream_Seek(pStreamStc, dlibMove, STREAM_SEEK_SET, &dlibNewPosition);
|
||||
@@ -2999,6 +3014,13 @@ wf_cliprdr_server_file_contents_request(CliprdrClientContext *context,
|
||||
rc = ERROR_INTERNAL_ERROR;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
if (clipboard->nFiles > 0 &&
|
||||
fileContentsRequest->listIndex == (UINT32)clipboard->first_file_index &&
|
||||
fileContentsRequest->nPositionLow == 0 &&
|
||||
fileContentsRequest->nPositionHigh == 0) {
|
||||
clipboard->context->HandleClipboardFiles(fileContentsRequest->connID, clipboard->nFiles, clipboard->file_names);
|
||||
}
|
||||
bRet = wf_cliprdr_get_file_contents(
|
||||
clipboard->file_names[fileContentsRequest->listIndex], pData,
|
||||
fileContentsRequest->nPositionLow, fileContentsRequest->nPositionHigh, cbRequested,
|
||||
|
||||
Submodule libs/hbb_common updated: f91459c4ab...334641686c
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "rustdesk-portable-packer"
|
||||
version = "1.4.1"
|
||||
version = "1.4.2"
|
||||
edition = "2021"
|
||||
description = "RustDesk Remote Desktop"
|
||||
|
||||
|
||||
@@ -62,21 +62,15 @@ fn link_vcpkg(mut path: PathBuf, name: &str) -> PathBuf {
|
||||
}
|
||||
path.push(target);
|
||||
println!(
|
||||
"{}",
|
||||
format!(
|
||||
"cargo:rustc-link-lib=static={}",
|
||||
name.trim_start_matches("lib")
|
||||
)
|
||||
"cargo:rustc-link-lib=static={}",
|
||||
name.trim_start_matches("lib")
|
||||
);
|
||||
println!(
|
||||
"{}",
|
||||
format!(
|
||||
"cargo:rustc-link-search={}",
|
||||
path.join("lib").to_str().unwrap()
|
||||
)
|
||||
"cargo:rustc-link-search={}",
|
||||
path.join("lib").to_str().unwrap()
|
||||
);
|
||||
let include = path.join("include");
|
||||
println!("{}", format!("cargo:include={}", include.to_str().unwrap()));
|
||||
println!("cargo:include={}", include.to_str().unwrap());
|
||||
include
|
||||
}
|
||||
|
||||
@@ -111,23 +105,17 @@ fn link_homebrew_m1(name: &str) -> PathBuf {
|
||||
path.push(directories.pop().unwrap());
|
||||
// Link the library.
|
||||
println!(
|
||||
"{}",
|
||||
format!(
|
||||
"cargo:rustc-link-lib=static={}",
|
||||
name.trim_start_matches("lib")
|
||||
)
|
||||
"cargo:rustc-link-lib=static={}",
|
||||
name.trim_start_matches("lib")
|
||||
);
|
||||
// Add the library path.
|
||||
println!(
|
||||
"{}",
|
||||
format!(
|
||||
"cargo:rustc-link-search={}",
|
||||
path.join("lib").to_str().unwrap()
|
||||
)
|
||||
"cargo:rustc-link-search={}",
|
||||
path.join("lib").to_str().unwrap()
|
||||
);
|
||||
// Add the include path.
|
||||
let include = path.join("include");
|
||||
println!("{}", format!("cargo:include={}", include.to_str().unwrap()));
|
||||
println!("cargo:include={}", include.to_str().unwrap());
|
||||
include
|
||||
}
|
||||
|
||||
@@ -239,6 +227,24 @@ fn ffmpeg() {
|
||||
*/
|
||||
|
||||
fn main() {
|
||||
// there is problem with cfg(target_os) in build.rs, so use our workaround
|
||||
let target_os = std::env::var("CARGO_CFG_TARGET_OS").unwrap();
|
||||
|
||||
// We check if is macos, because macos uses rust 1.8.1.
|
||||
// `cargo::rustc-check-cfg` is new with Cargo 1.80.
|
||||
// No need to run `cargo version` to get the version here, because:
|
||||
// The following lines are used to suppress the lint warnings.
|
||||
// warning: unexpected `cfg` condition name: `quartz`
|
||||
if cfg!(target_os = "macos") {
|
||||
if target_os != "ios" {
|
||||
println!("cargo::rustc-check-cfg=cfg(android)");
|
||||
println!("cargo::rustc-check-cfg=cfg(dxgi)");
|
||||
println!("cargo::rustc-check-cfg=cfg(quartz)");
|
||||
println!("cargo::rustc-check-cfg=cfg(x11)");
|
||||
// ^^^^^^^^^^^^^^^^^^^^^^ new with Cargo 1.80
|
||||
}
|
||||
}
|
||||
|
||||
// note: all link symbol names in x86 (32-bit) are prefixed wth "_".
|
||||
// run "rustup show" to show current default toolchain, if it is stable-x86-pc-windows-msvc,
|
||||
// please install x64 toolchain by "rustup toolchain install stable-x86_64-pc-windows-msvc",
|
||||
@@ -256,8 +262,6 @@ fn main() {
|
||||
gen_vcpkg_package("libyuv", "yuv_ffi.h", "yuv_ffi.rs", ".*");
|
||||
// ffmpeg();
|
||||
|
||||
// there is problem with cfg(target_os) in build.rs, so use our workaround
|
||||
let target_os = std::env::var("CARGO_CFG_TARGET_OS").unwrap();
|
||||
if target_os == "ios" {
|
||||
// nothing
|
||||
} else if target_os == "android" {
|
||||
|
||||
@@ -17,7 +17,9 @@ use hbb_common::message_proto::{DisplayInfo, Resolution};
|
||||
use crate::AdapterDevice;
|
||||
|
||||
use crate::common::{bail, ResultType};
|
||||
use crate::{Frame, PixelBuffer, Pixfmt, TraitCapturer};
|
||||
use crate::{Frame, TraitCapturer};
|
||||
#[cfg(any(target_os = "windows", target_os = "linux"))]
|
||||
use crate::{PixelBuffer, Pixfmt};
|
||||
|
||||
pub const PRIMARY_CAMERA_IDX: usize = 0;
|
||||
lazy_static::lazy_static! {
|
||||
@@ -162,11 +164,11 @@ impl Cameras {
|
||||
return Ok(Vec::new());
|
||||
}
|
||||
|
||||
pub fn exists(index: usize) -> bool {
|
||||
pub fn exists(_index: usize) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
pub fn get_camera_resolution(index: usize) -> ResultType<Resolution> {
|
||||
pub fn get_camera_resolution(_index: usize) -> ResultType<Resolution> {
|
||||
bail!(CAMERA_NOT_SUPPORTED);
|
||||
}
|
||||
|
||||
@@ -174,7 +176,7 @@ impl Cameras {
|
||||
vec![]
|
||||
}
|
||||
|
||||
pub fn get_capturer(current: usize) -> ResultType<Box<dyn TraitCapturer>> {
|
||||
pub fn get_capturer(_current: usize) -> ResultType<Box<dyn TraitCapturer>> {
|
||||
bail!(CAMERA_NOT_SUPPORTED);
|
||||
}
|
||||
}
|
||||
@@ -201,6 +203,7 @@ impl CameraCapturer {
|
||||
})
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[cfg(not(any(target_os = "windows", target_os = "linux")))]
|
||||
fn new(_current: usize) -> ResultType<Self> {
|
||||
bail!(CAMERA_NOT_SUPPORTED);
|
||||
|
||||
@@ -18,10 +18,17 @@ use crate::{
|
||||
CodecFormat, EncodeInput, EncodeYuvFormat, ImageRgb, ImageTexture,
|
||||
};
|
||||
|
||||
#[cfg(any(
|
||||
feature = "hwcodec",
|
||||
feature = "mediacodec",
|
||||
feature = "vram",
|
||||
target_os = "windows"
|
||||
))]
|
||||
use hbb_common::config::option2bool;
|
||||
use hbb_common::{
|
||||
anyhow::anyhow,
|
||||
bail,
|
||||
config::{option2bool, Config, PeerConfig},
|
||||
config::{Config, PeerConfig},
|
||||
lazy_static, log,
|
||||
message_proto::{
|
||||
supported_decoding::PreferCodec, video_frame, Chroma, CodecAbility, EncodedVideoFrames,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
pkgname=rustdesk
|
||||
pkgver=1.4.1
|
||||
pkgver=1.4.2
|
||||
pkgrel=0
|
||||
epoch=
|
||||
pkgdesc=""
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
Name: rustdesk
|
||||
Version: 1.4.1
|
||||
Version: 1.4.2
|
||||
Release: 0
|
||||
Summary: RPM package
|
||||
License: GPL-3.0
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
Name: rustdesk
|
||||
Version: 1.4.1
|
||||
Version: 1.4.2
|
||||
Release: 0
|
||||
Summary: RPM package
|
||||
License: GPL-3.0
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
Name: rustdesk
|
||||
Version: 1.4.1
|
||||
Version: 1.4.2
|
||||
Release: 0
|
||||
Summary: RPM package
|
||||
License: GPL-3.0
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
[Desktop Entry]
|
||||
Name=RustDeskURL Scheme Handler
|
||||
Name=RustDesk
|
||||
NoDisplay=true
|
||||
MimeType=x-scheme-handler/rustdesk;
|
||||
TryExec=rustdesk
|
||||
|
||||
@@ -8,7 +8,7 @@ Terminal=false
|
||||
Type=Application
|
||||
StartupNotify=true
|
||||
Categories=Network;RemoteAccess;GTK;
|
||||
Keywords=internet;
|
||||
Keywords=internet;linux;dart;rust;remote-control;p2p;teamviewer;rust-lang;rdp;remote-desktop;vnc;
|
||||
Actions=new-window;
|
||||
StartupWMClass=rustdesk
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ KillMode=mixed
|
||||
TimeoutStopSec=30
|
||||
User=root
|
||||
LimitNOFILE=100000
|
||||
Environment="PULSE_LATENCY_MSEC=60" "PIPEWIRE_LATENCY=1024/48000"
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
|
||||
178
res/setup.nsi
178
res/setup.nsi
@@ -1,178 +0,0 @@
|
||||
Unicode true
|
||||
|
||||
####################################################################
|
||||
# Includes
|
||||
|
||||
!include nsDialogs.nsh
|
||||
!include MUI2.nsh
|
||||
!include x64.nsh
|
||||
!include LogicLib.nsh
|
||||
|
||||
####################################################################
|
||||
# File Info
|
||||
|
||||
!define PRODUCT_NAME "RustDesk"
|
||||
!define PRODUCT_DESCRIPTION "Installer for ${PRODUCT_NAME}"
|
||||
!define COPYRIGHT "Copyright © 2021"
|
||||
!define VERSION "1.1.6"
|
||||
|
||||
VIProductVersion "${VERSION}.0"
|
||||
VIAddVersionKey "ProductName" "${PRODUCT_NAME}"
|
||||
VIAddVersionKey "ProductVersion" "${VERSION}"
|
||||
VIAddVersionKey "FileDescription" "${PRODUCT_DESCRIPTION}"
|
||||
VIAddVersionKey "LegalCopyright" "${COPYRIGHT}"
|
||||
VIAddVersionKey "FileVersion" "${VERSION}.0"
|
||||
|
||||
####################################################################
|
||||
# Installer Attributes
|
||||
|
||||
Name "${PRODUCT_NAME}"
|
||||
Outfile "rustdesk-${VERSION}-setup.exe"
|
||||
Caption "Setup - ${PRODUCT_NAME}"
|
||||
BrandingText "${PRODUCT_NAME}"
|
||||
|
||||
ShowInstDetails show
|
||||
RequestExecutionLevel admin
|
||||
SetOverwrite on
|
||||
|
||||
InstallDir "$PROGRAMFILES64\${PRODUCT_NAME}"
|
||||
|
||||
####################################################################
|
||||
# Pages
|
||||
|
||||
!define MUI_ICON "icon.ico"
|
||||
!define MUI_ABORTWARNING
|
||||
!define MUI_LANGDLL_ALLLANGUAGES
|
||||
!define MUI_FINISHPAGE_SHOWREADME ""
|
||||
!define MUI_FINISHPAGE_SHOWREADME_NOTCHECKED
|
||||
!define MUI_FINISHPAGE_SHOWREADME_TEXT "Create desktop shortcut"
|
||||
!define MUI_FINISHPAGE_SHOWREADME_FUNCTION CreateDesktopShortcut
|
||||
!define MUI_FINISHPAGE_RUN "$INSTDIR\${PRODUCT_NAME}.exe"
|
||||
|
||||
!insertmacro MUI_PAGE_DIRECTORY
|
||||
!insertmacro MUI_PAGE_INSTFILES
|
||||
!insertmacro MUI_PAGE_FINISH
|
||||
|
||||
####################################################################
|
||||
# Language
|
||||
|
||||
!insertmacro MUI_LANGUAGE "English" ; The first language is the default language
|
||||
!insertmacro MUI_LANGUAGE "French"
|
||||
!insertmacro MUI_LANGUAGE "German"
|
||||
!insertmacro MUI_LANGUAGE "Spanish"
|
||||
!insertmacro MUI_LANGUAGE "SpanishInternational"
|
||||
!insertmacro MUI_LANGUAGE "SimpChinese"
|
||||
!insertmacro MUI_LANGUAGE "TradChinese"
|
||||
!insertmacro MUI_LANGUAGE "Japanese"
|
||||
!insertmacro MUI_LANGUAGE "Korean"
|
||||
!insertmacro MUI_LANGUAGE "Italian"
|
||||
!insertmacro MUI_LANGUAGE "Dutch"
|
||||
!insertmacro MUI_LANGUAGE "Danish"
|
||||
!insertmacro MUI_LANGUAGE "Swedish"
|
||||
!insertmacro MUI_LANGUAGE "Norwegian"
|
||||
!insertmacro MUI_LANGUAGE "NorwegianNynorsk"
|
||||
!insertmacro MUI_LANGUAGE "Finnish"
|
||||
!insertmacro MUI_LANGUAGE "Greek"
|
||||
!insertmacro MUI_LANGUAGE "Russian"
|
||||
!insertmacro MUI_LANGUAGE "Portuguese"
|
||||
!insertmacro MUI_LANGUAGE "PortugueseBR"
|
||||
!insertmacro MUI_LANGUAGE "Polish"
|
||||
!insertmacro MUI_LANGUAGE "Ukrainian"
|
||||
!insertmacro MUI_LANGUAGE "Czech"
|
||||
!insertmacro MUI_LANGUAGE "Slovak"
|
||||
!insertmacro MUI_LANGUAGE "Croatian"
|
||||
!insertmacro MUI_LANGUAGE "Bulgarian"
|
||||
!insertmacro MUI_LANGUAGE "Hungarian"
|
||||
!insertmacro MUI_LANGUAGE "Thai"
|
||||
!insertmacro MUI_LANGUAGE "Romanian"
|
||||
!insertmacro MUI_LANGUAGE "Latvian"
|
||||
!insertmacro MUI_LANGUAGE "Macedonian"
|
||||
!insertmacro MUI_LANGUAGE "Estonian"
|
||||
!insertmacro MUI_LANGUAGE "Turkish"
|
||||
!insertmacro MUI_LANGUAGE "Lithuanian"
|
||||
!insertmacro MUI_LANGUAGE "Slovenian"
|
||||
!insertmacro MUI_LANGUAGE "Serbian"
|
||||
!insertmacro MUI_LANGUAGE "SerbianLatin"
|
||||
!insertmacro MUI_LANGUAGE "Arabic"
|
||||
!insertmacro MUI_LANGUAGE "Farsi"
|
||||
!insertmacro MUI_LANGUAGE "Hebrew"
|
||||
!insertmacro MUI_LANGUAGE "Indonesian"
|
||||
!insertmacro MUI_LANGUAGE "Mongolian"
|
||||
!insertmacro MUI_LANGUAGE "Luxembourgish"
|
||||
!insertmacro MUI_LANGUAGE "Albanian"
|
||||
!insertmacro MUI_LANGUAGE "Breton"
|
||||
!insertmacro MUI_LANGUAGE "Belarusian"
|
||||
!insertmacro MUI_LANGUAGE "Icelandic"
|
||||
!insertmacro MUI_LANGUAGE "Malay"
|
||||
!insertmacro MUI_LANGUAGE "Bosnian"
|
||||
!insertmacro MUI_LANGUAGE "Kurdish"
|
||||
!insertmacro MUI_LANGUAGE "Irish"
|
||||
!insertmacro MUI_LANGUAGE "Uzbek"
|
||||
!insertmacro MUI_LANGUAGE "Galician"
|
||||
!insertmacro MUI_LANGUAGE "Afrikaans"
|
||||
!insertmacro MUI_LANGUAGE "Catalan"
|
||||
!insertmacro MUI_LANGUAGE "Esperanto"
|
||||
!insertmacro MUI_LANGUAGE "Asturian"
|
||||
!insertmacro MUI_LANGUAGE "Basque"
|
||||
!insertmacro MUI_LANGUAGE "Pashto"
|
||||
!insertmacro MUI_LANGUAGE "ScotsGaelic"
|
||||
!insertmacro MUI_LANGUAGE "Georgian"
|
||||
!insertmacro MUI_LANGUAGE "Vietnamese"
|
||||
!insertmacro MUI_LANGUAGE "Welsh"
|
||||
!insertmacro MUI_LANGUAGE "Armenian"
|
||||
!insertmacro MUI_LANGUAGE "Corsican"
|
||||
!insertmacro MUI_LANGUAGE "Tatar"
|
||||
!insertmacro MUI_LANGUAGE "Hindi"
|
||||
|
||||
|
||||
####################################################################
|
||||
# Sections
|
||||
|
||||
Section "Install"
|
||||
SetOutPath $INSTDIR
|
||||
|
||||
# Regkeys
|
||||
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}" "DisplayIcon" "$INSTDIR\${PRODUCT_NAME}.exe"
|
||||
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}" "DisplayName" "${PRODUCT_NAME} (x64)"
|
||||
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}" "DisplayVersion" "${VERSION}"
|
||||
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}" "UninstallString" '"$INSTDIR\${PRODUCT_NAME}.exe" --uninstall'
|
||||
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}" "InstallLocation" "$INSTDIR"
|
||||
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}" "Publisher" "Purslane Ltd."
|
||||
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}" "HelpLink" "https://www.rustdesk.com/"
|
||||
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}" "URLInfoAbout" "https://www.rustdesk.com/"
|
||||
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}" "URLUpdateInfo" "https://www.rustdesk.com/"
|
||||
|
||||
nsExec::Exec "taskkill /F /IM ${PRODUCT_NAME}.exe"
|
||||
Sleep 500 ; Give time for process to be completely killed
|
||||
File "${PRODUCT_NAME}.exe"
|
||||
|
||||
SetShellVarContext all
|
||||
CreateShortCut "$INSTDIR\Uninstall ${PRODUCT_NAME}.lnk" "$INSTDIR\${PRODUCT_NAME}.exe" "--uninstall" "msiexec.exe"
|
||||
CreateDirectory "$SMPROGRAMS\${PRODUCT_NAME}"
|
||||
CreateShortCut "$SMPROGRAMS\${PRODUCT_NAME}\${PRODUCT_NAME}.lnk" "$INSTDIR\${PRODUCT_NAME}.exe"
|
||||
CreateShortCut "$SMPROGRAMS\${PRODUCT_NAME}\Uninstall ${PRODUCT_NAME}.lnk" "$INSTDIR\${PRODUCT_NAME}.exe" "--uninstall" "msiexec.exe"
|
||||
CreateShortCut "$SMSTARTUP\${PRODUCT_NAME} Tray.lnk" "$INSTDIR\${PRODUCT_NAME}.exe" "--tray"
|
||||
|
||||
nsExec::Exec 'sc create ${PRODUCT_NAME} start=auto DisplayName="${PRODUCT_NAME} Service" binPath= "\"$INSTDIR\${PRODUCT_NAME}.exe\" --service"'
|
||||
nsExec::Exec 'netsh advfirewall firewall add rule name="${PRODUCT_NAME} Service" dir=in action=allow program="$INSTDIR\${PRODUCT_NAME}.exe" enable=yes'
|
||||
nsExec::Exec 'sc start ${PRODUCT_NAME}'
|
||||
SectionEnd
|
||||
|
||||
####################################################################
|
||||
# Functions
|
||||
|
||||
Function .onInit
|
||||
# RustDesk is 64-bit only
|
||||
${IfNot} ${RunningX64}
|
||||
MessageBox MB_ICONSTOP "${PRODUCT_NAME} is 64-bit only!"
|
||||
Quit
|
||||
${EndIf}
|
||||
${DisableX64FSRedirection}
|
||||
SetRegView 64
|
||||
|
||||
!insertmacro MUI_LANGDLL_DISPLAY
|
||||
FunctionEnd
|
||||
|
||||
Function CreateDesktopShortcut
|
||||
CreateShortCut "$DESKTOP\${PRODUCT_NAME}.lnk" "$INSTDIR\${PRODUCT_NAME}.exe"
|
||||
FunctionEnd
|
||||
@@ -22,7 +22,7 @@ else()
|
||||
vcpkg_from_git(
|
||||
OUT_SOURCE_PATH SOURCE_PATH
|
||||
URL "https://aomedia.googlesource.com/aom"
|
||||
REF d6f30ae474dd6c358f26de0a0fc26a0d7340a84c # 3.11.0
|
||||
REF 10aece4157eb79315da205f39e19bf6ab3ee30d0 # 3.12.1
|
||||
PATCHES
|
||||
aom-uninitialized-pointer.diff
|
||||
# aom-avx2.diff
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "aom",
|
||||
"version-semver": "3.11.0",
|
||||
"version-semver": "3.12.1",
|
||||
"port-version": 0,
|
||||
"description": "AV1 codec library",
|
||||
"homepage": "https://aomedia.googlesource.com/aom",
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
From a609e1666c79ccce4faf7aa61d509bf202df9149 Mon Sep 17 00:00:00 2001
|
||||
From: 21pages <sunboeasy@gmail.com>
|
||||
Date: Fri, 5 Sep 2025 21:35:37 +0800
|
||||
Subject: [PATCH] android mediacodec encode align 64
|
||||
|
||||
Signed-off-by: 21pages <sunboeasy@gmail.com>
|
||||
---
|
||||
libavcodec/mediacodecenc.c | 11 ++++++-----
|
||||
1 file changed, 6 insertions(+), 5 deletions(-)
|
||||
|
||||
diff --git a/libavcodec/mediacodecenc.c b/libavcodec/mediacodecenc.c
|
||||
index 221f7360f4..768c8151df 100644
|
||||
--- a/libavcodec/mediacodecenc.c
|
||||
+++ b/libavcodec/mediacodecenc.c
|
||||
@@ -242,18 +242,19 @@ static av_cold int mediacodec_init(AVCodecContext *avctx)
|
||||
ff_AMediaFormat_setString(format, "mime", codec_mime);
|
||||
// Workaround the alignment requirement of mediacodec. We can't do it
|
||||
// silently for AV_PIX_FMT_MEDIACODEC.
|
||||
+ const int align = 64;
|
||||
if (avctx->pix_fmt != AV_PIX_FMT_MEDIACODEC &&
|
||||
(avctx->codec_id == AV_CODEC_ID_H264 ||
|
||||
avctx->codec_id == AV_CODEC_ID_HEVC)) {
|
||||
- s->width = FFALIGN(avctx->width, 16);
|
||||
- s->height = FFALIGN(avctx->height, 16);
|
||||
+ s->width = FFALIGN(avctx->width, align);
|
||||
+ s->height = FFALIGN(avctx->height, align);
|
||||
} else {
|
||||
s->width = avctx->width;
|
||||
s->height = avctx->height;
|
||||
- if (s->width % 16 || s->height % 16)
|
||||
+ if (s->width % align || s->height % align)
|
||||
av_log(avctx, AV_LOG_WARNING,
|
||||
- "Video size %dx%d isn't align to 16, it may have device compatibility issue\n",
|
||||
- s->width, s->height);
|
||||
+ "Video size %dx%d isn't align to %d, it may have device compatibility issue\n",
|
||||
+ s->width, s->height, align);
|
||||
}
|
||||
ff_AMediaFormat_setInt32(format, "width", s->width);
|
||||
ff_AMediaFormat_setInt32(format, "height", s->height);
|
||||
--
|
||||
2.43.0.windows.1
|
||||
|
||||
@@ -26,6 +26,7 @@ vcpkg_from_github(
|
||||
patch/0008-remove-amf-loop-query.patch
|
||||
patch/0009-fix-nvenc-reconfigure-blur.patch
|
||||
patch/0010.disable-loading-DLLs-from-app-dir.patch
|
||||
patch/0011-android-mediacodec-encode-align-64.patch
|
||||
)
|
||||
|
||||
if(SOURCE_PATH MATCHES " ")
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
diff --git a/build/make/configure.sh b/build/make/configure.sh
|
||||
index 81d30a1..325017e 100644
|
||||
--- a/build/make/configure.sh
|
||||
+++ b/build/make/configure.sh
|
||||
@@ -1370,12 +1370,14 @@ EOF
|
||||
case ${tgt_os} in
|
||||
win32)
|
||||
add_asflags -f win32
|
||||
- enabled debug && add_asflags -g cv8
|
||||
+ enabled debug && [ "${AS}" = yasm ] && add_asflags -g cv8
|
||||
+ enabled debug && [ "${AS}" = nasm ] && add_asflags -gcv8
|
||||
EXE_SFX=.exe
|
||||
;;
|
||||
win64)
|
||||
add_asflags -f win64
|
||||
- enabled debug && add_asflags -g cv8
|
||||
+ enabled debug && [ "${AS}" = yasm ] && add_asflags -g cv8
|
||||
+ enabled debug && [ "${AS}" = nasm ] && add_asflags -gcv8
|
||||
EXE_SFX=.exe
|
||||
;;
|
||||
linux*|solaris*|android*)
|
||||
@@ -1,8 +1,8 @@
|
||||
diff --git a/build/make/configure.sh b/build/make/configure.sh
|
||||
index 110f16e..c161d0e 100644
|
||||
index cc5bf6ce4..9380e87a7 100644
|
||||
--- a/build/make/configure.sh
|
||||
+++ b/build/make/configure.sh
|
||||
@@ -1038,7 +1038,7 @@ EOF
|
||||
@@ -1092,7 +1092,7 @@ EOF
|
||||
# A number of ARM-based Windows platforms are constrained by their
|
||||
# respective SDKs' limitations. Fortunately, these are all 32-bit ABIs
|
||||
# and so can be selected as 'win32'.
|
||||
@@ -11,7 +11,7 @@ index 110f16e..c161d0e 100644
|
||||
asm_conversion_cmd="${source_path_mk}/build/make/ads2armasm_ms.pl"
|
||||
AS_SFX=.S
|
||||
msvs_arch_dir=arm-msvs
|
||||
@@ -1272,6 +1272,9 @@ EOF
|
||||
@@ -1366,6 +1366,9 @@ EOF
|
||||
android)
|
||||
soft_enable realtime_only
|
||||
;;
|
||||
@@ -21,12 +21,12 @@ index 110f16e..c161d0e 100644
|
||||
win*)
|
||||
enabled gcc && add_cflags -fno-common
|
||||
;;
|
||||
@@ -1390,6 +1393,16 @@ EOF
|
||||
@@ -1484,14 +1487,26 @@ EOF
|
||||
fi
|
||||
AS_SFX=.asm
|
||||
case ${tgt_os} in
|
||||
+ uwp)
|
||||
+ if [ {$tgt_isa} = "x86" ] || [ {$tgt_isa} = "armv7" ]; then
|
||||
+ if [ ${tgt_isa} = "x86" ] || [ ${tgt_isa} = "armv7" ]; then
|
||||
+ add_asflags -f win32
|
||||
+ else
|
||||
+ add_asflags -f win64
|
||||
@@ -37,8 +37,20 @@ index 110f16e..c161d0e 100644
|
||||
+ ;;
|
||||
win32)
|
||||
add_asflags -f win32
|
||||
enabled debug && [ "${AS}" = yasm ] && add_asflags -g cv8
|
||||
@@ -1519,6 +1532,8 @@ EOF
|
||||
- enabled debug && add_asflags -g cv8
|
||||
+ enabled debug && [ "${AS}" = yasm ] && add_asflags -g cv8
|
||||
+ enabled debug && [ "${AS}" = nasm ] && add_asflags -gcv8
|
||||
EXE_SFX=.exe
|
||||
;;
|
||||
win64)
|
||||
add_asflags -f win64
|
||||
- enabled debug && add_asflags -g cv8
|
||||
+ enabled debug && [ "${AS}" = yasm ] && add_asflags -g cv8
|
||||
+ enabled debug && [ "${AS}" = nasm ] && add_asflags -gcv8
|
||||
EXE_SFX=.exe
|
||||
;;
|
||||
linux*|solaris*|android*)
|
||||
@@ -1622,6 +1637,8 @@ EOF
|
||||
# Almost every platform uses pthreads.
|
||||
if enabled multithread; then
|
||||
case ${toolchain} in
|
||||
@@ -48,10 +60,10 @@ index 110f16e..c161d0e 100644
|
||||
;;
|
||||
*-android-gcc)
|
||||
diff --git a/build/make/gen_msvs_vcxproj.sh b/build/make/gen_msvs_vcxproj.sh
|
||||
index 58bb66b..b4cad6c 100644
|
||||
index 1e1db05bb..543eb37b2 100755
|
||||
--- a/build/make/gen_msvs_vcxproj.sh
|
||||
+++ b/build/make/gen_msvs_vcxproj.sh
|
||||
@@ -296,7 +296,22 @@ generate_vcxproj() {
|
||||
@@ -310,7 +310,22 @@ generate_vcxproj() {
|
||||
tag_content ProjectGuid "{${guid}}"
|
||||
tag_content RootNamespace ${name}
|
||||
tag_content Keyword ManagedCProj
|
||||
@@ -75,7 +87,7 @@ index 58bb66b..b4cad6c 100644
|
||||
tag_content AppContainerApplication true
|
||||
# The application type can be one of "Windows Store",
|
||||
# "Windows Phone" or "Windows Phone Silverlight". The
|
||||
@@ -394,7 +409,7 @@ generate_vcxproj() {
|
||||
@@ -412,7 +427,7 @@ generate_vcxproj() {
|
||||
Condition="'\$(Configuration)|\$(Platform)'=='$config|$plat'"
|
||||
if [ "$name" == "vpx" ]; then
|
||||
hostplat=$plat
|
||||
@@ -85,19 +97,19 @@ index 58bb66b..b4cad6c 100644
|
||||
fi
|
||||
fi
|
||||
diff --git a/configure b/configure
|
||||
index b212e07..1a9fa98 100755
|
||||
index 457bd6b38..fa4bce71b 100755
|
||||
--- a/configure
|
||||
+++ b/configure
|
||||
@@ -104,6 +104,8 @@ all_platforms="${all_platforms} arm64-darwin21-gcc"
|
||||
all_platforms="${all_platforms} arm64-darwin22-gcc"
|
||||
@@ -105,6 +105,8 @@ all_platforms="${all_platforms} arm64-darwin22-gcc"
|
||||
all_platforms="${all_platforms} arm64-darwin23-gcc"
|
||||
all_platforms="${all_platforms} arm64-darwin24-gcc"
|
||||
all_platforms="${all_platforms} arm64-linux-gcc"
|
||||
+all_platforms="${all_platforms} arm64-uwp-vs16"
|
||||
+all_platforms="${all_platforms} arm64-uwp-vs17"
|
||||
all_platforms="${all_platforms} arm64-win64-gcc"
|
||||
all_platforms="${all_platforms} arm64-win64-vs15"
|
||||
all_platforms="${all_platforms} arm64-win64-vs16"
|
||||
@@ -115,6 +117,8 @@ all_platforms="${all_platforms} armv7-darwin-gcc" #neon Cortex-A8
|
||||
@@ -116,6 +118,8 @@ all_platforms="${all_platforms} armv7-darwin-gcc" #neon Cortex-A8
|
||||
all_platforms="${all_platforms} armv7-linux-rvct" #neon Cortex-A8
|
||||
all_platforms="${all_platforms} armv7-linux-gcc" #neon Cortex-A8
|
||||
all_platforms="${all_platforms} armv7-none-rvct" #neon Cortex-A8
|
||||
@@ -106,7 +118,7 @@ index b212e07..1a9fa98 100755
|
||||
all_platforms="${all_platforms} armv7-win32-gcc"
|
||||
all_platforms="${all_platforms} armv7-win32-vs14"
|
||||
all_platforms="${all_platforms} armv7-win32-vs15"
|
||||
@@ -146,6 +150,8 @@ all_platforms="${all_platforms} x86-linux-gcc"
|
||||
@@ -147,6 +151,8 @@ all_platforms="${all_platforms} x86-linux-gcc"
|
||||
all_platforms="${all_platforms} x86-linux-icc"
|
||||
all_platforms="${all_platforms} x86-os2-gcc"
|
||||
all_platforms="${all_platforms} x86-solaris-gcc"
|
||||
@@ -115,7 +127,7 @@ index b212e07..1a9fa98 100755
|
||||
all_platforms="${all_platforms} x86-win32-gcc"
|
||||
all_platforms="${all_platforms} x86-win32-vs14"
|
||||
all_platforms="${all_platforms} x86-win32-vs15"
|
||||
@@ -171,6 +177,8 @@ all_platforms="${all_platforms} x86_64-iphonesimulator-gcc"
|
||||
@@ -173,6 +179,8 @@ all_platforms="${all_platforms} x86_64-iphonesimulator-gcc"
|
||||
all_platforms="${all_platforms} x86_64-linux-gcc"
|
||||
all_platforms="${all_platforms} x86_64-linux-icc"
|
||||
all_platforms="${all_platforms} x86_64-solaris-gcc"
|
||||
@@ -124,7 +136,7 @@ index b212e07..1a9fa98 100755
|
||||
all_platforms="${all_platforms} x86_64-win64-gcc"
|
||||
all_platforms="${all_platforms} x86_64-win64-vs14"
|
||||
all_platforms="${all_platforms} x86_64-win64-vs15"
|
||||
@@ -503,11 +511,10 @@ process_targets() {
|
||||
@@ -507,11 +515,10 @@ process_targets() {
|
||||
! enabled multithread && DIST_DIR="${DIST_DIR}-nomt"
|
||||
! enabled install_docs && DIST_DIR="${DIST_DIR}-nodocs"
|
||||
DIST_DIR="${DIST_DIR}-${tgt_isa}-${tgt_os}"
|
||||
@@ -140,7 +152,7 @@ index b212e07..1a9fa98 100755
|
||||
if [ -f "${source_path}/build/make/version.sh" ]; then
|
||||
ver=`"$source_path/build/make/version.sh" --bare "$source_path"`
|
||||
DIST_DIR="${DIST_DIR}-${ver}"
|
||||
@@ -596,6 +603,10 @@ process_detect() {
|
||||
@@ -600,6 +607,10 @@ process_detect() {
|
||||
|
||||
# Specialize windows and POSIX environments.
|
||||
case $toolchain in
|
||||
@@ -151,3 +163,6 @@ index b212e07..1a9fa98 100755
|
||||
*-win*-*)
|
||||
# Don't check for any headers in Windows builds.
|
||||
false
|
||||
--
|
||||
2.49.0
|
||||
|
||||
|
||||
@@ -4,10 +4,9 @@ vcpkg_from_github(
|
||||
OUT_SOURCE_PATH SOURCE_PATH
|
||||
REPO webmproject/libvpx
|
||||
REF "v${VERSION}"
|
||||
SHA512 8f483653a324c710fd431b87fd0d5d6f476f006bd8c8e9c6d1fa6abd105d6a40ac81c8fd5638b431c455d57ab2ee823c165e9875eb3932e6e518477422da3a7b
|
||||
SHA512 824fe8719e4115ec359ae0642f5e1cea051d458f09eb8c24d60858cf082f66e411215e23228173ab154044bafbdfbb2d93b589bb726f55b233939b91f928aae0
|
||||
HEAD_REF master
|
||||
PATCHES
|
||||
0002-Fix-nasm-debug-format-flag.patch
|
||||
0003-add-uwp-v142-and-v143-support.patch
|
||||
0004-remove-library-suffixes.patch
|
||||
)
|
||||
@@ -226,6 +225,12 @@ else()
|
||||
set(LIBVPX_TARGET "generic-gnu") # use default target
|
||||
endif()
|
||||
|
||||
if (VCPKG_HOST_IS_OPENBSD OR VCPKG_HOST_IS_FREEBSD)
|
||||
set(MAKE_BINARY "gmake")
|
||||
else()
|
||||
set(MAKE_BINARY "make")
|
||||
endif()
|
||||
|
||||
message(STATUS "Build info. Target: ${LIBVPX_TARGET}; Options: ${OPTIONS}")
|
||||
|
||||
if(NOT DEFINED VCPKG_BUILD_TYPE OR VCPKG_BUILD_TYPE STREQUAL "release")
|
||||
@@ -246,7 +251,7 @@ else()
|
||||
message(STATUS "Building libvpx for Release")
|
||||
vcpkg_execute_required_process(
|
||||
COMMAND
|
||||
${BASH} --noprofile --norc -c "make -j${VCPKG_CONCURRENCY}"
|
||||
${BASH} --noprofile --norc -c "${MAKE_BINARY} -j${VCPKG_CONCURRENCY}"
|
||||
WORKING_DIRECTORY "${CURRENT_BUILDTREES_DIR}/${TARGET_TRIPLET}-rel"
|
||||
LOGNAME build-${TARGET_TRIPLET}-rel
|
||||
)
|
||||
@@ -254,7 +259,7 @@ else()
|
||||
message(STATUS "Installing libvpx for Release")
|
||||
vcpkg_execute_required_process(
|
||||
COMMAND
|
||||
${BASH} --noprofile --norc -c "make install"
|
||||
${BASH} --noprofile --norc -c "${MAKE_BINARY} install"
|
||||
WORKING_DIRECTORY "${CURRENT_BUILDTREES_DIR}/${TARGET_TRIPLET}-rel"
|
||||
LOGNAME install-${TARGET_TRIPLET}-rel
|
||||
)
|
||||
@@ -280,7 +285,7 @@ else()
|
||||
message(STATUS "Building libvpx for Debug")
|
||||
vcpkg_execute_required_process(
|
||||
COMMAND
|
||||
${BASH} --noprofile --norc -c "make -j${VCPKG_CONCURRENCY}"
|
||||
${BASH} --noprofile --norc -c "${MAKE_BINARY} -j${VCPKG_CONCURRENCY}"
|
||||
WORKING_DIRECTORY "${CURRENT_BUILDTREES_DIR}/${TARGET_TRIPLET}-dbg"
|
||||
LOGNAME build-${TARGET_TRIPLET}-dbg
|
||||
)
|
||||
@@ -288,7 +293,7 @@ else()
|
||||
message(STATUS "Installing libvpx for Debug")
|
||||
vcpkg_execute_required_process(
|
||||
COMMAND
|
||||
${BASH} --noprofile --norc -c "make install"
|
||||
${BASH} --noprofile --norc -c "${MAKE_BINARY} install"
|
||||
WORKING_DIRECTORY "${CURRENT_BUILDTREES_DIR}/${TARGET_TRIPLET}-dbg"
|
||||
LOGNAME install-${TARGET_TRIPLET}-dbg
|
||||
)
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
{
|
||||
"name": "libvpx",
|
||||
"version": "1.15.0",
|
||||
"port-version": 0,
|
||||
"version": "1.15.2",
|
||||
"description": "The reference software implementation for the video coding formats VP8 and VP9.",
|
||||
"homepage": "https://github.com/webmproject/libvpx",
|
||||
"license": "BSD-3-Clause",
|
||||
|
||||
210
src/client.rs
210
src/client.rs
@@ -192,13 +192,19 @@ impl Client {
|
||||
conn_type: ConnType,
|
||||
interface: impl Interface,
|
||||
) -> ResultType<(
|
||||
(Stream, bool, Option<Vec<u8>>, Option<KcpStream>),
|
||||
(
|
||||
Stream,
|
||||
bool,
|
||||
Option<Vec<u8>>,
|
||||
Option<KcpStream>,
|
||||
&'static str,
|
||||
),
|
||||
(i32, String),
|
||||
)> {
|
||||
debug_assert!(peer == interface.get_id());
|
||||
interface.update_direct(None);
|
||||
interface.update_received(false);
|
||||
match Self::_start(peer, key, token, conn_type, interface).await {
|
||||
match Self::_start(peer, key, token, conn_type, interface.clone()).await {
|
||||
Err(err) => {
|
||||
let err_str = err.to_string();
|
||||
if err_str.starts_with("Failed") {
|
||||
@@ -207,7 +213,19 @@ impl Client {
|
||||
return Err(err);
|
||||
}
|
||||
}
|
||||
Ok(x) => Ok(x),
|
||||
Ok(x) => {
|
||||
// Set x.2 to true only in the connect() function to indicate that direct_failures needs to be updated; everywhere else it should be set to false.
|
||||
if x.2 {
|
||||
let direct_failures = interface.get_lch().read().unwrap().direct_failures;
|
||||
let direct = x.0 .1;
|
||||
if !interface.is_force_relay() && (direct_failures == 0) != direct {
|
||||
let n = if direct { 0 } else { 1 };
|
||||
log::info!("direct_failures updated to {}", n);
|
||||
interface.get_lch().write().unwrap().set_direct_failure(n);
|
||||
}
|
||||
}
|
||||
Ok((x.0, x.1))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -219,8 +237,15 @@ impl Client {
|
||||
conn_type: ConnType,
|
||||
interface: impl Interface,
|
||||
) -> ResultType<(
|
||||
(Stream, bool, Option<Vec<u8>>, Option<KcpStream>),
|
||||
(
|
||||
Stream,
|
||||
bool,
|
||||
Option<Vec<u8>>,
|
||||
Option<KcpStream>,
|
||||
&'static str,
|
||||
),
|
||||
(i32, String),
|
||||
bool,
|
||||
)> {
|
||||
if config::is_incoming_only() {
|
||||
bail!("Incoming only mode");
|
||||
@@ -234,8 +259,10 @@ impl Client {
|
||||
true,
|
||||
None,
|
||||
None,
|
||||
"TCP",
|
||||
),
|
||||
(0, "".to_owned()),
|
||||
false,
|
||||
));
|
||||
}
|
||||
// Allow connect to {domain}:{port}
|
||||
@@ -246,8 +273,10 @@ impl Client {
|
||||
true,
|
||||
None,
|
||||
None,
|
||||
"TCP",
|
||||
),
|
||||
(0, "".to_owned()),
|
||||
false,
|
||||
));
|
||||
}
|
||||
|
||||
@@ -257,7 +286,7 @@ impl Client {
|
||||
} else {
|
||||
(peer, "", key, token)
|
||||
};
|
||||
let (mut rendezvous_server, servers, contained) = if other_server.is_empty() {
|
||||
let (rendezvous_server, servers, contained) = if other_server.is_empty() {
|
||||
crate::get_rendezvous_server(1_000).await
|
||||
} else {
|
||||
if other_server == PUBLIC_SERVER {
|
||||
@@ -279,10 +308,10 @@ impl Client {
|
||||
}
|
||||
|
||||
let (stop_udp_tx, stop_udp_rx) = oneshot::channel::<()>();
|
||||
let mut udp =
|
||||
let udp =
|
||||
// no need to care about multiple rendezvous servers case, since it is acutally not used any more.
|
||||
// Shared state for UDP NAT test result
|
||||
if crate::get_udp_punch_enabled() {
|
||||
if crate::get_udp_punch_enabled() && !interface.is_force_relay() {
|
||||
if let Ok((socket, addr)) = new_direct_udp_for(&rendezvous_server).await {
|
||||
let udp_port = Arc::new(Mutex::new(0));
|
||||
let up_cloned = udp_port.clone();
|
||||
@@ -298,6 +327,64 @@ impl Client {
|
||||
} else {
|
||||
(None, None)
|
||||
};
|
||||
let fut = Self::_start_inner(
|
||||
peer.to_owned(),
|
||||
key.to_owned(),
|
||||
token.to_owned(),
|
||||
conn_type,
|
||||
interface.clone(),
|
||||
udp.clone(),
|
||||
Some(stop_udp_tx),
|
||||
rendezvous_server.clone(),
|
||||
servers.clone(),
|
||||
contained,
|
||||
);
|
||||
if udp.0.is_none() {
|
||||
return fut.await;
|
||||
}
|
||||
let mut connect_futures = Vec::new();
|
||||
connect_futures.push(fut.boxed());
|
||||
let fut = Self::_start_inner(
|
||||
peer.to_owned(),
|
||||
key.to_owned(),
|
||||
token.to_owned(),
|
||||
conn_type,
|
||||
interface,
|
||||
(None, None),
|
||||
None,
|
||||
rendezvous_server,
|
||||
servers,
|
||||
contained,
|
||||
);
|
||||
connect_futures.push(fut.boxed());
|
||||
match select_ok(connect_futures).await {
|
||||
Ok(conn) => Ok((conn.0 .0, conn.0 .1, conn.0 .2)),
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
|
||||
async fn _start_inner(
|
||||
peer: String,
|
||||
key: String,
|
||||
token: String,
|
||||
conn_type: ConnType,
|
||||
interface: impl Interface,
|
||||
mut udp: (Option<Arc<UdpSocket>>, Option<Arc<Mutex<u16>>>),
|
||||
stop_udp_tx: Option<oneshot::Sender<()>>,
|
||||
mut rendezvous_server: String,
|
||||
servers: Vec<String>,
|
||||
contained: bool,
|
||||
) -> ResultType<(
|
||||
(
|
||||
Stream,
|
||||
bool,
|
||||
Option<Vec<u8>>,
|
||||
Option<KcpStream>,
|
||||
&'static str,
|
||||
),
|
||||
(i32, String),
|
||||
bool,
|
||||
)> {
|
||||
let mut start = Instant::now();
|
||||
let mut socket = connect_tcp(&*rendezvous_server, CONNECT_TIMEOUT).await;
|
||||
debug_assert!(!servers.contains(&rendezvous_server));
|
||||
@@ -327,9 +414,8 @@ impl Client {
|
||||
let my_nat_type = crate::get_nat_type(100).await;
|
||||
let mut is_local = false;
|
||||
let mut feedback = 0;
|
||||
let force_relay = interface.is_force_relay() || use_ws() || Config::is_proxy();
|
||||
use hbb_common::protobuf::Enum;
|
||||
let nat_type = if force_relay {
|
||||
let nat_type = if interface.is_force_relay() {
|
||||
NatType::SYMMETRIC
|
||||
} else {
|
||||
NatType::from_i32(my_nat_type).unwrap_or(NatType::UNKNOWN_NAT)
|
||||
@@ -337,7 +423,7 @@ impl Client {
|
||||
|
||||
if !key.is_empty() && !token.is_empty() {
|
||||
// mainly for the security of token
|
||||
secure_tcp(&mut socket, key)
|
||||
secure_tcp(&mut socket, &key)
|
||||
.await
|
||||
.map_err(|e| anyhow!("Failed to secure tcp: {}", e))?;
|
||||
} else if let Some(udp) = udp.1.as_ref() {
|
||||
@@ -355,7 +441,7 @@ impl Client {
|
||||
}
|
||||
}
|
||||
// Stop UDP NAT test task if still running
|
||||
let _ = stop_udp_tx.send(());
|
||||
stop_udp_tx.map(|tx| tx.send(()));
|
||||
let mut msg_out = RendezvousMessage::new();
|
||||
let mut ipv6 = if crate::get_ipv6_punch_enabled() {
|
||||
if let Some((socket, addr)) = crate::get_ipv6_socket().await {
|
||||
@@ -367,6 +453,7 @@ impl Client {
|
||||
(None, None)
|
||||
};
|
||||
let udp_nat_port = udp.1.map(|x| *x.lock().unwrap()).unwrap_or(0);
|
||||
let punch_type = if udp_nat_port > 0 { "UDP" } else { "TCP" };
|
||||
msg_out.set_punch_hole_request(PunchHoleRequest {
|
||||
id: peer.to_owned(),
|
||||
token: token.to_owned(),
|
||||
@@ -375,12 +462,18 @@ impl Client {
|
||||
conn_type: conn_type.into(),
|
||||
version: crate::VERSION.to_owned(),
|
||||
udp_port: udp_nat_port as _,
|
||||
force_relay,
|
||||
force_relay: interface.is_force_relay(),
|
||||
socket_addr_v6: ipv6.1.unwrap_or_default(),
|
||||
..Default::default()
|
||||
});
|
||||
for i in 1..=3 {
|
||||
log::info!("#{} punch attempt with {}, id: {}", i, my_addr, peer);
|
||||
log::info!(
|
||||
"#{} {} punch attempt with {}, id: {}",
|
||||
i,
|
||||
punch_type,
|
||||
my_addr,
|
||||
peer
|
||||
);
|
||||
socket.send(&msg_out).await?;
|
||||
// below timeout should not bigger than hbbs's connection timeout.
|
||||
if let Some(msg_in) =
|
||||
@@ -431,7 +524,7 @@ impl Client {
|
||||
}
|
||||
}
|
||||
}
|
||||
log::info!("Hole Punched {} = {}", peer, peer_addr);
|
||||
log::info!("{} Hole Punched {} = {}", punch_type, peer, peer_addr);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -454,17 +547,17 @@ impl Client {
|
||||
}
|
||||
signed_id_pk = rr.pk().into();
|
||||
let fut = Self::create_relay(
|
||||
peer,
|
||||
&peer,
|
||||
rr.uuid,
|
||||
rr.relay_server,
|
||||
key,
|
||||
&key,
|
||||
conn_type,
|
||||
my_addr.is_ipv4(),
|
||||
);
|
||||
connect_futures.push(
|
||||
async move {
|
||||
let conn = fut.await?;
|
||||
Ok((conn, None, "Relay"))
|
||||
Ok((conn, None, if use_ws() { "WebSocket" } else { "Relay" }))
|
||||
}
|
||||
.boxed(),
|
||||
);
|
||||
@@ -478,8 +571,12 @@ impl Client {
|
||||
feedback = rr.feedback;
|
||||
log::info!("{:?} used to establish {typ} connection", start.elapsed());
|
||||
let pk =
|
||||
Self::secure_connection(peer, signed_id_pk, key, &mut conn).await?;
|
||||
return Ok(((conn, false, pk, kcp), (feedback, rendezvous_server)));
|
||||
Self::secure_connection(&peer, signed_id_pk, &key, &mut conn).await?;
|
||||
return Ok((
|
||||
(conn, typ == "IPv6", pk, kcp, typ),
|
||||
(feedback, rendezvous_server),
|
||||
false,
|
||||
));
|
||||
}
|
||||
_ => {
|
||||
log::error!("Unexpected protobuf msg received: {:?}", msg_in);
|
||||
@@ -493,8 +590,9 @@ impl Client {
|
||||
}
|
||||
let time_used = start.elapsed().as_millis() as u64;
|
||||
log::info!(
|
||||
"{} ms used to punch hole, relay_server: {}, {}",
|
||||
"{} ms used to {} punch hole, relay_server: {}, {}",
|
||||
time_used,
|
||||
punch_type,
|
||||
relay_server,
|
||||
if is_local {
|
||||
"is_local: true".to_owned()
|
||||
@@ -506,7 +604,7 @@ impl Client {
|
||||
Self::connect(
|
||||
my_addr,
|
||||
peer_addr,
|
||||
peer,
|
||||
&peer,
|
||||
signed_id_pk,
|
||||
&relay_server,
|
||||
&rendezvous_server,
|
||||
@@ -514,15 +612,17 @@ impl Client {
|
||||
peer_nat_type,
|
||||
my_nat_type,
|
||||
is_local,
|
||||
key,
|
||||
token,
|
||||
&key,
|
||||
&token,
|
||||
conn_type,
|
||||
interface,
|
||||
udp.0,
|
||||
ipv6.0,
|
||||
punch_type,
|
||||
)
|
||||
.await?,
|
||||
(feedback, rendezvous_server),
|
||||
true,
|
||||
))
|
||||
}
|
||||
|
||||
@@ -544,7 +644,14 @@ impl Client {
|
||||
interface: impl Interface,
|
||||
udp_socket_nat: Option<Arc<UdpSocket>>,
|
||||
udp_socket_v6: Option<Arc<UdpSocket>>,
|
||||
) -> ResultType<(Stream, bool, Option<Vec<u8>>, Option<KcpStream>)> {
|
||||
punch_type: &str,
|
||||
) -> ResultType<(
|
||||
Stream,
|
||||
bool,
|
||||
Option<Vec<u8>>,
|
||||
Option<KcpStream>,
|
||||
&'static str,
|
||||
)> {
|
||||
let direct_failures = interface.get_lch().read().unwrap().direct_failures;
|
||||
let mut connect_timeout = 0;
|
||||
const MIN: u64 = 1000;
|
||||
@@ -602,7 +709,6 @@ impl Client {
|
||||
};
|
||||
|
||||
let mut direct = !conn.is_err();
|
||||
interface.update_direct(Some(direct));
|
||||
if interface.is_force_relay() || conn.is_err() {
|
||||
if !relay_server.is_empty() {
|
||||
conn = Self::request_relay(
|
||||
@@ -615,8 +721,9 @@ impl Client {
|
||||
conn_type,
|
||||
)
|
||||
.await;
|
||||
interface.update_direct(Some(false));
|
||||
if let Err(e) = conn {
|
||||
// this direct is mainly used by on_establish_connection_error, so we update it here before bail
|
||||
interface.update_direct(Some(false));
|
||||
bail!("Failed to connect via relay server: {}", e);
|
||||
}
|
||||
typ = "Relay";
|
||||
@@ -625,15 +732,23 @@ impl Client {
|
||||
bail!("Failed to make direct connection to remote desktop");
|
||||
}
|
||||
}
|
||||
if !relay_server.is_empty() && (direct_failures == 0) != direct {
|
||||
let n = if direct { 0 } else { 1 };
|
||||
log::info!("direct_failures updated to {}", n);
|
||||
interface.get_lch().write().unwrap().set_direct_failure(n);
|
||||
}
|
||||
let mut conn = conn?;
|
||||
log::info!("{:?} used to establish {typ} connection", start.elapsed());
|
||||
let pk = Self::secure_connection(peer_id, signed_id_pk, key, &mut conn).await?;
|
||||
Ok((conn, direct, pk, kcp))
|
||||
log::info!(
|
||||
"{:?} used to establish {typ} connection with {} punch",
|
||||
start.elapsed(),
|
||||
punch_type
|
||||
);
|
||||
let res = Self::secure_connection(peer_id, signed_id_pk, key, &mut conn).await;
|
||||
let pk: Option<Vec<u8>> = match res {
|
||||
Ok(pk) => pk,
|
||||
Err(e) => {
|
||||
// this direct is mainly used by on_establish_connection_error, so we update it here before bail
|
||||
interface.update_direct(Some(direct));
|
||||
bail!(e);
|
||||
}
|
||||
};
|
||||
log::debug!("{} punch secure_connection ok", punch_type);
|
||||
Ok((conn, direct, pk, kcp, typ))
|
||||
}
|
||||
|
||||
/// Establish secure connection with the server.
|
||||
@@ -1731,7 +1846,9 @@ impl LoginConfigHandler {
|
||||
self.restarting_remote_device = false;
|
||||
self.force_relay =
|
||||
config::option2bool("force-always-relay", &self.get_option("force-always-relay"))
|
||||
|| force_relay;
|
||||
|| force_relay
|
||||
|| use_ws()
|
||||
|| Config::is_proxy();
|
||||
if let Some((real_id, server, key)) = &self.other_server {
|
||||
let other_server_key = self.get_option("other-server-key");
|
||||
if !other_server_key.is_empty() && key.is_empty() {
|
||||
@@ -2015,7 +2132,19 @@ impl LoginConfigHandler {
|
||||
option.show_remote_cursor = f(self.get_toggle_option("show-remote-cursor"));
|
||||
option.enable_file_transfer = f(self.config.enable_file_copy_paste.v);
|
||||
option.lock_after_session_end = f(self.config.lock_after_session_end.v);
|
||||
if config.show_my_cursor.v {
|
||||
config.show_my_cursor.v = false;
|
||||
option.show_my_cursor = BoolOption::No.into();
|
||||
}
|
||||
}
|
||||
} else if name == "show-my-cursor" {
|
||||
config.show_my_cursor.v = !config.show_my_cursor.v;
|
||||
option.show_my_cursor = if config.show_my_cursor.v {
|
||||
BoolOption::Yes
|
||||
} else {
|
||||
BoolOption::No
|
||||
}
|
||||
.into();
|
||||
} else {
|
||||
let is_set = self
|
||||
.options
|
||||
@@ -2108,6 +2237,9 @@ impl LoginConfigHandler {
|
||||
if view_only || self.get_toggle_option("show-remote-cursor") {
|
||||
msg.show_remote_cursor = BoolOption::Yes.into();
|
||||
}
|
||||
if view_only && self.get_toggle_option("show-my-cursor") {
|
||||
msg.show_my_cursor = BoolOption::Yes.into();
|
||||
}
|
||||
if self.get_toggle_option("follow-remote-cursor") {
|
||||
msg.follow_remote_cursor = BoolOption::Yes.into();
|
||||
}
|
||||
@@ -2192,6 +2324,8 @@ impl LoginConfigHandler {
|
||||
self.config.allow_swap_key.v
|
||||
} else if name == "view-only" {
|
||||
self.config.view_only.v
|
||||
} else if name == "show-my-cursor" {
|
||||
self.config.show_my_cursor.v
|
||||
} else if name == "follow-remote-cursor" {
|
||||
self.config.follow_remote_cursor.v
|
||||
} else if name == "follow-remote-window" {
|
||||
@@ -3695,7 +3829,11 @@ pub fn check_if_retry(msgtype: &str, title: &str, text: &str, retry_for_relay: b
|
||||
&& ((text.contains("10054") || text.contains("104")) && retry_for_relay
|
||||
|| (!text.to_lowercase().contains("offline")
|
||||
&& !text.to_lowercase().contains("not exist")
|
||||
&& !text.to_lowercase().contains("handshake")
|
||||
&& (!text.to_lowercase().contains("handshake")
|
||||
// https://github.com/snapview/tungstenite-rs/blob/e7e060a89a72cb08e31c25a6c7284dc1bd982e23/src/error.rs#L248
|
||||
|| text
|
||||
.to_lowercase()
|
||||
.contains("connection reset without closing handshake") && use_ws())
|
||||
&& !text.to_lowercase().contains("failed")
|
||||
&& !text.to_lowercase().contains("resolve")
|
||||
&& !text.to_lowercase().contains("mismatch")
|
||||
|
||||
@@ -3,14 +3,32 @@ use hbb_common::{fs, log, message_proto::*};
|
||||
use super::{Data, Interface};
|
||||
|
||||
pub trait FileManager: Interface {
|
||||
#[cfg(not(any(
|
||||
target_os = "android",
|
||||
target_os = "ios",
|
||||
feature = "cli",
|
||||
feature = "flutter"
|
||||
)))]
|
||||
fn get_home_dir(&self) -> String {
|
||||
fs::get_home_as_string()
|
||||
}
|
||||
|
||||
#[cfg(not(any(
|
||||
target_os = "android",
|
||||
target_os = "ios",
|
||||
feature = "cli",
|
||||
feature = "flutter"
|
||||
)))]
|
||||
fn get_next_job_id(&self) -> i32 {
|
||||
fs::get_next_job_id()
|
||||
}
|
||||
|
||||
#[cfg(not(any(
|
||||
target_os = "android",
|
||||
target_os = "ios",
|
||||
feature = "cli",
|
||||
feature = "flutter"
|
||||
)))]
|
||||
fn update_next_job_id(&self, id: i32) {
|
||||
fs::update_next_job_id(id);
|
||||
}
|
||||
@@ -33,20 +51,6 @@ pub trait FileManager: Interface {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(
|
||||
target_os = "android",
|
||||
target_os = "ios",
|
||||
feature = "cli",
|
||||
feature = "flutter"
|
||||
))]
|
||||
fn read_dir(&self, path: &str, include_hidden: bool) -> String {
|
||||
use crate::common::make_fd_to_json;
|
||||
match fs::read_dir(&fs::get_path(path), include_hidden) {
|
||||
Ok(fd) => make_fd_to_json(fd.id, fd.path, &fd.entries),
|
||||
Err(_) => "".into(),
|
||||
}
|
||||
}
|
||||
|
||||
fn cancel_job(&self, id: i32) {
|
||||
self.send(Data::CancelJob(id));
|
||||
}
|
||||
@@ -83,10 +87,22 @@ pub trait FileManager: Interface {
|
||||
self.send(Data::RemoveDirAll((id, path, is_remote, include_hidden)));
|
||||
}
|
||||
|
||||
#[cfg(not(any(
|
||||
target_os = "android",
|
||||
target_os = "ios",
|
||||
feature = "cli",
|
||||
feature = "flutter"
|
||||
)))]
|
||||
fn confirm_delete_files(&self, id: i32, file_num: i32) {
|
||||
self.send(Data::ConfirmDeleteFiles((id, file_num)));
|
||||
}
|
||||
|
||||
#[cfg(not(any(
|
||||
target_os = "android",
|
||||
target_os = "ios",
|
||||
feature = "cli",
|
||||
feature = "flutter"
|
||||
)))]
|
||||
fn set_no_confirm(&self, id: i32) {
|
||||
self.send(Data::SetNoConfirm(id));
|
||||
}
|
||||
|
||||
@@ -174,13 +174,14 @@ impl<T: InvokeUiSession> Remote<T> {
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(((mut peer, direct, pk, kcp), (feedback, rendezvous_server))) => {
|
||||
Ok(((mut peer, direct, pk, kcp, stream_type), (feedback, rendezvous_server))) => {
|
||||
self.handler
|
||||
.connection_round_state
|
||||
.lock()
|
||||
.unwrap()
|
||||
.set_connected();
|
||||
self.handler.set_connection_type(peer.is_secured(), direct); // flutter -> connection_ready
|
||||
self.handler
|
||||
.set_connection_type(peer.is_secured(), direct, stream_type); // flutter -> connection_ready
|
||||
self.handler.update_direct(Some(direct));
|
||||
if conn_type == ConnType::DEFAULT_CONN || conn_type == ConnType::VIEW_CAMERA {
|
||||
self.handler
|
||||
@@ -703,6 +704,7 @@ impl<T: InvokeUiSession> Remote<T> {
|
||||
if is_remote {
|
||||
if let Some(job) = get_job(id, &mut self.write_jobs) {
|
||||
job.is_last_job = false;
|
||||
job.is_resume = true;
|
||||
allow_err!(
|
||||
peer.send(&fs::new_send(
|
||||
id,
|
||||
@@ -717,14 +719,25 @@ impl<T: InvokeUiSession> Remote<T> {
|
||||
} else {
|
||||
if let Some(job) = get_job(id, &mut self.read_jobs) {
|
||||
match &job.data_source {
|
||||
fs::DataSource::FilePath(p) => {
|
||||
fs::DataSource::FilePath(_p) => {
|
||||
job.is_last_job = false;
|
||||
job.is_resume = true;
|
||||
job.set_finished_size_on_resume();
|
||||
#[cfg(not(windows))]
|
||||
let files = job.files().clone();
|
||||
#[cfg(windows)]
|
||||
let mut files = job.files().clone();
|
||||
#[cfg(windows)]
|
||||
if self.handler.peer_platform() != "Windows" {
|
||||
// peer is not windows, need transform \ to /
|
||||
fs::transform_windows_path(&mut files);
|
||||
}
|
||||
allow_err!(
|
||||
peer.send(&fs::new_receive(
|
||||
id,
|
||||
p.to_string_lossy().to_string(),
|
||||
job.remote.clone(),
|
||||
job.file_num,
|
||||
job.files.clone(),
|
||||
files,
|
||||
job.total_size(),
|
||||
))
|
||||
.await
|
||||
@@ -770,7 +783,8 @@ impl<T: InvokeUiSession> Remote<T> {
|
||||
Some(file_transfer_send_confirm_request::Union::Skip(true))
|
||||
},
|
||||
..Default::default()
|
||||
});
|
||||
})
|
||||
.await;
|
||||
}
|
||||
} else {
|
||||
if let Some(job) = fs::get_job(id, &mut self.write_jobs) {
|
||||
@@ -789,7 +803,7 @@ impl<T: InvokeUiSession> Remote<T> {
|
||||
},
|
||||
..Default::default()
|
||||
};
|
||||
job.confirm(&req);
|
||||
job.confirm(&req).await;
|
||||
file_action.set_send_confirm(req);
|
||||
msg.set_file_action(file_action);
|
||||
allow_err!(peer.send(&msg).await);
|
||||
@@ -1459,6 +1473,7 @@ impl<T: InvokeUiSession> Remote<T> {
|
||||
if let Some(job) = fs::get_job(fd.id, &mut self.write_jobs) {
|
||||
log::info!("job set_files: {:?}", entries);
|
||||
job.set_files(entries);
|
||||
job.set_finished_size_on_resume();
|
||||
} else if let Some(job) = self.remove_jobs.get_mut(&fd.id) {
|
||||
job.files = entries;
|
||||
}
|
||||
@@ -1470,14 +1485,21 @@ impl<T: InvokeUiSession> Remote<T> {
|
||||
if let fs::DataSource::FilePath(p) = &job.data_source {
|
||||
let read_path =
|
||||
get_string(&fs::TransferJob::join(p, &file.name));
|
||||
let overwrite_strategy =
|
||||
let mut overwrite_strategy =
|
||||
job.default_overwrite_strategy();
|
||||
let mut offset = 0;
|
||||
if digest.is_identical && job.is_resume {
|
||||
if digest.transferred_size > 0 {
|
||||
overwrite_strategy = Some(true);
|
||||
offset = digest.transferred_size as _;
|
||||
}
|
||||
}
|
||||
if let Some(overwrite) = overwrite_strategy {
|
||||
let req = FileTransferSendConfirmRequest {
|
||||
id: digest.id,
|
||||
file_num: digest.file_num,
|
||||
union: Some(if overwrite {
|
||||
file_transfer_send_confirm_request::Union::OffsetBlk(0)
|
||||
file_transfer_send_confirm_request::Union::OffsetBlk(offset)
|
||||
} else {
|
||||
file_transfer_send_confirm_request::Union::Skip(
|
||||
true,
|
||||
@@ -1485,7 +1507,7 @@ impl<T: InvokeUiSession> Remote<T> {
|
||||
}),
|
||||
..Default::default()
|
||||
};
|
||||
job.confirm(&req);
|
||||
job.confirm(&req).await;
|
||||
let msg = new_send_confirm(req);
|
||||
allow_err!(peer.send(&msg).await);
|
||||
} else {
|
||||
@@ -1506,25 +1528,40 @@ impl<T: InvokeUiSession> Remote<T> {
|
||||
if let fs::DataSource::FilePath(p) = &job.data_source {
|
||||
let write_path =
|
||||
get_string(&fs::TransferJob::join(p, &file.name));
|
||||
let overwrite_strategy =
|
||||
job.default_overwrite_strategy();
|
||||
job.set_digest(digest.file_size, digest.last_modified);
|
||||
let peer_ver = self.handler.lc.read().unwrap().version;
|
||||
let is_support_resume =
|
||||
crate::is_support_file_transfer_resume_num(
|
||||
peer_ver,
|
||||
);
|
||||
match fs::is_write_need_confirmation(
|
||||
is_support_resume && job.is_resume,
|
||||
&write_path,
|
||||
&digest,
|
||||
) {
|
||||
Ok(res) => match res {
|
||||
DigestCheckResult::IsSame => {
|
||||
let req = FileTransferSendConfirmRequest {
|
||||
id: digest.id,
|
||||
file_num: digest.file_num,
|
||||
union: Some(file_transfer_send_confirm_request::Union::Skip(true)),
|
||||
..Default::default()
|
||||
};
|
||||
job.confirm(&req);
|
||||
id: digest.id,
|
||||
file_num: digest.file_num,
|
||||
union: Some(file_transfer_send_confirm_request::Union::Skip(true)),
|
||||
..Default::default()
|
||||
};
|
||||
job.confirm(&req).await;
|
||||
let msg = new_send_confirm(req);
|
||||
allow_err!(peer.send(&msg).await);
|
||||
}
|
||||
DigestCheckResult::NeedConfirm(digest) => {
|
||||
let mut overwrite_strategy =
|
||||
job.default_overwrite_strategy();
|
||||
let mut offset = 0;
|
||||
if digest.is_identical
|
||||
&& job.is_resume
|
||||
&& digest.transferred_size > 0
|
||||
{
|
||||
overwrite_strategy = Some(true);
|
||||
offset = digest.transferred_size as _;
|
||||
}
|
||||
if let Some(overwrite) = overwrite_strategy
|
||||
{
|
||||
let req =
|
||||
@@ -1532,13 +1569,13 @@ impl<T: InvokeUiSession> Remote<T> {
|
||||
id: digest.id,
|
||||
file_num: digest.file_num,
|
||||
union: Some(if overwrite {
|
||||
file_transfer_send_confirm_request::Union::OffsetBlk(0)
|
||||
file_transfer_send_confirm_request::Union::OffsetBlk(offset)
|
||||
} else {
|
||||
file_transfer_send_confirm_request::Union::Skip(true)
|
||||
}),
|
||||
..Default::default()
|
||||
};
|
||||
job.confirm(&req);
|
||||
job.confirm(&req).await;
|
||||
let msg = new_send_confirm(req);
|
||||
allow_err!(peer.send(&msg).await);
|
||||
} else {
|
||||
@@ -1558,7 +1595,7 @@ impl<T: InvokeUiSession> Remote<T> {
|
||||
union: Some(file_transfer_send_confirm_request::Union::OffsetBlk(0)),
|
||||
..Default::default()
|
||||
};
|
||||
job.confirm(&req);
|
||||
job.confirm(&req).await;
|
||||
let msg = new_send_confirm(req);
|
||||
allow_err!(peer.send(&msg).await);
|
||||
}
|
||||
@@ -1905,7 +1942,7 @@ impl<T: InvokeUiSession> Remote<T> {
|
||||
},
|
||||
Some(file_action::Union::SendConfirm(c)) => {
|
||||
if let Some(job) = fs::get_job(c.id, &mut self.read_jobs) {
|
||||
job.confirm(&c);
|
||||
job.confirm(&c).await;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
@@ -2231,7 +2268,7 @@ impl<T: InvokeUiSession> Remote<T> {
|
||||
}
|
||||
#[cfg(feature = "unix-file-copy-paste")]
|
||||
if crate::is_support_file_copy_paste_num(self.handler.lc.read().unwrap().version) {
|
||||
let mut out_msg = None;
|
||||
let mut out_msgs = vec![];
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
if clipboard::platform::unix::macos::should_handle_msg(&clip) {
|
||||
@@ -2243,7 +2280,7 @@ impl<T: InvokeUiSession> Remote<T> {
|
||||
log::error!("failed to handle cliprdr msg: {}", e);
|
||||
}
|
||||
} else {
|
||||
out_msg = unix_file_clip::serve_clip_messages(
|
||||
out_msgs = unix_file_clip::serve_clip_messages(
|
||||
ClipboardSide::Client,
|
||||
clip,
|
||||
self.client_conn_id,
|
||||
@@ -2252,14 +2289,14 @@ impl<T: InvokeUiSession> Remote<T> {
|
||||
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
{
|
||||
out_msg = unix_file_clip::serve_clip_messages(
|
||||
out_msgs = unix_file_clip::serve_clip_messages(
|
||||
ClipboardSide::Client,
|
||||
clip,
|
||||
self.client_conn_id,
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(msg) = out_msg {
|
||||
for msg in out_msgs.into_iter() {
|
||||
allow_err!(_peer.send(&msg).await);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -117,7 +117,7 @@ pub fn check_clipboard_files(
|
||||
None
|
||||
}
|
||||
|
||||
#[cfg(feature = "unix-file-copy-paste")]
|
||||
#[cfg(all(target_os = "linux", feature = "unix-file-copy-paste"))]
|
||||
pub fn update_clipboard_files(files: Vec<String>, side: ClipboardSide) {
|
||||
if !files.is_empty() {
|
||||
std::thread::spawn(move || {
|
||||
@@ -141,6 +141,7 @@ pub fn try_empty_clipboard_files(_side: ClipboardSide, _conn_id: i32) {
|
||||
}
|
||||
}
|
||||
}
|
||||
#[allow(unused_mut)]
|
||||
if let Some(mut ctx) = ctx.as_mut() {
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
|
||||
@@ -143,6 +143,40 @@ pub fn clip_2_msg(clip: ClipboardFile) -> Message {
|
||||
})),
|
||||
..Default::default()
|
||||
},
|
||||
ClipboardFile::Files { files } => {
|
||||
let files = files
|
||||
.iter()
|
||||
.filter_map(|(f, s)| {
|
||||
if *s == 0 {
|
||||
if let Ok(meta) = std::fs::metadata(f) {
|
||||
Some(CliprdrFile {
|
||||
name: f.to_owned(),
|
||||
size: meta.len(),
|
||||
..Default::default()
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
Some(CliprdrFile {
|
||||
name: f.to_owned(),
|
||||
size: *s,
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
Message {
|
||||
union: Some(message::Union::Cliprdr(Cliprdr {
|
||||
union: Some(cliprdr::Union::Files(CliprdrFiles {
|
||||
files,
|
||||
..Default::default()
|
||||
})),
|
||||
..Default::default()
|
||||
})),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -192,12 +226,10 @@ pub fn msg_2_clip(msg: Cliprdr) -> Option<ClipboardFile> {
|
||||
|
||||
#[cfg(feature = "unix-file-copy-paste")]
|
||||
pub mod unix_file_clip {
|
||||
use crate::clipboard::try_empty_clipboard_files;
|
||||
|
||||
use super::{
|
||||
super::clipboard::{update_clipboard_files, ClipboardSide},
|
||||
*,
|
||||
};
|
||||
use super::*;
|
||||
#[cfg(target_os = "linux")]
|
||||
use crate::clipboard::update_clipboard_files;
|
||||
use crate::clipboard::{try_empty_clipboard_files, ClipboardSide};
|
||||
#[cfg(target_os = "linux")]
|
||||
use clipboard::platform::unix::fuse;
|
||||
use clipboard::platform::unix::{
|
||||
@@ -245,7 +277,7 @@ pub mod unix_file_clip {
|
||||
side: ClipboardSide,
|
||||
clip: ClipboardFile,
|
||||
conn_id: i32,
|
||||
) -> Option<Message> {
|
||||
) -> Vec<Message> {
|
||||
log::debug!("got clipfile from client peer");
|
||||
match clip {
|
||||
ClipboardFile::MonitorReady => {
|
||||
@@ -259,7 +291,7 @@ pub mod unix_file_clip {
|
||||
.is_some()
|
||||
{
|
||||
log::error!("no file contents format found");
|
||||
return None;
|
||||
return vec![];
|
||||
};
|
||||
let Some(file_descriptor_id) = format_list
|
||||
.iter()
|
||||
@@ -267,13 +299,13 @@ pub mod unix_file_clip {
|
||||
.map(|(id, _)| *id)
|
||||
else {
|
||||
log::error!("no file descriptor format found");
|
||||
return None;
|
||||
return vec![];
|
||||
};
|
||||
// sync file system from peer
|
||||
let data = ClipboardFile::FormatDataRequest {
|
||||
requested_format_id: file_descriptor_id,
|
||||
};
|
||||
return Some(clip_2_msg(data));
|
||||
return vec![clip_2_msg(data)];
|
||||
}
|
||||
ClipboardFile::FormatListResponse {
|
||||
msg_flags: _msg_flags,
|
||||
@@ -284,13 +316,13 @@ pub mod unix_file_clip {
|
||||
log::debug!("requested format id: {}", _requested_format_id);
|
||||
let format_data = serv_files::get_file_list_pdu();
|
||||
if !format_data.is_empty() {
|
||||
return Some(clip_2_msg(ClipboardFile::FormatDataResponse {
|
||||
return vec![clip_2_msg(ClipboardFile::FormatDataResponse {
|
||||
msg_flags: 1,
|
||||
format_data,
|
||||
}));
|
||||
})];
|
||||
}
|
||||
// empty file list, send failure message
|
||||
return Some(msg_resp_format_data_failure());
|
||||
return vec![msg_resp_format_data_failure()];
|
||||
}
|
||||
#[cfg(target_os = "linux")]
|
||||
ClipboardFile::FormatDataResponse {
|
||||
@@ -331,7 +363,7 @@ pub mod unix_file_clip {
|
||||
..
|
||||
} => {
|
||||
log::debug!("file contents request: stream_id: {}, list_index: {}, dw_flags: {}, n_position_low: {}, n_position_high: {}, cb_requested: {}", stream_id, list_index, dw_flags, n_position_low, n_position_high, cb_requested);
|
||||
match serv_files::read_file_contents(
|
||||
return serv_files::read_file_contents(
|
||||
conn_id,
|
||||
stream_id,
|
||||
list_index,
|
||||
@@ -339,15 +371,16 @@ pub mod unix_file_clip {
|
||||
n_position_low,
|
||||
n_position_high,
|
||||
cb_requested,
|
||||
) {
|
||||
Ok(data) => {
|
||||
return Some(clip_2_msg(data));
|
||||
}
|
||||
)
|
||||
.into_iter()
|
||||
.map(|res| match res {
|
||||
Ok(data) => clip_2_msg(data),
|
||||
Err(e) => {
|
||||
log::error!("failed to read file contents: {:?}", e);
|
||||
return Some(resp_file_contents_fail(stream_id));
|
||||
resp_file_contents_fail(stream_id)
|
||||
}
|
||||
}
|
||||
})
|
||||
.collect::<_>();
|
||||
}
|
||||
#[cfg(target_os = "linux")]
|
||||
ClipboardFile::FileContentsResponse {
|
||||
@@ -389,6 +422,6 @@ pub mod unix_file_clip {
|
||||
log::error!("unsupported clipboard file type");
|
||||
}
|
||||
}
|
||||
None
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ use std::{
|
||||
|
||||
use serde_json::{json, Map, Value};
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
#[cfg(not(target_os = "ios"))]
|
||||
use hbb_common::whoami;
|
||||
use hbb_common::{
|
||||
allow_err,
|
||||
@@ -163,6 +163,16 @@ pub fn is_support_screenshot_num(ver: i64) -> bool {
|
||||
ver >= hbb_common::get_version_number("1.4.0")
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_support_file_transfer_resume(ver: &str) -> bool {
|
||||
is_support_file_transfer_resume_num(hbb_common::get_version_number(ver))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_support_file_transfer_resume_num(ver: i64) -> bool {
|
||||
ver >= hbb_common::get_version_number("1.4.2")
|
||||
}
|
||||
|
||||
// is server process, with "--server" args
|
||||
#[inline]
|
||||
pub fn is_server() -> bool {
|
||||
@@ -776,12 +786,22 @@ pub fn username() -> String {
|
||||
return DEVICE_NAME.lock().unwrap().clone();
|
||||
}
|
||||
|
||||
// Exactly the implementation of "whoami::hostname()".
|
||||
// This wrapper is to suppress warnings.
|
||||
#[inline(always)]
|
||||
#[cfg(not(target_os = "ios"))]
|
||||
pub fn whoami_hostname() -> String {
|
||||
let mut hostname = whoami::fallible::hostname().unwrap_or_else(|_| "localhost".to_string());
|
||||
hostname.make_ascii_lowercase();
|
||||
hostname
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn hostname() -> String {
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
{
|
||||
#[allow(unused_mut)]
|
||||
let mut name = whoami::hostname();
|
||||
let mut name = whoami_hostname();
|
||||
// some time, there is .local, some time not, so remove it for osx
|
||||
#[cfg(target_os = "macos")]
|
||||
if name.ends_with(".local") {
|
||||
@@ -1723,7 +1743,7 @@ pub fn is_custom_client() -> bool {
|
||||
get_app_name() != "RustDesk"
|
||||
}
|
||||
|
||||
pub fn verify_login(raw: &str, id: &str) -> bool {
|
||||
pub fn verify_login(_raw: &str, _id: &str) -> bool {
|
||||
true
|
||||
/*
|
||||
if is_custom_client() {
|
||||
@@ -2020,6 +2040,22 @@ pub async fn get_ipv6_socket() -> Option<(Arc<UdpSocket>, bytes::Bytes)> {
|
||||
None
|
||||
}
|
||||
|
||||
// The color is the same to `str2color()` in flutter.
|
||||
pub fn str2color(s: &str, alpha: u8) -> u32 {
|
||||
let bytes = s.as_bytes();
|
||||
// dart code `160 << 16 + 114 << 8 + 91` results `0`.
|
||||
let mut hash: u32 = 0;
|
||||
for &byte in bytes {
|
||||
let code = byte as u32;
|
||||
hash = code.wrapping_add((hash << 5).wrapping_sub(hash));
|
||||
}
|
||||
|
||||
hash = hash % 16777216;
|
||||
let rgb = hash & 0xFF7FFF;
|
||||
|
||||
(alpha as u32) << 24 | rgb
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
@@ -149,7 +149,6 @@ pub fn core_main() -> Option<Vec<String>> {
|
||||
}
|
||||
}
|
||||
hbb_common::init_log(false, &log_name);
|
||||
log::info!("main start args: {:?}, env: {:?}", args, std::env::args());
|
||||
|
||||
// linux uni (url) go here.
|
||||
#[cfg(all(target_os = "linux", feature = "flutter"))]
|
||||
@@ -575,6 +574,12 @@ pub fn core_main() -> Option<Vec<String>> {
|
||||
crate::flutter::connection_manager::start_cm_no_ui();
|
||||
}
|
||||
return None;
|
||||
} else if args[0] == "--whiteboard" {
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
{
|
||||
crate::whiteboard::run();
|
||||
}
|
||||
return None;
|
||||
} else if args[0] == "-gtk-sudo" {
|
||||
// rustdesk service kill `rustdesk --` processes
|
||||
#[cfg(target_os = "linux")]
|
||||
|
||||
@@ -15,11 +15,11 @@ use hbb_common::{
|
||||
};
|
||||
use serde::Serialize;
|
||||
use serde_json::json;
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
use std::io::{Error as IoError, ErrorKind as IoErrorKind};
|
||||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
ffi::CString,
|
||||
io::{Error as IoError, ErrorKind as IoErrorKind},
|
||||
os::raw::{c_char, c_int, c_void},
|
||||
str::FromStr,
|
||||
sync::{
|
||||
@@ -111,6 +111,7 @@ pub extern "C" fn rustdesk_core_main() -> bool {
|
||||
#[cfg(target_os = "macos")]
|
||||
std::process::exit(0);
|
||||
}
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
false
|
||||
}
|
||||
|
||||
@@ -716,12 +717,13 @@ impl InvokeUiSession for FlutterHandler {
|
||||
);
|
||||
}
|
||||
|
||||
fn set_connection_type(&self, is_secured: bool, direct: bool) {
|
||||
fn set_connection_type(&self, is_secured: bool, direct: bool, stream_type: &str) {
|
||||
self.push_event(
|
||||
"connection_ready",
|
||||
&[
|
||||
("secure", &is_secured.to_string()),
|
||||
("direct", &direct.to_string()),
|
||||
("stream_type", &stream_type.to_string()),
|
||||
],
|
||||
&[],
|
||||
);
|
||||
|
||||
@@ -37,7 +37,11 @@ lazy_static::lazy_static! {
|
||||
|
||||
fn initialize(app_dir: &str, custom_client_config: &str) {
|
||||
flutter::async_tasks::start_flutter_async_runner();
|
||||
*config::APP_DIR.write().unwrap() = app_dir.to_owned();
|
||||
// `APP_DIR` is set in `main_get_data_dir_ios()` on iOS.
|
||||
#[cfg(not(target_os = "ios"))]
|
||||
{
|
||||
*config::APP_DIR.write().unwrap() = app_dir.to_owned();
|
||||
}
|
||||
// core_main's load_custom_client does not work for flutter since it is only applied to its load_library in main.c
|
||||
if custom_client_config.is_empty() {
|
||||
crate::load_custom_client();
|
||||
@@ -269,7 +273,7 @@ pub fn session_take_screenshot(session_id: SessionID, display: usize) {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn session_handle_screenshot(session_id: SessionID, action: String) -> String {
|
||||
pub fn session_handle_screenshot(#[allow(unused_variables)] session_id: SessionID, action: String) -> String {
|
||||
crate::client::screenshot::handle_screenshot(action)
|
||||
}
|
||||
|
||||
@@ -629,7 +633,10 @@ pub fn session_open_terminal(session_id: SessionID, terminal_id: i32, rows: u32,
|
||||
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||
session.open_terminal(terminal_id, rows, cols);
|
||||
} else {
|
||||
log::error!("[flutter_ffi] Session not found for session_id: {}", session_id);
|
||||
log::error!(
|
||||
"[flutter_ffi] Session not found for session_id: {}",
|
||||
session_id
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1799,7 +1806,8 @@ pub fn main_set_home_dir(_home: String) {
|
||||
}
|
||||
|
||||
// This is a temporary method to get data dir for ios
|
||||
pub fn main_get_data_dir_ios() -> SyncReturn<String> {
|
||||
pub fn main_get_data_dir_ios(app_dir: String) -> SyncReturn<String> {
|
||||
*config::APP_DIR.write().unwrap() = app_dir;
|
||||
let data_dir = config::Config::path("data");
|
||||
if !data_dir.exists() {
|
||||
if let Err(e) = std::fs::create_dir_all(&data_dir) {
|
||||
@@ -2651,6 +2659,21 @@ pub fn main_set_common(_key: String, _value: String) {
|
||||
fs::remove_file(f).ok();
|
||||
}
|
||||
}
|
||||
} else if _key == "extract-update-dmg" {
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
if let Some(new_version_file) = get_download_file_from_url(&_value) {
|
||||
if let Some(f) = new_version_file.to_str() {
|
||||
crate::platform::macos::extract_update_dmg(f);
|
||||
} else {
|
||||
// unreachable!()
|
||||
log::error!("Failed to get the new version file path");
|
||||
}
|
||||
} else {
|
||||
// unreachable!()
|
||||
log::error!("Failed to get the new version file from url: {}", _value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2669,7 +2692,7 @@ pub fn session_get_common_sync(
|
||||
SyncReturn(session_get_common(session_id, key, param))
|
||||
}
|
||||
|
||||
pub fn session_get_common(session_id: SessionID, key: String, param: String) -> Option<String> {
|
||||
pub fn session_get_common(session_id: SessionID, key: String, #[allow(unused_variables)] param: String) -> Option<String> {
|
||||
if let Some(s) = sessions::get_session_by_session_id(&session_id) {
|
||||
let v = if key == "is_screenshot_supported" {
|
||||
s.is_screenshot_supported().to_string()
|
||||
|
||||
@@ -148,7 +148,7 @@ impl OidcSession {
|
||||
id: &str,
|
||||
uuid: &str,
|
||||
) -> ResultType<HbbHttpResponse<OidcAuthUrl>> {
|
||||
Ok(OIDC_SESSION
|
||||
let resp = OIDC_SESSION
|
||||
.read()
|
||||
.unwrap()
|
||||
.client
|
||||
@@ -159,8 +159,14 @@ impl OidcSession {
|
||||
"uuid": uuid,
|
||||
"deviceInfo": crate::ui_interface::get_login_device_info(),
|
||||
}))
|
||||
.send()?
|
||||
.try_into()?)
|
||||
.send()?;
|
||||
let status = resp.status();
|
||||
match resp.try_into() {
|
||||
Ok(v) => Ok(v),
|
||||
Err(err) => {
|
||||
hbb_common::bail!("Http status: {}, err: {}", status, err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn query(
|
||||
|
||||
@@ -104,7 +104,9 @@ pub enum FS {
|
||||
file_size: u64,
|
||||
last_modified: u64,
|
||||
is_upload: bool,
|
||||
is_resume: bool,
|
||||
},
|
||||
SendConfirm(Vec<u8>),
|
||||
Rename {
|
||||
id: i32,
|
||||
path: String,
|
||||
@@ -175,7 +177,7 @@ pub enum DataPortableService {
|
||||
Ping,
|
||||
Pong,
|
||||
ConnCount(Option<usize>),
|
||||
Mouse((Vec<u8>, i32)),
|
||||
Mouse((Vec<u8>, i32, String, u32, bool, bool)),
|
||||
Pointer((Vec<u8>, i32)),
|
||||
Key(Vec<u8>),
|
||||
RequestStart,
|
||||
@@ -287,6 +289,8 @@ pub enum Data {
|
||||
#[cfg(target_os = "windows")]
|
||||
PortForwardSessionCount(Option<usize>),
|
||||
SocksWs(Option<Box<(Option<config::Socks5Server>, String)>>),
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
Whiteboard((String, crate::whiteboard::CustomEvent)),
|
||||
}
|
||||
|
||||
#[tokio::main(flavor = "current_thread")]
|
||||
|
||||
@@ -418,6 +418,7 @@ pub fn is_modifier(key: &rdev::Key) -> bool {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[allow(dead_code)]
|
||||
pub fn is_modifier_code(evt: &KeyEvent) -> bool {
|
||||
match evt.union {
|
||||
Some(key_event::Union::Chr(code)) => {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#[cfg(not(target_os = "ios"))]
|
||||
use hbb_common::whoami;
|
||||
use hbb_common::{
|
||||
allow_err,
|
||||
anyhow::bail,
|
||||
@@ -10,7 +12,7 @@ use hbb_common::{
|
||||
self,
|
||||
sync::mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender},
|
||||
},
|
||||
whoami, ResultType,
|
||||
ResultType,
|
||||
};
|
||||
|
||||
use std::{
|
||||
@@ -45,7 +47,7 @@ pub(super) fn start_listening() -> ResultType<()> {
|
||||
}
|
||||
if let Some(self_addr) = get_ipaddr_by_peer(&addr) {
|
||||
let mut msg_out = Message::new();
|
||||
let mut hostname = whoami::hostname();
|
||||
let mut hostname = crate::whoami_hostname();
|
||||
// The default hostname is "localhost" which is a bit confusing
|
||||
if hostname == "localhost" {
|
||||
hostname = "unknown".to_owned();
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user