Merge branch 'rustdesk:master' into master
This commit is contained in:
commit
fa71d6fca1
45
.github/workflows/build-macos-arm64.yml
vendored
45
.github/workflows/build-macos-arm64.yml
vendored
@ -32,25 +32,38 @@ env:
|
|||||||
SIGN_BASE_URL: "${{ secrets.SIGN_BASE_URL }}"
|
SIGN_BASE_URL: "${{ secrets.SIGN_BASE_URL }}"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build-for-macOS-arm64:
|
build-rustdesk-ios:
|
||||||
name: build-for-macOS-arm64
|
|
||||||
runs-on: [self-hosted, macOS, ARM64]
|
runs-on: [self-hosted, macOS, ARM64]
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
steps:
|
steps:
|
||||||
#- name: Import the codesign cert
|
- name: Export GitHub Actions cache environment variables
|
||||||
# if: env.MACOS_P12_BASE64 != null
|
uses: actions/github-script@v6
|
||||||
# uses: apple-actions/import-codesign-certs@v1
|
with:
|
||||||
# continue-on-error: true
|
script: |
|
||||||
# with:
|
core.exportVariable('ACTIONS_CACHE_URL', process.env.ACTIONS_CACHE_URL || '');
|
||||||
# p12-file-base64: ${{ secrets.MACOS_P12_BASE64 }}
|
core.exportVariable('ACTIONS_RUNTIME_TOKEN', process.env.ACTIONS_RUNTIME_TOKEN || '');
|
||||||
# p12-password: ${{ secrets.MACOS_P12_PASSWORD }}
|
|
||||||
# keychain: rustdesk
|
|
||||||
|
|
||||||
#- name: Check sign and import sign key
|
- name: Checkout source code
|
||||||
# if: env.MACOS_P12_BASE64 != null
|
uses: actions/checkout@v3
|
||||||
# run: |
|
|
||||||
# security default-keychain -s rustdesk.keychain
|
|
||||||
# security find-identity -v
|
|
||||||
|
|
||||||
- name: Run
|
# $VCPKG_ROOT/vcpkg install --triplet arm64-ios --x-install-root="$VCPKG_ROOT/installed"
|
||||||
|
|
||||||
|
- name: Install flutter rust bridge deps
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
|
cargo install flutter_rust_bridge_codegen --version ${{ env.FLUTTER_RUST_BRIDGE_VERSION }} --features "uuid"
|
||||||
|
pushd flutter && flutter pub get && popd
|
||||||
|
~/.cargo/bin/flutter_rust_bridge_codegen --rust-input ./src/flutter_ffi.rs --dart-output ./flutter/lib/generated_bridge.dart --c-output ./flutter/ios/Runner/bridge_generated.h
|
||||||
|
|
||||||
|
- name: Build rustdesk lib
|
||||||
|
run: |
|
||||||
|
cargo build --features flutter,hwcodec --release --target aarch64-apple-ios --lib
|
||||||
|
|
||||||
|
- name: Build rustdesk
|
||||||
|
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
|
||||||
|
|||||||
271
.github/workflows/flutter-build.yml
vendored
271
.github/workflows/flutter-build.yml
vendored
@ -18,12 +18,10 @@ env:
|
|||||||
FLUTTER_RUST_BRIDGE_VERSION: "1.80.1"
|
FLUTTER_RUST_BRIDGE_VERSION: "1.80.1"
|
||||||
# for arm64 linux because official Dart SDK does not work
|
# for arm64 linux because official Dart SDK does not work
|
||||||
FLUTTER_ELINUX_VERSION: "3.16.9"
|
FLUTTER_ELINUX_VERSION: "3.16.9"
|
||||||
FLUTTER_ELINUX_COMMIT_ID: "c02bd16e1630f5bd690b85c5c2456ac1920e25af"
|
|
||||||
TAG_NAME: "${{ inputs.upload-tag }}"
|
TAG_NAME: "${{ inputs.upload-tag }}"
|
||||||
VCPKG_BINARY_SOURCES: "clear;x-gha,readwrite"
|
VCPKG_BINARY_SOURCES: "clear;x-gha,readwrite"
|
||||||
# vcpkg version: 2023.10.19
|
# vcpkg version: 2024.03.25
|
||||||
# for multiarch gcc compatibility
|
VCPKG_COMMIT_ID: "a34c873a9717a888f58dc05268dea15592c2f0ff"
|
||||||
VCPKG_COMMIT_ID: "8eb57355a4ffb410a2e94c07b4dca2dffbee8e50"
|
|
||||||
VERSION: "1.2.4"
|
VERSION: "1.2.4"
|
||||||
NDK_VERSION: "r26b"
|
NDK_VERSION: "r26b"
|
||||||
#signing keys env variable checks
|
#signing keys env variable checks
|
||||||
@ -321,7 +319,9 @@ jobs:
|
|||||||
./SignOutput/rustdesk-*.exe
|
./SignOutput/rustdesk-*.exe
|
||||||
./rustdesk-*.tar.gz
|
./rustdesk-*.tar.gz
|
||||||
|
|
||||||
build-for-macOS-arm64:
|
build-for-macOS-arm64-selfhost:
|
||||||
|
# use build-for-macOS instead
|
||||||
|
if: false
|
||||||
name: build-for-macOS-arm64
|
name: build-for-macOS-arm64
|
||||||
runs-on: [self-hosted, macOS, ARM64]
|
runs-on: [self-hosted, macOS, ARM64]
|
||||||
steps:
|
steps:
|
||||||
@ -392,6 +392,154 @@ jobs:
|
|||||||
files: |
|
files: |
|
||||||
rustdesk*-aarch64.dmg
|
rustdesk*-aarch64.dmg
|
||||||
|
|
||||||
|
build-rustdesk-ios:
|
||||||
|
if: ${{ inputs.upload-artifact }}
|
||||||
|
name: build rustdesk ios ipa ${{ matrix.job.target }} (${{ matrix.job.os }}) [${{ matrix.job.extra-build-features }}]
|
||||||
|
runs-on: ${{ matrix.job.os }}
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
job:
|
||||||
|
- {
|
||||||
|
arch: aarch64,
|
||||||
|
target: aarch64-apple-ios,
|
||||||
|
os: macos-13,
|
||||||
|
extra-build-features: "",
|
||||||
|
}
|
||||||
|
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: Install dependencies
|
||||||
|
run: |
|
||||||
|
brew install nasm
|
||||||
|
- name: Checkout source code
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
- name: Install flutter
|
||||||
|
uses: subosito/flutter-action@v2
|
||||||
|
with:
|
||||||
|
channel: "stable"
|
||||||
|
flutter-version: ${{ env.FLUTTER_VERSION }}
|
||||||
|
|
||||||
|
- name: Setup vcpkg with Github Actions binary cache
|
||||||
|
uses: lukka/run-vcpkg@v11
|
||||||
|
with:
|
||||||
|
vcpkgGitCommitId: ${{ env.VCPKG_COMMIT_ID }}
|
||||||
|
|
||||||
|
- name: Install vcpkg dependencies
|
||||||
|
run: |
|
||||||
|
$VCPKG_ROOT/vcpkg install --triplet arm64-ios --x-install-root="$VCPKG_ROOT/installed"
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: Install Rust toolchain
|
||||||
|
uses: dtolnay/rust-toolchain@v1
|
||||||
|
with:
|
||||||
|
toolchain: ${{ env.RUST_VERSION }}
|
||||||
|
targets: ${{ matrix.job.target }}
|
||||||
|
components: "rustfmt"
|
||||||
|
|
||||||
|
- uses: Swatinem/rust-cache@v2
|
||||||
|
with:
|
||||||
|
prefix-key: rustdesk-lib-cache-ios
|
||||||
|
key: ${{ matrix.job.target }}-${{ matrix.job.extra-build-features }}
|
||||||
|
|
||||||
|
- name: Install flutter rust bridge deps
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
cargo install flutter_rust_bridge_codegen --version ${{ env.FLUTTER_RUST_BRIDGE_VERSION }} --features "uuid"
|
||||||
|
pushd flutter && flutter pub get && popd
|
||||||
|
~/.cargo/bin/flutter_rust_bridge_codegen --rust-input ./src/flutter_ffi.rs --dart-output ./flutter/lib/generated_bridge.dart --c-output ./flutter/ios/Runner/bridge_generated.h
|
||||||
|
|
||||||
|
- name: Build rustdesk lib
|
||||||
|
run: |
|
||||||
|
rustup target add ${{ matrix.job.target }}
|
||||||
|
cargo build --features flutter,hwcodec --release --target aarch64-apple-ios --lib
|
||||||
|
|
||||||
|
- name: Build rustdesk
|
||||||
|
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-rustdesk-ios-selfhost:
|
||||||
|
#if: ${{ inputs.upload-artifact }}
|
||||||
|
if: false
|
||||||
|
runs-on: [self-hosted, macOS, ARM64]
|
||||||
|
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@v3
|
||||||
|
|
||||||
|
# $VCPKG_ROOT/vcpkg install --triplet arm64-ios --x-install-root="$VCPKG_ROOT/installed"
|
||||||
|
|
||||||
|
- name: Install flutter rust bridge deps
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
cargo install flutter_rust_bridge_codegen --version ${{ env.FLUTTER_RUST_BRIDGE_VERSION }} --features "uuid"
|
||||||
|
pushd flutter && flutter pub get && popd
|
||||||
|
~/.cargo/bin/flutter_rust_bridge_codegen --rust-input ./src/flutter_ffi.rs --dart-output ./flutter/lib/generated_bridge.dart --c-output ./flutter/ios/Runner/bridge_generated.h
|
||||||
|
|
||||||
|
- 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:
|
build-for-macOS:
|
||||||
name: ${{ matrix.job.target }} (${{ matrix.job.os }}) [${{ matrix.job.extra-build-args }}]
|
name: ${{ matrix.job.target }} (${{ matrix.job.os }}) [${{ matrix.job.extra-build-args }}]
|
||||||
runs-on: ${{ matrix.job.os }}
|
runs-on: ${{ matrix.job.os }}
|
||||||
@ -401,10 +549,16 @@ jobs:
|
|||||||
job:
|
job:
|
||||||
- {
|
- {
|
||||||
target: x86_64-apple-darwin,
|
target: x86_64-apple-darwin,
|
||||||
os: macos-latest,
|
os: macos-13, #macos-latest or macos-14 use M1 now, https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners/about-github-hosted-runners#:~:text=14%20GB-,macos%2Dlatest%20or%20macos%2D14,-The%20macos%2Dlatestlabel
|
||||||
extra-build-args: "",
|
extra-build-args: "",
|
||||||
arch: x86_64,
|
arch: x86_64,
|
||||||
}
|
}
|
||||||
|
- {
|
||||||
|
target: aarch64-apple-darwin,
|
||||||
|
os: macos-latest,
|
||||||
|
extra-build-args: "",
|
||||||
|
arch: aarch64,
|
||||||
|
}
|
||||||
steps:
|
steps:
|
||||||
- name: Export GitHub Actions cache environment variables
|
- name: Export GitHub Actions cache environment variables
|
||||||
uses: actions/github-script@v6
|
uses: actions/github-script@v6
|
||||||
@ -506,14 +660,14 @@ jobs:
|
|||||||
CREATE_DMG="$(command -v create-dmg)"
|
CREATE_DMG="$(command -v create-dmg)"
|
||||||
CREATE_DMG="$(readlink -f "$CREATE_DMG")"
|
CREATE_DMG="$(readlink -f "$CREATE_DMG")"
|
||||||
sed -i -e 's/MAXIMUM_UNMOUNTING_ATTEMPTS=3/MAXIMUM_UNMOUNTING_ATTEMPTS=7/' "$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 }}-x64.dmg ./flutter/build/macos/Build/Products/Release/RustDesk.app
|
create-dmg --icon "RustDesk.app" 200 190 --hide-extension "RustDesk.app" --window-size 800 400 --app-drop-link 600 185 rustdesk-${{ env.VERSION }}-${{ matrix.job.arch }}.dmg ./flutter/build/macos/Build/Products/Release/RustDesk.app
|
||||||
|
|
||||||
- name: Upload unsigned macOS app
|
- name: Upload unsigned macOS app
|
||||||
if: env.UPLOAD_ARTIFACT == 'true'
|
if: env.UPLOAD_ARTIFACT == 'true'
|
||||||
uses: actions/upload-artifact@master
|
uses: actions/upload-artifact@master
|
||||||
with:
|
with:
|
||||||
name: rustdesk-unsigned-macos-x64
|
name: rustdesk-unsigned-macos-${{ matrix.job.arch }}
|
||||||
path: rustdesk-${{ env.VERSION }}-x64.dmg # can not upload the directory directly or tar.gz, which destroy the link structure, causing the codesign failed
|
path: rustdesk-${{ env.VERSION }}-${{ matrix.job.arch }}.dmg # can not upload the directory directly or tar.gz, which destroy the link structure, causing the codesign failed
|
||||||
|
|
||||||
- name: Codesign app and create signed dmg
|
- name: Codesign app and create signed dmg
|
||||||
if: env.MACOS_P12_BASE64 != null && env.UPLOAD_ARTIFACT == 'true'
|
if: env.MACOS_P12_BASE64 != null && env.UPLOAD_ARTIFACT == 'true'
|
||||||
@ -552,20 +706,20 @@ jobs:
|
|||||||
publish_unsigned_mac:
|
publish_unsigned_mac:
|
||||||
needs:
|
needs:
|
||||||
- build-for-macOS
|
- build-for-macOS
|
||||||
- build-for-macOS-arm64
|
#- build-for-macOS-arm64
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
if: ${{ inputs.upload-artifact }}
|
if: ${{ inputs.upload-artifact }}
|
||||||
steps:
|
steps:
|
||||||
- name: Download artifacts
|
- name: Download artifacts
|
||||||
uses: actions/download-artifact@master
|
uses: actions/download-artifact@master
|
||||||
with:
|
with:
|
||||||
name: rustdesk-unsigned-macos-x64
|
name: rustdesk-unsigned-macos-x86_64
|
||||||
path: ./
|
path: ./
|
||||||
|
|
||||||
- name: Download Artifacts
|
- name: Download Artifacts
|
||||||
uses: actions/download-artifact@master
|
uses: actions/download-artifact@master
|
||||||
with:
|
with:
|
||||||
name: rustdesk-unsigned-macos-arm64
|
name: rustdesk-unsigned-macos-aarch64
|
||||||
path: ./
|
path: ./
|
||||||
|
|
||||||
- name: Combine unsigned macos app
|
- name: Combine unsigned macos app
|
||||||
@ -582,97 +736,6 @@ jobs:
|
|||||||
generate-bridge-linux:
|
generate-bridge-linux:
|
||||||
uses: ./.github/workflows/bridge.yml
|
uses: ./.github/workflows/bridge.yml
|
||||||
|
|
||||||
build-rustdesk-ios:
|
|
||||||
if: ${{ inputs.upload-artifact }}
|
|
||||||
name: build rustdesk ios ipa ${{ matrix.job.target }} (${{ matrix.job.os }}) [${{ matrix.job.extra-build-features }}]
|
|
||||||
runs-on: ${{ matrix.job.os }}
|
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
|
||||||
job:
|
|
||||||
- {
|
|
||||||
arch: aarch64,
|
|
||||||
target: aarch64-apple-ios,
|
|
||||||
os: macos-latest,
|
|
||||||
extra-build-features: "",
|
|
||||||
}
|
|
||||||
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: Install dependencies
|
|
||||||
run: |
|
|
||||||
brew install nasm
|
|
||||||
- name: Checkout source code
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
- name: Install flutter
|
|
||||||
uses: subosito/flutter-action@v2
|
|
||||||
with:
|
|
||||||
channel: "stable"
|
|
||||||
flutter-version: ${{ env.FLUTTER_VERSION }}
|
|
||||||
|
|
||||||
- name: Setup vcpkg with Github Actions binary cache
|
|
||||||
uses: lukka/run-vcpkg@v11
|
|
||||||
with:
|
|
||||||
vcpkgGitCommitId: ${{ env.VCPKG_COMMIT_ID }}
|
|
||||||
|
|
||||||
- name: Install vcpkg dependencies
|
|
||||||
run: |
|
|
||||||
$VCPKG_ROOT/vcpkg install --triplet arm64-ios --x-install-root="$VCPKG_ROOT/installed"
|
|
||||||
shell: bash
|
|
||||||
|
|
||||||
- name: Install Rust toolchain
|
|
||||||
uses: dtolnay/rust-toolchain@v1
|
|
||||||
with:
|
|
||||||
toolchain: ${{ env.RUST_VERSION }}
|
|
||||||
targets: ${{ matrix.job.target }}
|
|
||||||
components: "rustfmt"
|
|
||||||
|
|
||||||
- uses: Swatinem/rust-cache@v2
|
|
||||||
with:
|
|
||||||
prefix-key: rustdesk-lib-cache-ios
|
|
||||||
key: ${{ matrix.job.target }}-${{ matrix.job.extra-build-features }}
|
|
||||||
|
|
||||||
- name: Install flutter rust bridge deps
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
cargo install flutter_rust_bridge_codegen --version ${{ env.FLUTTER_RUST_BRIDGE_VERSION }} --features "uuid"
|
|
||||||
pushd flutter && flutter pub get && popd
|
|
||||||
~/.cargo/bin/flutter_rust_bridge_codegen --rust-input ./src/flutter_ffi.rs --dart-output ./flutter/lib/generated_bridge.dart --c-output ./flutter/ios/Runner/bridge_generated.h
|
|
||||||
|
|
||||||
- name: Build rustdesk lib
|
|
||||||
run: |
|
|
||||||
rustup target add ${{ matrix.job.target }}
|
|
||||||
cargo build --features flutter,hwcodec --release --target aarch64-apple-ios --lib
|
|
||||||
|
|
||||||
- name: Build rustdesk
|
|
||||||
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-rustdesk-android:
|
build-rustdesk-android:
|
||||||
needs: [generate-bridge-linux]
|
needs: [generate-bridge-linux]
|
||||||
name: build rustdesk android apk ${{ matrix.job.target }} (${{ matrix.job.os }}) [${{ matrix.job.extra-build-features }}]
|
name: build rustdesk android apk ${{ matrix.job.target }} (${{ matrix.job.os }}) [${{ matrix.job.extra-build-features }}]
|
||||||
@ -1545,7 +1608,7 @@ jobs:
|
|||||||
git clone https://github.com/sony/flutter-elinux.git || true
|
git clone https://github.com/sony/flutter-elinux.git || true
|
||||||
pushd flutter-elinux
|
pushd flutter-elinux
|
||||||
git fetch
|
git fetch
|
||||||
git reset --hard ${{ env.FLUTTER_ELINUX_COMMIT_ID }}
|
git reset --hard ${{ env.FLUTTER_VERSION }}
|
||||||
popd
|
popd
|
||||||
|
|
||||||
- uses: rustdesk-org/run-on-arch-action@amd64-support
|
- uses: rustdesk-org/run-on-arch-action@amd64-support
|
||||||
|
|||||||
208
Cargo.lock
generated
208
Cargo.lock
generated
@ -300,9 +300,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "async-compression"
|
name = "async-compression"
|
||||||
version = "0.4.5"
|
version = "0.4.8"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bc2d0cfb2a7388d34f590e76686704c494ed7aaceed62ee1ba35cbf363abc2a5"
|
checksum = "07dbbf24db18d609b1462965249abdf49129ccad073ec257da372adc83259c60"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"flate2",
|
"flate2",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
@ -517,6 +517,12 @@ version = "0.21.5"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9"
|
checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "base64"
|
||||||
|
version = "0.22.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9475866fec1451be56a3c2400fd081ff546538961565ccb5b7142cbd22bc7a51"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "base64ct"
|
name = "base64ct"
|
||||||
version = "1.6.0"
|
version = "1.6.0"
|
||||||
@ -1825,9 +1831,9 @@ checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "encoding_rs"
|
name = "encoding_rs"
|
||||||
version = "0.8.33"
|
version = "0.8.34"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1"
|
checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if 1.0.0",
|
"cfg-if 1.0.0",
|
||||||
]
|
]
|
||||||
@ -2825,9 +2831,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "h2"
|
name = "h2"
|
||||||
version = "0.3.24"
|
version = "0.3.26"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bb2c4422095b67ee78da96fbb51a4cc413b3b25883c7717ff7ca1ab31022c9c9"
|
checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"fnv",
|
"fnv",
|
||||||
@ -2876,6 +2882,7 @@ version = "0.1.0"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"backtrace",
|
"backtrace",
|
||||||
|
"base64 0.22.0",
|
||||||
"bytes",
|
"bytes",
|
||||||
"chrono",
|
"chrono",
|
||||||
"confy",
|
"confy",
|
||||||
@ -2887,6 +2894,7 @@ dependencies = [
|
|||||||
"flexi_logger",
|
"flexi_logger",
|
||||||
"futures",
|
"futures",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
|
"httparse",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"libc",
|
"libc",
|
||||||
"log",
|
"log",
|
||||||
@ -2898,6 +2906,8 @@ dependencies = [
|
|||||||
"quinn",
|
"quinn",
|
||||||
"rand 0.8.5",
|
"rand 0.8.5",
|
||||||
"regex",
|
"regex",
|
||||||
|
"rustls-pki-types",
|
||||||
|
"rustls-platform-verifier",
|
||||||
"serde 1.0.190",
|
"serde 1.0.190",
|
||||||
"serde_derive",
|
"serde_derive",
|
||||||
"serde_json 1.0.107",
|
"serde_json 1.0.107",
|
||||||
@ -2906,9 +2916,12 @@ dependencies = [
|
|||||||
"sysinfo",
|
"sysinfo",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-socks",
|
"tokio-native-tls",
|
||||||
|
"tokio-rustls 0.26.0",
|
||||||
|
"tokio-socks 0.5.1-2",
|
||||||
"tokio-util",
|
"tokio-util",
|
||||||
"toml 0.7.8",
|
"toml 0.7.8",
|
||||||
|
"url",
|
||||||
"uuid",
|
"uuid",
|
||||||
"winapi 0.3.9",
|
"winapi 0.3.9",
|
||||||
"zstd 0.13.0",
|
"zstd 0.13.0",
|
||||||
@ -2985,9 +2998,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "http"
|
name = "http"
|
||||||
version = "0.2.9"
|
version = "0.2.12"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482"
|
checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"fnv",
|
"fnv",
|
||||||
@ -2996,9 +3009,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "http-body"
|
name = "http-body"
|
||||||
version = "0.4.5"
|
version = "0.4.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1"
|
checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"http",
|
"http",
|
||||||
@ -3025,8 +3038,8 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hwcodec"
|
name = "hwcodec"
|
||||||
version = "0.3.3"
|
version = "0.4.3"
|
||||||
source = "git+https://github.com/21pages/hwcodec#eeebf980d4eb41daaf05090b097d5a59d688d3d8"
|
source = "git+https://github.com/21pages/hwcodec#db7c2d4afcb4947bfb452213ef7e9ba647578b43"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bindgen 0.59.2",
|
"bindgen 0.59.2",
|
||||||
"cc",
|
"cc",
|
||||||
@ -3038,9 +3051,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hyper"
|
name = "hyper"
|
||||||
version = "0.14.27"
|
version = "0.14.28"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ffb1cfd654a8219eaef89881fdb3bb3b1cdc5fa75ded05d6933b2b382e395468"
|
checksum = "bf96e135eb83a2a8ddf766e426a841d8ddd7449d5f00d34ea02b41d2f19eef80"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"futures-channel",
|
"futures-channel",
|
||||||
@ -3053,7 +3066,7 @@ dependencies = [
|
|||||||
"httpdate",
|
"httpdate",
|
||||||
"itoa 1.0.9",
|
"itoa 1.0.9",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"socket2 0.4.10",
|
"socket2 0.5.5",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tower-service",
|
"tower-service",
|
||||||
"tracing",
|
"tracing",
|
||||||
@ -3071,7 +3084,7 @@ dependencies = [
|
|||||||
"hyper",
|
"hyper",
|
||||||
"rustls 0.21.10",
|
"rustls 0.21.10",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-rustls",
|
"tokio-rustls 0.24.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -4565,7 +4578,7 @@ version = "1.5.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9a4a0cfc5fb21a09dc6af4bf834cf10d4a32fccd9e2ea468c4b1751a097487aa"
|
checksum = "9a4a0cfc5fb21a09dc6af4bf834cf10d4a32fccd9e2ea468c4b1751a097487aa"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64",
|
"base64 0.21.5",
|
||||||
"indexmap 1.9.3",
|
"indexmap 1.9.3",
|
||||||
"line-wrap",
|
"line-wrap",
|
||||||
"quick-xml",
|
"quick-xml",
|
||||||
@ -4838,7 +4851,7 @@ dependencies = [
|
|||||||
"ring 0.16.20",
|
"ring 0.16.20",
|
||||||
"rustc-hash",
|
"rustc-hash",
|
||||||
"rustls 0.20.9",
|
"rustls 0.20.9",
|
||||||
"rustls-native-certs",
|
"rustls-native-certs 0.6.3",
|
||||||
"slab",
|
"slab",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"tinyvec",
|
"tinyvec",
|
||||||
@ -5173,10 +5186,10 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "reqwest"
|
name = "reqwest"
|
||||||
version = "0.11.23"
|
version = "0.11.23"
|
||||||
source = "git+https://github.com/rustdesk-org/reqwest"
|
source = "git+https://github.com/rustdesk-org/reqwest#9cb758c9fb2f4edc62eb790acfd45a6a3da21ed3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-compression",
|
"async-compression",
|
||||||
"base64",
|
"base64 0.21.5",
|
||||||
"bytes",
|
"bytes",
|
||||||
"encoding_rs",
|
"encoding_rs",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
@ -5196,8 +5209,8 @@ dependencies = [
|
|||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"rustls 0.21.10",
|
"rustls 0.21.10",
|
||||||
"rustls-native-certs",
|
"rustls-native-certs 0.6.3",
|
||||||
"rustls-pemfile",
|
"rustls-pemfile 1.0.3",
|
||||||
"serde 1.0.190",
|
"serde 1.0.190",
|
||||||
"serde_json 1.0.107",
|
"serde_json 1.0.107",
|
||||||
"serde_urlencoded",
|
"serde_urlencoded",
|
||||||
@ -5205,14 +5218,15 @@ dependencies = [
|
|||||||
"system-configuration",
|
"system-configuration",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-native-tls",
|
"tokio-native-tls",
|
||||||
"tokio-rustls",
|
"tokio-rustls 0.24.1",
|
||||||
|
"tokio-socks 0.5.1",
|
||||||
"tokio-util",
|
"tokio-util",
|
||||||
"tower-service",
|
"tower-service",
|
||||||
"url",
|
"url",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
"wasm-bindgen-futures",
|
"wasm-bindgen-futures",
|
||||||
"web-sys",
|
"web-sys",
|
||||||
"webpki-roots",
|
"webpki-roots 0.25.4",
|
||||||
"winreg 0.50.0",
|
"winreg 0.50.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -5358,7 +5372,6 @@ dependencies = [
|
|||||||
"arboard",
|
"arboard",
|
||||||
"async-process",
|
"async-process",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"base64",
|
|
||||||
"bytes",
|
"bytes",
|
||||||
"cc",
|
"cc",
|
||||||
"cfg-if 1.0.0",
|
"cfg-if 1.0.0",
|
||||||
@ -5519,10 +5532,25 @@ checksum = "f9d5a6813c0759e4609cd494e8e725babae6a2ca7b62a5536a13daaec6fcb7ba"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"log",
|
"log",
|
||||||
"ring 0.17.5",
|
"ring 0.17.5",
|
||||||
"rustls-webpki",
|
"rustls-webpki 0.101.7",
|
||||||
"sct",
|
"sct",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustls"
|
||||||
|
version = "0.23.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8c4d6d8ad9f2492485e13453acbb291dd08f64441b6609c491f1c2cd2c6b4fe1"
|
||||||
|
dependencies = [
|
||||||
|
"log",
|
||||||
|
"once_cell",
|
||||||
|
"ring 0.17.5",
|
||||||
|
"rustls-pki-types",
|
||||||
|
"rustls-webpki 0.102.2",
|
||||||
|
"subtle",
|
||||||
|
"zeroize",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustls-native-certs"
|
name = "rustls-native-certs"
|
||||||
version = "0.6.3"
|
version = "0.6.3"
|
||||||
@ -5530,7 +5558,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "a9aace74cb666635c918e9c12bc0d348266037aa8eb599b5cba565709a8dff00"
|
checksum = "a9aace74cb666635c918e9c12bc0d348266037aa8eb599b5cba565709a8dff00"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"openssl-probe",
|
"openssl-probe",
|
||||||
"rustls-pemfile",
|
"rustls-pemfile 1.0.3",
|
||||||
|
"schannel",
|
||||||
|
"security-framework",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustls-native-certs"
|
||||||
|
version = "0.7.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8f1fb85efa936c42c6d5fc28d2629bb51e4b2f4b8a5211e297d599cc5a093792"
|
||||||
|
dependencies = [
|
||||||
|
"openssl-probe",
|
||||||
|
"rustls-pemfile 2.1.2",
|
||||||
|
"rustls-pki-types",
|
||||||
"schannel",
|
"schannel",
|
||||||
"security-framework",
|
"security-framework",
|
||||||
]
|
]
|
||||||
@ -5541,9 +5582,52 @@ version = "1.0.3"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2d3987094b1d07b653b7dfdc3f70ce9a1da9c51ac18c1b06b662e4f9a0e9f4b2"
|
checksum = "2d3987094b1d07b653b7dfdc3f70ce9a1da9c51ac18c1b06b662e4f9a0e9f4b2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64",
|
"base64 0.21.5",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustls-pemfile"
|
||||||
|
version = "2.1.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "29993a25686778eb88d4189742cd713c9bce943bc54251a33509dc63cbacf73d"
|
||||||
|
dependencies = [
|
||||||
|
"base64 0.22.0",
|
||||||
|
"rustls-pki-types",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustls-pki-types"
|
||||||
|
version = "1.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ecd36cc4259e3e4514335c4a138c6b43171a8d61d8f5c9348f9fc7529416f247"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustls-platform-verifier"
|
||||||
|
version = "0.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b5f0d26fa1ce3c790f9590868f0109289a044acb954525f933e2aa3b871c157d"
|
||||||
|
dependencies = [
|
||||||
|
"core-foundation 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"core-foundation-sys 0.8.4",
|
||||||
|
"jni 0.19.0",
|
||||||
|
"log",
|
||||||
|
"once_cell",
|
||||||
|
"rustls 0.23.4",
|
||||||
|
"rustls-native-certs 0.7.0",
|
||||||
|
"rustls-platform-verifier-android",
|
||||||
|
"rustls-webpki 0.102.2",
|
||||||
|
"security-framework",
|
||||||
|
"security-framework-sys",
|
||||||
|
"webpki-roots 0.26.1",
|
||||||
|
"winapi 0.3.9",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustls-platform-verifier-android"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "84e217e7fdc8466b5b35d30f8c0a30febd29173df4a3a0c2115d306b9c4117ad"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustls-webpki"
|
name = "rustls-webpki"
|
||||||
version = "0.101.7"
|
version = "0.101.7"
|
||||||
@ -5554,6 +5638,17 @@ dependencies = [
|
|||||||
"untrusted 0.9.0",
|
"untrusted 0.9.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustls-webpki"
|
||||||
|
version = "0.102.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "faaa0a62740bedb9b2ef5afa303da42764c012f743917351dc9a237ea1663610"
|
||||||
|
dependencies = [
|
||||||
|
"ring 0.17.5",
|
||||||
|
"rustls-pki-types",
|
||||||
|
"untrusted 0.9.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustversion"
|
name = "rustversion"
|
||||||
version = "1.0.14"
|
version = "1.0.14"
|
||||||
@ -5666,22 +5761,23 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "security-framework"
|
name = "security-framework"
|
||||||
version = "2.9.2"
|
version = "2.10.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de"
|
checksum = "770452e37cad93e0a50d5abc3990d2bc351c36d0328f86cefec2f2fb206eaef6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 1.3.2",
|
"bitflags 1.3.2",
|
||||||
"core-foundation 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
"core-foundation 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"core-foundation-sys 0.8.4",
|
"core-foundation-sys 0.8.4",
|
||||||
"libc",
|
"libc",
|
||||||
|
"num-bigint",
|
||||||
"security-framework-sys",
|
"security-framework-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "security-framework-sys"
|
name = "security-framework-sys"
|
||||||
version = "2.9.1"
|
version = "2.10.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a"
|
checksum = "41f3cc463c0ef97e11c3461a9d3787412d30e8e7eb907c79180c4a57bf7c04ef"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"core-foundation-sys 0.8.4",
|
"core-foundation-sys 0.8.4",
|
||||||
"libc",
|
"libc",
|
||||||
@ -6404,6 +6500,17 @@ dependencies = [
|
|||||||
"tokio",
|
"tokio",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tokio-rustls"
|
||||||
|
version = "0.26.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4"
|
||||||
|
dependencies = [
|
||||||
|
"rustls 0.23.4",
|
||||||
|
"rustls-pki-types",
|
||||||
|
"tokio",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio-socks"
|
name = "tokio-socks"
|
||||||
version = "0.5.1-2"
|
version = "0.5.1-2"
|
||||||
@ -6420,6 +6527,18 @@ dependencies = [
|
|||||||
"tokio-util",
|
"tokio-util",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tokio-socks"
|
||||||
|
version = "0.5.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "51165dfa029d2a65969413a6cc96f354b86b464498702f174a4efa13608fd8c0"
|
||||||
|
dependencies = [
|
||||||
|
"either",
|
||||||
|
"futures-util",
|
||||||
|
"thiserror",
|
||||||
|
"tokio",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio-util"
|
name = "tokio-util"
|
||||||
version = "0.7.10"
|
version = "0.7.10"
|
||||||
@ -6637,9 +6756,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "try-lock"
|
name = "try-lock"
|
||||||
version = "0.2.4"
|
version = "0.2.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed"
|
checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "typenum"
|
name = "typenum"
|
||||||
@ -7081,9 +7200,18 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "webpki-roots"
|
name = "webpki-roots"
|
||||||
version = "0.25.3"
|
version = "0.25.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1778a42e8b3b90bff8d0f5032bf22250792889a5cdc752aa0020c84abe3aaf10"
|
checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "webpki-roots"
|
||||||
|
version = "0.26.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b3de34ae270483955a94f4b21bdaaeb83d508bb84a01435f393818edb0012009"
|
||||||
|
dependencies = [
|
||||||
|
"rustls-pki-types",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "weezl"
|
name = "weezl"
|
||||||
@ -7856,6 +7984,12 @@ dependencies = [
|
|||||||
"syn 2.0.55",
|
"syn 2.0.55",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zeroize"
|
||||||
|
version = "1.7.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zip"
|
name = "zip"
|
||||||
version = "0.6.6"
|
version = "0.6.6"
|
||||||
|
|||||||
@ -65,7 +65,6 @@ samplerate = { version = "0.2", optional = true }
|
|||||||
uuid = { version = "1.3", features = ["v4"] }
|
uuid = { version = "1.3", features = ["v4"] }
|
||||||
clap = "4.2"
|
clap = "4.2"
|
||||||
rpassword = "7.2"
|
rpassword = "7.2"
|
||||||
base64 = "0.21"
|
|
||||||
num_cpus = "1.15"
|
num_cpus = "1.15"
|
||||||
bytes = { version = "1.4", features = ["serde"] }
|
bytes = { version = "1.4", features = ["serde"] }
|
||||||
default-net = "0.14"
|
default-net = "0.14"
|
||||||
@ -145,10 +144,10 @@ wallpaper = { git = "https://github.com/21pages/wallpaper.rs" }
|
|||||||
|
|
||||||
[target.'cfg(any(target_os = "macos", target_os = "windows"))'.dependencies]
|
[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
|
# https://github.com/rustdesk/rustdesk-server-pro/issues/189, using native-tls for better tls support
|
||||||
reqwest = { git = "https://github.com/rustdesk-org/reqwest", features = ["blocking", "json", "native-tls", "gzip"], default-features=false }
|
reqwest = { git = "https://github.com/rustdesk-org/reqwest", features = ["blocking", "socks", "json", "native-tls", "gzip"], default-features=false }
|
||||||
|
|
||||||
[target.'cfg(not(any(target_os = "macos", target_os = "windows")))'.dependencies]
|
[target.'cfg(not(any(target_os = "macos", target_os = "windows")))'.dependencies]
|
||||||
reqwest = { git = "https://github.com/rustdesk-org/reqwest", features = ["blocking", "json", "rustls-tls", "rustls-tls-native-roots", "gzip"], default-features=false }
|
reqwest = { git = "https://github.com/rustdesk-org/reqwest", features = ["blocking", "socks", "json", "rustls-tls", "rustls-tls-native-roots", "gzip"], default-features=false }
|
||||||
|
|
||||||
[target.'cfg(target_os = "linux")'.dependencies]
|
[target.'cfg(target_os = "linux")'.dependencies]
|
||||||
psimple = { package = "libpulse-simple-binding", version = "2.27" }
|
psimple = { package = "libpulse-simple-binding", version = "2.27" }
|
||||||
|
|||||||
@ -54,6 +54,8 @@ AppDir:
|
|||||||
- libwayland-cursor0
|
- libwayland-cursor0
|
||||||
- libwayland-egl1
|
- libwayland-egl1
|
||||||
- libpulse0
|
- libpulse0
|
||||||
|
- packagekit-gtk3-module
|
||||||
|
- libcanberra-gtk3-module
|
||||||
exclude:
|
exclude:
|
||||||
- humanity-icon-theme
|
- humanity-icon-theme
|
||||||
- hicolor-icon-theme
|
- hicolor-icon-theme
|
||||||
|
|||||||
@ -57,6 +57,8 @@ AppDir:
|
|||||||
- libwayland-cursor0
|
- libwayland-cursor0
|
||||||
- libwayland-egl1
|
- libwayland-egl1
|
||||||
- libpulse0
|
- libpulse0
|
||||||
|
- packagekit-gtk3-module
|
||||||
|
- libcanberra-gtk3-module
|
||||||
exclude:
|
exclude:
|
||||||
- humanity-icon-theme
|
- humanity-icon-theme
|
||||||
- hicolor-icon-theme
|
- hicolor-icon-theme
|
||||||
|
|||||||
@ -139,4 +139,4 @@ SPEC CHECKSUMS:
|
|||||||
|
|
||||||
PODFILE CHECKSUM: d4cb12ad5d3bdb3352770b1d3db237584e155156
|
PODFILE CHECKSUM: d4cb12ad5d3bdb3352770b1d3db237584e155156
|
||||||
|
|
||||||
COCOAPODS: 1.12.1
|
COCOAPODS: 1.15.2
|
||||||
|
|||||||
@ -159,7 +159,7 @@
|
|||||||
97C146E61CF9000F007C117D /* Project object */ = {
|
97C146E61CF9000F007C117D /* Project object */ = {
|
||||||
isa = PBXProject;
|
isa = PBXProject;
|
||||||
attributes = {
|
attributes = {
|
||||||
LastUpgradeCheck = 1430;
|
LastUpgradeCheck = 1510;
|
||||||
ORGANIZATIONNAME = "";
|
ORGANIZATIONNAME = "";
|
||||||
TargetAttributes = {
|
TargetAttributes = {
|
||||||
97C146ED1CF9000F007C117D = {
|
97C146ED1CF9000F007C117D = {
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<Scheme
|
<Scheme
|
||||||
LastUpgradeVersion = "1430"
|
LastUpgradeVersion = "1510"
|
||||||
version = "1.3">
|
version = "1.3">
|
||||||
<BuildAction
|
<BuildAction
|
||||||
parallelizeBuildables = "YES"
|
parallelizeBuildables = "YES"
|
||||||
|
|||||||
@ -2978,16 +2978,16 @@ Future<bool> setServerConfig(
|
|||||||
}
|
}
|
||||||
// id
|
// id
|
||||||
if (config.idServer.isNotEmpty && errMsgs != null) {
|
if (config.idServer.isNotEmpty && errMsgs != null) {
|
||||||
errMsgs[0].value =
|
errMsgs[0].value = translate(await bind.mainTestIfValidServer(
|
||||||
translate(await bind.mainTestIfValidServer(server: config.idServer));
|
server: config.idServer, testWithProxy: true));
|
||||||
if (errMsgs[0].isNotEmpty) {
|
if (errMsgs[0].isNotEmpty) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// relay
|
// relay
|
||||||
if (config.relayServer.isNotEmpty && errMsgs != null) {
|
if (config.relayServer.isNotEmpty && errMsgs != null) {
|
||||||
errMsgs[1].value =
|
errMsgs[1].value = translate(await bind.mainTestIfValidServer(
|
||||||
translate(await bind.mainTestIfValidServer(server: config.relayServer));
|
server: config.relayServer, testWithProxy: true));
|
||||||
if (errMsgs[1].isNotEmpty) {
|
if (errMsgs[1].isNotEmpty) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -168,6 +168,29 @@ class ShowRemoteCursorState {
|
|||||||
static RxBool find(String id) => Get.find<RxBool>(tag: tag(id));
|
static RxBool find(String id) => Get.find<RxBool>(tag: tag(id));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class ShowRemoteCursorLockState {
|
||||||
|
static String tag(String id) => 'show_remote_cursor_lock_$id';
|
||||||
|
|
||||||
|
static void init(String id) {
|
||||||
|
final key = tag(id);
|
||||||
|
if (!Get.isRegistered(tag: key)) {
|
||||||
|
final RxBool state = false.obs;
|
||||||
|
Get.put(state, tag: key);
|
||||||
|
} else {
|
||||||
|
Get.find<RxBool>(tag: key).value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void delete(String id) {
|
||||||
|
final key = tag(id);
|
||||||
|
if (Get.isRegistered(tag: key)) {
|
||||||
|
Get.delete(tag: key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static RxBool find(String id) => Get.find<RxBool>(tag: tag(id));
|
||||||
|
}
|
||||||
|
|
||||||
class KeyboardEnabledState {
|
class KeyboardEnabledState {
|
||||||
static String tag(String id) => 'keyboard_enabled_$id';
|
static String tag(String id) => 'keyboard_enabled_$id';
|
||||||
|
|
||||||
@ -315,6 +338,7 @@ initSharedStates(String id) {
|
|||||||
CurrentDisplayState.init(id);
|
CurrentDisplayState.init(id);
|
||||||
KeyboardEnabledState.init(id);
|
KeyboardEnabledState.init(id);
|
||||||
ShowRemoteCursorState.init(id);
|
ShowRemoteCursorState.init(id);
|
||||||
|
ShowRemoteCursorLockState.init(id);
|
||||||
RemoteCursorMovedState.init(id);
|
RemoteCursorMovedState.init(id);
|
||||||
FingerprintState.init(id);
|
FingerprintState.init(id);
|
||||||
PeerBoolOption.init(id, 'zoom-cursor', () => false);
|
PeerBoolOption.init(id, 'zoom-cursor', () => false);
|
||||||
@ -327,6 +351,7 @@ removeSharedStates(String id) {
|
|||||||
BlockInputState.delete(id);
|
BlockInputState.delete(id);
|
||||||
CurrentDisplayState.delete(id);
|
CurrentDisplayState.delete(id);
|
||||||
ShowRemoteCursorState.delete(id);
|
ShowRemoteCursorState.delete(id);
|
||||||
|
ShowRemoteCursorLockState.delete(id);
|
||||||
KeyboardEnabledState.delete(id);
|
KeyboardEnabledState.delete(id);
|
||||||
RemoteCursorMovedState.delete(id);
|
RemoteCursorMovedState.delete(id);
|
||||||
FingerprintState.delete(id);
|
FingerprintState.delete(id);
|
||||||
|
|||||||
@ -203,7 +203,7 @@ class _RawTouchGestureDetectorRegionState
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!handleTouch) {
|
if (!handleTouch) {
|
||||||
ffi.cursorModel.updatePan(d.delta.dx, d.delta.dy, handleTouch);
|
ffi.cursorModel.updatePan(d.delta, d.localPosition, handleTouch);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -222,6 +222,9 @@ class _RawTouchGestureDetectorRegionState
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (handleTouch) {
|
if (handleTouch) {
|
||||||
|
if (isDesktop) {
|
||||||
|
ffi.cursorModel.trySetRemoteWindowCoords();
|
||||||
|
}
|
||||||
inputModel.sendMouse('down', MouseButtons.left);
|
inputModel.sendMouse('down', MouseButtons.left);
|
||||||
ffi.cursorModel.move(d.localPosition.dx, d.localPosition.dy);
|
ffi.cursorModel.move(d.localPosition.dx, d.localPosition.dy);
|
||||||
} else {
|
} else {
|
||||||
@ -241,13 +244,16 @@ class _RawTouchGestureDetectorRegionState
|
|||||||
if (lastDeviceKind != PointerDeviceKind.touch) {
|
if (lastDeviceKind != PointerDeviceKind.touch) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ffi.cursorModel.updatePan(d.delta.dx, d.delta.dy, handleTouch);
|
ffi.cursorModel.updatePan(d.delta, d.localPosition, handleTouch);
|
||||||
}
|
}
|
||||||
|
|
||||||
onOneFingerPanEnd(DragEndDetails d) {
|
onOneFingerPanEnd(DragEndDetails d) {
|
||||||
if (lastDeviceKind != PointerDeviceKind.touch) {
|
if (lastDeviceKind != PointerDeviceKind.touch) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (isDesktop) {
|
||||||
|
ffi.cursorModel.clearRemoteWindowCoords();
|
||||||
|
}
|
||||||
inputModel.sendMouse('up', MouseButtons.left);
|
inputModel.sendMouse('up', MouseButtons.left);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -212,6 +212,8 @@ List<(String, String)> otherDefaultSettings() {
|
|||||||
if ((isDesktop || isWebDesktop)) ('show_monitors_tip', kKeyShowMonitorsToolbar),
|
if ((isDesktop || isWebDesktop)) ('show_monitors_tip', kKeyShowMonitorsToolbar),
|
||||||
if ((isDesktop || isWebDesktop)) ('Collapse toolbar', 'collapse_toolbar'),
|
if ((isDesktop || isWebDesktop)) ('Collapse toolbar', 'collapse_toolbar'),
|
||||||
('Show remote cursor', 'show_remote_cursor'),
|
('Show remote cursor', 'show_remote_cursor'),
|
||||||
|
('Follow remote cursor', 'follow_remote_cursor'),
|
||||||
|
('Follow remote window focus', 'follow_remote_window'),
|
||||||
if ((isDesktop || isWebDesktop)) ('Zoom cursor', 'zoom-cursor'),
|
if ((isDesktop || isWebDesktop)) ('Zoom cursor', 'zoom-cursor'),
|
||||||
('Show quality monitor', 'show_quality_monitor'),
|
('Show quality monitor', 'show_quality_monitor'),
|
||||||
('Mute', 'disable_audio'),
|
('Mute', 'disable_audio'),
|
||||||
|
|||||||
@ -371,7 +371,7 @@ Future<List<TRadioMenu<String>>> toolbarCodec(
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<TToggleMenu>> toolbarDisplayToggle(
|
Future<List<TToggleMenu>> toolbarCursor(
|
||||||
BuildContext context, String id, FFI ffi) async {
|
BuildContext context, String id, FFI ffi) async {
|
||||||
List<TToggleMenu> v = [];
|
List<TToggleMenu> v = [];
|
||||||
final ffiModel = ffi.ffiModel;
|
final ffiModel = ffi.ffiModel;
|
||||||
@ -384,12 +384,17 @@ Future<List<TToggleMenu>> toolbarDisplayToggle(
|
|||||||
!ffi.canvasModel.cursorEmbedded &&
|
!ffi.canvasModel.cursorEmbedded &&
|
||||||
!pi.isWayland) {
|
!pi.isWayland) {
|
||||||
final state = ShowRemoteCursorState.find(id);
|
final state = ShowRemoteCursorState.find(id);
|
||||||
|
final lockState = ShowRemoteCursorLockState.find(id);
|
||||||
final enabled = !ffiModel.viewOnly;
|
final enabled = !ffiModel.viewOnly;
|
||||||
final option = 'show-remote-cursor';
|
final option = 'show-remote-cursor';
|
||||||
|
if (pi.currentDisplay == kAllDisplayValue ||
|
||||||
|
bind.sessionIsMultiUiSession(sessionId: sessionId)) {
|
||||||
|
lockState.value = false;
|
||||||
|
}
|
||||||
v.add(TToggleMenu(
|
v.add(TToggleMenu(
|
||||||
child: Text(translate('Show remote cursor')),
|
child: Text(translate('Show remote cursor')),
|
||||||
value: state.value,
|
value: state.value,
|
||||||
onChanged: enabled
|
onChanged: enabled && !lockState.value
|
||||||
? (value) async {
|
? (value) async {
|
||||||
if (value == null) return;
|
if (value == null) return;
|
||||||
await bind.sessionToggleOption(
|
await bind.sessionToggleOption(
|
||||||
@ -399,6 +404,67 @@ Future<List<TToggleMenu>> toolbarDisplayToggle(
|
|||||||
}
|
}
|
||||||
: null));
|
: null));
|
||||||
}
|
}
|
||||||
|
// follow remote cursor
|
||||||
|
if (pi.platform != kPeerPlatformAndroid &&
|
||||||
|
!ffi.canvasModel.cursorEmbedded &&
|
||||||
|
!pi.isWayland &&
|
||||||
|
versionCmp(pi.version, "1.2.4") >= 0 &&
|
||||||
|
pi.displays.length > 1 &&
|
||||||
|
pi.currentDisplay != kAllDisplayValue &&
|
||||||
|
!bind.sessionIsMultiUiSession(sessionId: sessionId)) {
|
||||||
|
final option = 'follow-remote-cursor';
|
||||||
|
final value =
|
||||||
|
bind.sessionGetToggleOptionSync(sessionId: sessionId, arg: option);
|
||||||
|
final showCursorOption = 'show-remote-cursor';
|
||||||
|
final showCursorState = ShowRemoteCursorState.find(id);
|
||||||
|
final showCursorLockState = ShowRemoteCursorLockState.find(id);
|
||||||
|
final showCursorEnabled = bind.sessionGetToggleOptionSync(
|
||||||
|
sessionId: sessionId, arg: showCursorOption);
|
||||||
|
showCursorLockState.value = value;
|
||||||
|
if (value && !showCursorEnabled) {
|
||||||
|
await bind.sessionToggleOption(
|
||||||
|
sessionId: sessionId, value: showCursorOption);
|
||||||
|
showCursorState.value = bind.sessionGetToggleOptionSync(
|
||||||
|
sessionId: sessionId, arg: showCursorOption);
|
||||||
|
}
|
||||||
|
v.add(TToggleMenu(
|
||||||
|
child: Text(translate('Follow remote cursor')),
|
||||||
|
value: value,
|
||||||
|
onChanged: (value) async {
|
||||||
|
if (value == null) return;
|
||||||
|
await bind.sessionToggleOption(sessionId: sessionId, value: option);
|
||||||
|
value = bind.sessionGetToggleOptionSync(
|
||||||
|
sessionId: sessionId, arg: option);
|
||||||
|
showCursorLockState.value = value;
|
||||||
|
if (!showCursorEnabled) {
|
||||||
|
await bind.sessionToggleOption(
|
||||||
|
sessionId: sessionId, value: showCursorOption);
|
||||||
|
showCursorState.value = bind.sessionGetToggleOptionSync(
|
||||||
|
sessionId: sessionId, arg: showCursorOption);
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
// follow remote window focus
|
||||||
|
if (pi.platform != kPeerPlatformAndroid &&
|
||||||
|
!ffi.canvasModel.cursorEmbedded &&
|
||||||
|
!pi.isWayland &&
|
||||||
|
versionCmp(pi.version, "1.2.4") >= 0 &&
|
||||||
|
pi.displays.length > 1 &&
|
||||||
|
pi.currentDisplay != kAllDisplayValue &&
|
||||||
|
!bind.sessionIsMultiUiSession(sessionId: sessionId)) {
|
||||||
|
final option = 'follow-remote-window';
|
||||||
|
final value =
|
||||||
|
bind.sessionGetToggleOptionSync(sessionId: sessionId, arg: option);
|
||||||
|
v.add(TToggleMenu(
|
||||||
|
child: Text(translate('Follow remote window focus')),
|
||||||
|
value: value,
|
||||||
|
onChanged: (value) async {
|
||||||
|
if (value == null) return;
|
||||||
|
await bind.sessionToggleOption(sessionId: sessionId, value: option);
|
||||||
|
value = bind.sessionGetToggleOptionSync(
|
||||||
|
sessionId: sessionId, arg: option);
|
||||||
|
}));
|
||||||
|
}
|
||||||
// zoom cursor
|
// zoom cursor
|
||||||
final viewStyle = await bind.sessionGetViewStyle(sessionId: sessionId) ?? '';
|
final viewStyle = await bind.sessionGetViewStyle(sessionId: sessionId) ?? '';
|
||||||
if (!isMobile &&
|
if (!isMobile &&
|
||||||
@ -417,6 +483,17 @@ Future<List<TToggleMenu>> toolbarDisplayToggle(
|
|||||||
},
|
},
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<TToggleMenu>> toolbarDisplayToggle(
|
||||||
|
BuildContext context, String id, FFI ffi) async {
|
||||||
|
List<TToggleMenu> v = [];
|
||||||
|
final ffiModel = ffi.ffiModel;
|
||||||
|
final pi = ffiModel.pi;
|
||||||
|
final perms = ffiModel.permissions;
|
||||||
|
final sessionId = ffi.sessionId;
|
||||||
|
|
||||||
// show quality monitor
|
// show quality monitor
|
||||||
final option = 'show-quality-monitor';
|
final option = 'show-quality-monitor';
|
||||||
v.add(TToggleMenu(
|
v.add(TToggleMenu(
|
||||||
|
|||||||
@ -60,6 +60,7 @@ const String kWindowEventActiveSession = "active_session";
|
|||||||
const String kWindowEventActiveDisplaySession = "active_display_session";
|
const String kWindowEventActiveDisplaySession = "active_display_session";
|
||||||
const String kWindowEventGetRemoteList = "get_remote_list";
|
const String kWindowEventGetRemoteList = "get_remote_list";
|
||||||
const String kWindowEventGetSessionIdList = "get_session_id_list";
|
const String kWindowEventGetSessionIdList = "get_session_id_list";
|
||||||
|
const String kWindowEventRemoteWindowCoords = "remote_window_coords";
|
||||||
|
|
||||||
const String kWindowEventMoveTabToNewWindow = "move_tab_to_new_window";
|
const String kWindowEventMoveTabToNewWindow = "move_tab_to_new_window";
|
||||||
const String kWindowEventGetCachedSessionData = "get_cached_session_data";
|
const String kWindowEventGetCachedSessionData = "get_cached_session_data";
|
||||||
|
|||||||
@ -153,7 +153,13 @@ class _DesktopHomePageState extends State<DesktopHomePage>
|
|||||||
size: 22,
|
size: 22,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
onTap: () => DesktopSettingPage.switch2page(0),
|
onTap: () => {
|
||||||
|
if (DesktopSettingPage.tabKeys.isNotEmpty)
|
||||||
|
{
|
||||||
|
DesktopSettingPage.switch2page(
|
||||||
|
DesktopSettingPage.tabKeys[0])
|
||||||
|
}
|
||||||
|
},
|
||||||
onHover: (value) => _editHover.value = value,
|
onHover: (value) => _editHover.value = value,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -347,7 +353,8 @@ class _DesktopHomePageState extends State<DesktopHomePage>
|
|||||||
).marginOnly(right: 8, top: 4),
|
).marginOnly(right: 8, top: 4),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
onTap: () => DesktopSettingPage.switch2page(0),
|
onTap: () => DesktopSettingPage.switch2page(
|
||||||
|
SettingsTabKey.safety),
|
||||||
onHover: (value) => editHover.value = value,
|
onHover: (value) => editHover.value = value,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@ -774,6 +781,12 @@ class _DesktopHomePageState extends State<DesktopHomePage>
|
|||||||
final screenRect = parseParamScreenRect(args);
|
final screenRect = parseParamScreenRect(args);
|
||||||
await rustDeskWinManager.openMonitorSession(
|
await rustDeskWinManager.openMonitorSession(
|
||||||
windowId, peerId, display, displayCount, screenRect);
|
windowId, peerId, display, displayCount, screenRect);
|
||||||
|
} else if (call.method == kWindowEventRemoteWindowCoords) {
|
||||||
|
final windowId = int.tryParse(call.arguments);
|
||||||
|
if (windowId != null) {
|
||||||
|
return jsonEncode(
|
||||||
|
await rustDeskWinManager.getOtherRemoteWindowCoords(windowId));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
_uniLinksSubscription = listenUniLinks();
|
_uniLinksSubscription = listenUniLinks();
|
||||||
|
|||||||
@ -36,34 +36,57 @@ const double _kTitleFontSize = 20;
|
|||||||
const double _kContentFontSize = 15;
|
const double _kContentFontSize = 15;
|
||||||
const Color _accentColor = MyTheme.accent;
|
const Color _accentColor = MyTheme.accent;
|
||||||
const String _kSettingPageControllerTag = 'settingPageController';
|
const String _kSettingPageControllerTag = 'settingPageController';
|
||||||
const String _kSettingPageIndexTag = 'settingPageIndex';
|
const String _kSettingPageTabKeyTag = 'settingPageTabKey';
|
||||||
const int _kPageCount = 6;
|
|
||||||
|
|
||||||
class _TabInfo {
|
class _TabInfo {
|
||||||
|
late final SettingsTabKey key;
|
||||||
late final String label;
|
late final String label;
|
||||||
late final IconData unselected;
|
late final IconData unselected;
|
||||||
late final IconData selected;
|
late final IconData selected;
|
||||||
_TabInfo(this.label, this.unselected, this.selected);
|
_TabInfo(this.key, this.label, this.unselected, this.selected);
|
||||||
|
}
|
||||||
|
|
||||||
|
enum SettingsTabKey {
|
||||||
|
general,
|
||||||
|
safety,
|
||||||
|
network,
|
||||||
|
display,
|
||||||
|
plugin,
|
||||||
|
account,
|
||||||
|
about,
|
||||||
}
|
}
|
||||||
|
|
||||||
class DesktopSettingPage extends StatefulWidget {
|
class DesktopSettingPage extends StatefulWidget {
|
||||||
final int initialPage;
|
final SettingsTabKey initialTabkey;
|
||||||
|
static final List<SettingsTabKey> tabKeys = [
|
||||||
|
SettingsTabKey.general,
|
||||||
|
if (!bind.isOutgoingOnly() && !bind.isDisableSettings())
|
||||||
|
SettingsTabKey.safety,
|
||||||
|
if (!bind.isDisableSettings()) SettingsTabKey.network,
|
||||||
|
if (!bind.isIncomingOnly()) SettingsTabKey.display,
|
||||||
|
if (!isWeb && !bind.isIncomingOnly() && bind.pluginFeatureIsEnabled())
|
||||||
|
SettingsTabKey.plugin,
|
||||||
|
if (!bind.isDisableAccount()) SettingsTabKey.account,
|
||||||
|
SettingsTabKey.about,
|
||||||
|
];
|
||||||
|
|
||||||
const DesktopSettingPage({Key? key, required this.initialPage})
|
DesktopSettingPage({Key? key, required this.initialTabkey}) : super(key: key);
|
||||||
: super(key: key);
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<DesktopSettingPage> createState() => _DesktopSettingPageState();
|
State<DesktopSettingPage> createState() => _DesktopSettingPageState();
|
||||||
|
|
||||||
static void switch2page(int page) {
|
static void switch2page(SettingsTabKey page) {
|
||||||
if (page >= _kPageCount) return;
|
|
||||||
try {
|
try {
|
||||||
|
int index = tabKeys.indexOf(page);
|
||||||
|
if (index == -1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (Get.isRegistered<PageController>(tag: _kSettingPageControllerTag)) {
|
if (Get.isRegistered<PageController>(tag: _kSettingPageControllerTag)) {
|
||||||
DesktopTabPage.onAddSetting(initialPage: page);
|
DesktopTabPage.onAddSetting(initialPage: page);
|
||||||
PageController controller = Get.find(tag: _kSettingPageControllerTag);
|
PageController controller = Get.find(tag: _kSettingPageControllerTag);
|
||||||
RxInt selectedIndex = Get.find(tag: _kSettingPageIndexTag);
|
Rx<SettingsTabKey> selected = Get.find(tag: _kSettingPageTabKeyTag);
|
||||||
selectedIndex.value = page;
|
selected.value = page;
|
||||||
controller.jumpToPage(page);
|
controller.jumpToPage(index);
|
||||||
} else {
|
} else {
|
||||||
DesktopTabPage.onAddSetting(initialPage: page);
|
DesktopTabPage.onAddSetting(initialPage: page);
|
||||||
}
|
}
|
||||||
@ -76,7 +99,7 @@ class DesktopSettingPage extends StatefulWidget {
|
|||||||
class _DesktopSettingPageState extends State<DesktopSettingPage>
|
class _DesktopSettingPageState extends State<DesktopSettingPage>
|
||||||
with TickerProviderStateMixin, AutomaticKeepAliveClientMixin {
|
with TickerProviderStateMixin, AutomaticKeepAliveClientMixin {
|
||||||
late PageController controller;
|
late PageController controller;
|
||||||
late RxInt selectedIndex;
|
late Rx<SettingsTabKey> selectedTab;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool get wantKeepAlive => true;
|
bool get wantKeepAlive => true;
|
||||||
@ -84,14 +107,20 @@ class _DesktopSettingPageState extends State<DesktopSettingPage>
|
|||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
selectedIndex =
|
var initialIndex = DesktopSettingPage.tabKeys.indexOf(widget.initialTabkey);
|
||||||
(widget.initialPage < _kPageCount ? widget.initialPage : 0).obs;
|
if (initialIndex == -1) {
|
||||||
Get.put<RxInt>(selectedIndex, tag: _kSettingPageIndexTag);
|
initialIndex = 0;
|
||||||
controller = PageController(initialPage: widget.initialPage);
|
}
|
||||||
|
selectedTab = DesktopSettingPage.tabKeys[initialIndex].obs;
|
||||||
|
Get.put<Rx<SettingsTabKey>>(selectedTab, tag: _kSettingPageTabKeyTag);
|
||||||
|
controller = PageController(initialPage: initialIndex);
|
||||||
Get.put<PageController>(controller, tag: _kSettingPageControllerTag);
|
Get.put<PageController>(controller, tag: _kSettingPageControllerTag);
|
||||||
controller.addListener(() {
|
controller.addListener(() {
|
||||||
if (controller.page != null) {
|
if (controller.page != null) {
|
||||||
selectedIndex.value = controller.page!.toInt();
|
int page = controller.page!.toInt();
|
||||||
|
if (page < DesktopSettingPage.tabKeys.length) {
|
||||||
|
selectedTab.value = DesktopSettingPage.tabKeys[page];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -100,26 +129,43 @@ class _DesktopSettingPageState extends State<DesktopSettingPage>
|
|||||||
void dispose() {
|
void dispose() {
|
||||||
super.dispose();
|
super.dispose();
|
||||||
Get.delete<PageController>(tag: _kSettingPageControllerTag);
|
Get.delete<PageController>(tag: _kSettingPageControllerTag);
|
||||||
Get.delete<RxInt>(tag: _kSettingPageIndexTag);
|
Get.delete<RxInt>(tag: _kSettingPageTabKeyTag);
|
||||||
}
|
}
|
||||||
|
|
||||||
List<_TabInfo> _settingTabs() {
|
List<_TabInfo> _settingTabs() {
|
||||||
final List<_TabInfo> settingTabs = <_TabInfo>[
|
final List<_TabInfo> settingTabs = <_TabInfo>[];
|
||||||
_TabInfo('General', Icons.settings_outlined, Icons.settings),
|
for (final tab in DesktopSettingPage.tabKeys) {
|
||||||
if (!bind.isOutgoingOnly() && !bind.isDisableSettings())
|
switch (tab) {
|
||||||
_TabInfo('Security', Icons.enhanced_encryption_outlined,
|
case SettingsTabKey.general:
|
||||||
Icons.enhanced_encryption),
|
settingTabs.add(_TabInfo(
|
||||||
if (!bind.isDisableSettings())
|
tab, 'General', Icons.settings_outlined, Icons.settings));
|
||||||
_TabInfo('Network', Icons.link_outlined, Icons.link),
|
break;
|
||||||
if (!bind.isIncomingOnly())
|
case SettingsTabKey.safety:
|
||||||
_TabInfo(
|
settingTabs.add(_TabInfo(tab, 'Security',
|
||||||
'Display', Icons.desktop_windows_outlined, Icons.desktop_windows),
|
Icons.enhanced_encryption_outlined, Icons.enhanced_encryption));
|
||||||
if (!isWeb && !bind.isIncomingOnly() && bind.pluginFeatureIsEnabled())
|
break;
|
||||||
_TabInfo('Plugin', Icons.extension_outlined, Icons.extension),
|
case SettingsTabKey.network:
|
||||||
if (!bind.isDisableAccount())
|
settingTabs
|
||||||
_TabInfo('Account', Icons.person_outline, Icons.person),
|
.add(_TabInfo(tab, 'Network', Icons.link_outlined, Icons.link));
|
||||||
_TabInfo('About', Icons.info_outline, Icons.info)
|
break;
|
||||||
];
|
case SettingsTabKey.display:
|
||||||
|
settingTabs.add(_TabInfo(tab, 'Display',
|
||||||
|
Icons.desktop_windows_outlined, Icons.desktop_windows));
|
||||||
|
break;
|
||||||
|
case SettingsTabKey.plugin:
|
||||||
|
settingTabs.add(_TabInfo(
|
||||||
|
tab, 'Plugin', Icons.extension_outlined, Icons.extension));
|
||||||
|
break;
|
||||||
|
case SettingsTabKey.account:
|
||||||
|
settingTabs.add(
|
||||||
|
_TabInfo(tab, 'Account', Icons.person_outline, Icons.person));
|
||||||
|
break;
|
||||||
|
case SettingsTabKey.about:
|
||||||
|
settingTabs
|
||||||
|
.add(_TabInfo(tab, 'About', Icons.info_outline, Icons.info));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
return settingTabs;
|
return settingTabs;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -198,26 +244,26 @@ class _DesktopSettingPageState extends State<DesktopSettingPage>
|
|||||||
child: ListView(
|
child: ListView(
|
||||||
physics: DraggableNeverScrollableScrollPhysics(),
|
physics: DraggableNeverScrollableScrollPhysics(),
|
||||||
controller: scrollController,
|
controller: scrollController,
|
||||||
children: tabs
|
children: tabs.map((tab) => _listItem(tab: tab)).toList(),
|
||||||
.asMap()
|
|
||||||
.entries
|
|
||||||
.map((tab) => _listItem(tab: tab.value, index: tab.key))
|
|
||||||
.toList(),
|
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _listItem({required _TabInfo tab, required int index}) {
|
Widget _listItem({required _TabInfo tab}) {
|
||||||
return Obx(() {
|
return Obx(() {
|
||||||
bool selected = index == selectedIndex.value;
|
bool selected = tab.key == selectedTab.value;
|
||||||
return SizedBox(
|
return SizedBox(
|
||||||
width: _kTabWidth,
|
width: _kTabWidth,
|
||||||
height: _kTabHeight,
|
height: _kTabHeight,
|
||||||
child: InkWell(
|
child: InkWell(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
if (selectedIndex.value != index) {
|
if (selectedTab.value != tab.key) {
|
||||||
|
int index = DesktopSettingPage.tabKeys.indexOf(tab.key);
|
||||||
|
if (index == -1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
controller.jumpToPage(index);
|
controller.jumpToPage(index);
|
||||||
}
|
}
|
||||||
selectedIndex.value = index;
|
selectedTab.value = tab.key;
|
||||||
},
|
},
|
||||||
child: Row(children: [
|
child: Row(children: [
|
||||||
Container(
|
Container(
|
||||||
@ -1117,7 +1163,7 @@ class _NetworkState extends State<_Network> with AutomaticKeepAliveClientMixin {
|
|||||||
child: Column(children: [
|
child: Column(children: [
|
||||||
server(enabled),
|
server(enabled),
|
||||||
_Card(title: 'Proxy', children: [
|
_Card(title: 'Proxy', children: [
|
||||||
_Button('Socks5 Proxy', changeSocks5Proxy,
|
_Button('Socks5/Http(s) Proxy', changeSocks5Proxy,
|
||||||
enabled: enabled),
|
enabled: enabled),
|
||||||
]),
|
]),
|
||||||
]),
|
]),
|
||||||
@ -2033,7 +2079,12 @@ void changeSocks5Proxy() async {
|
|||||||
password = pwdController.text.trim();
|
password = pwdController.text.trim();
|
||||||
|
|
||||||
if (proxy.isNotEmpty) {
|
if (proxy.isNotEmpty) {
|
||||||
proxyMsg = translate(await bind.mainTestIfValidServer(server: proxy));
|
String domainPort = proxy;
|
||||||
|
if (domainPort.contains('://')) {
|
||||||
|
domainPort = domainPort.split('://')[1];
|
||||||
|
}
|
||||||
|
proxyMsg = translate(await bind.mainTestIfValidServer(
|
||||||
|
server: domainPort, testWithProxy: false));
|
||||||
if (proxyMsg.isEmpty) {
|
if (proxyMsg.isEmpty) {
|
||||||
// ignore
|
// ignore
|
||||||
} else {
|
} else {
|
||||||
@ -2047,7 +2098,7 @@ void changeSocks5Proxy() async {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return CustomAlertDialog(
|
return CustomAlertDialog(
|
||||||
title: Text(translate('Socks5 Proxy')),
|
title: Text(translate('Socks5/Http(s) Proxy')),
|
||||||
content: ConstrainedBox(
|
content: ConstrainedBox(
|
||||||
constraints: const BoxConstraints(minWidth: 500),
|
constraints: const BoxConstraints(minWidth: 500),
|
||||||
child: Column(
|
child: Column(
|
||||||
@ -2064,7 +2115,10 @@ void changeSocks5Proxy() async {
|
|||||||
Expanded(
|
Expanded(
|
||||||
child: TextField(
|
child: TextField(
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
errorText: proxyMsg.isNotEmpty ? proxyMsg : null),
|
errorText: proxyMsg.isNotEmpty ? proxyMsg : null,
|
||||||
|
hintText: translate(
|
||||||
|
'Default protocol and port are Socks5 and 1080'),
|
||||||
|
),
|
||||||
controller: proxyController,
|
controller: proxyController,
|
||||||
autofocus: true,
|
autofocus: true,
|
||||||
),
|
),
|
||||||
|
|||||||
@ -17,7 +17,8 @@ class DesktopTabPage extends StatefulWidget {
|
|||||||
@override
|
@override
|
||||||
State<DesktopTabPage> createState() => _DesktopTabPageState();
|
State<DesktopTabPage> createState() => _DesktopTabPageState();
|
||||||
|
|
||||||
static void onAddSetting({int initialPage = 0}) {
|
static void onAddSetting(
|
||||||
|
{SettingsTabKey initialPage = SettingsTabKey.general}) {
|
||||||
try {
|
try {
|
||||||
DesktopTabController tabController = Get.find();
|
DesktopTabController tabController = Get.find();
|
||||||
tabController.add(TabInfo(
|
tabController.add(TabInfo(
|
||||||
@ -27,7 +28,7 @@ class DesktopTabPage extends StatefulWidget {
|
|||||||
unselectedIcon: Icons.build_outlined,
|
unselectedIcon: Icons.build_outlined,
|
||||||
page: DesktopSettingPage(
|
page: DesktopSettingPage(
|
||||||
key: const ValueKey(kTabLabelSettingPage),
|
key: const ValueKey(kTabLabelSettingPage),
|
||||||
initialPage: initialPage,
|
initialTabkey: initialPage,
|
||||||
)));
|
)));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
debugPrintStack(label: '$e');
|
debugPrintStack(label: '$e');
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_hbb/common.dart';
|
import 'package:flutter_hbb/common.dart';
|
||||||
import 'package:flutter_hbb/common/shared_state.dart';
|
import 'package:flutter_hbb/common/shared_state.dart';
|
||||||
import 'package:flutter_hbb/consts.dart';
|
import 'package:flutter_hbb/consts.dart';
|
||||||
|
import 'package:flutter_hbb/models/input_model.dart';
|
||||||
import 'package:flutter_hbb/models/state_model.dart';
|
import 'package:flutter_hbb/models/state_model.dart';
|
||||||
import 'package:flutter_hbb/desktop/pages/remote_page.dart';
|
import 'package:flutter_hbb/desktop/pages/remote_page.dart';
|
||||||
import 'package:flutter_hbb/desktop/widgets/remote_toolbar.dart';
|
import 'package:flutter_hbb/desktop/widgets/remote_toolbar.dart';
|
||||||
@ -107,107 +108,7 @@ class _ConnectionTabPageState extends State<ConnectionTabPage> {
|
|||||||
|
|
||||||
tabController.onRemoved = (_, id) => onRemoveId(id);
|
tabController.onRemoved = (_, id) => onRemoveId(id);
|
||||||
|
|
||||||
rustDeskWinManager.setMethodHandler((call, fromWindowId) async {
|
rustDeskWinManager.setMethodHandler(_remoteMethodHandler);
|
||||||
print(
|
|
||||||
"[Remote Page] call ${call.method} with args ${call.arguments} from window $fromWindowId");
|
|
||||||
|
|
||||||
dynamic returnValue;
|
|
||||||
// for simplify, just replace connectionId
|
|
||||||
if (call.method == kWindowEventNewRemoteDesktop) {
|
|
||||||
final args = jsonDecode(call.arguments);
|
|
||||||
final id = args['id'];
|
|
||||||
final switchUuid = args['switch_uuid'];
|
|
||||||
final sessionId = args['session_id'];
|
|
||||||
final tabWindowId = args['tab_window_id'];
|
|
||||||
final display = args['display'];
|
|
||||||
final displays = args['displays'];
|
|
||||||
final screenRect = parseParamScreenRect(args);
|
|
||||||
windowOnTop(windowId());
|
|
||||||
tryMoveToScreenAndSetFullscreen(screenRect);
|
|
||||||
if (tabController.length == 0) {
|
|
||||||
// Show the hidden window.
|
|
||||||
if (isMacOS && stateGlobal.closeOnFullscreen == true) {
|
|
||||||
stateGlobal.setFullscreen(true);
|
|
||||||
}
|
|
||||||
// Reset the state
|
|
||||||
stateGlobal.closeOnFullscreen = null;
|
|
||||||
}
|
|
||||||
ConnectionTypeState.init(id);
|
|
||||||
_toolbarState.setShow(
|
|
||||||
bind.mainGetUserDefaultOption(key: 'collapse_toolbar') != 'Y');
|
|
||||||
tabController.add(TabInfo(
|
|
||||||
key: id,
|
|
||||||
label: id,
|
|
||||||
selectedIcon: selectedIcon,
|
|
||||||
unselectedIcon: unselectedIcon,
|
|
||||||
onTabCloseButton: () => tabController.closeBy(id),
|
|
||||||
page: RemotePage(
|
|
||||||
key: ValueKey(id),
|
|
||||||
id: id,
|
|
||||||
sessionId: sessionId == null ? null : SessionID(sessionId),
|
|
||||||
tabWindowId: tabWindowId,
|
|
||||||
display: display,
|
|
||||||
displays: displays?.cast<int>(),
|
|
||||||
password: args['password'],
|
|
||||||
toolbarState: _toolbarState,
|
|
||||||
tabController: tabController,
|
|
||||||
switchUuid: switchUuid,
|
|
||||||
forceRelay: args['forceRelay'],
|
|
||||||
isSharedPassword: args['isSharedPassword'],
|
|
||||||
),
|
|
||||||
));
|
|
||||||
} else if (call.method == kWindowDisableGrabKeyboard) {
|
|
||||||
// ???
|
|
||||||
} else if (call.method == "onDestroy") {
|
|
||||||
tabController.clear();
|
|
||||||
} else if (call.method == kWindowActionRebuild) {
|
|
||||||
reloadCurrentWindow();
|
|
||||||
} else if (call.method == kWindowEventActiveSession) {
|
|
||||||
final jumpOk = tabController.jumpToByKey(call.arguments);
|
|
||||||
if (jumpOk) {
|
|
||||||
windowOnTop(windowId());
|
|
||||||
}
|
|
||||||
return jumpOk;
|
|
||||||
} else if (call.method == kWindowEventActiveDisplaySession) {
|
|
||||||
final args = jsonDecode(call.arguments);
|
|
||||||
final id = args['id'];
|
|
||||||
final display = args['display'];
|
|
||||||
final jumpOk = tabController.jumpToByKeyAndDisplay(id, display);
|
|
||||||
if (jumpOk) {
|
|
||||||
windowOnTop(windowId());
|
|
||||||
}
|
|
||||||
return jumpOk;
|
|
||||||
} else if (call.method == kWindowEventGetRemoteList) {
|
|
||||||
return tabController.state.value.tabs
|
|
||||||
.map((e) => e.key)
|
|
||||||
.toList()
|
|
||||||
.join(',');
|
|
||||||
} else if (call.method == kWindowEventGetSessionIdList) {
|
|
||||||
return tabController.state.value.tabs
|
|
||||||
.map((e) => '${e.key},${(e.page as RemotePage).ffi.sessionId}')
|
|
||||||
.toList()
|
|
||||||
.join(';');
|
|
||||||
} else if (call.method == kWindowEventGetCachedSessionData) {
|
|
||||||
// Ready to show new window and close old tab.
|
|
||||||
final args = jsonDecode(call.arguments);
|
|
||||||
final id = args['id'];
|
|
||||||
final close = args['close'];
|
|
||||||
try {
|
|
||||||
final remotePage = tabController.state.value.tabs
|
|
||||||
.firstWhere((tab) => tab.key == id)
|
|
||||||
.page as RemotePage;
|
|
||||||
returnValue = remotePage.ffi.ffiModel.cachedPeerData.toString();
|
|
||||||
} catch (e) {
|
|
||||||
debugPrint('Failed to get cached session data: $e');
|
|
||||||
}
|
|
||||||
if (close && returnValue != null) {
|
|
||||||
closeSessionOnDispose[id] = false;
|
|
||||||
tabController.closeBy(id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_update_remote_count();
|
|
||||||
return returnValue;
|
|
||||||
});
|
|
||||||
if (!_isScreenRectSet) {
|
if (!_isScreenRectSet) {
|
||||||
Future.delayed(Duration.zero, () {
|
Future.delayed(Duration.zero, () {
|
||||||
restoreWindowPosition(
|
restoreWindowPosition(
|
||||||
@ -499,4 +400,130 @@ class _ConnectionTabPageState extends State<ConnectionTabPage> {
|
|||||||
|
|
||||||
_update_remote_count() =>
|
_update_remote_count() =>
|
||||||
RemoteCountState.find().value = tabController.length;
|
RemoteCountState.find().value = tabController.length;
|
||||||
|
|
||||||
|
Future<dynamic> _remoteMethodHandler(call, fromWindowId) async {
|
||||||
|
print(
|
||||||
|
"[Remote Page] call ${call.method} with args ${call.arguments} from window $fromWindowId");
|
||||||
|
|
||||||
|
dynamic returnValue;
|
||||||
|
// for simplify, just replace connectionId
|
||||||
|
if (call.method == kWindowEventNewRemoteDesktop) {
|
||||||
|
final args = jsonDecode(call.arguments);
|
||||||
|
final id = args['id'];
|
||||||
|
final switchUuid = args['switch_uuid'];
|
||||||
|
final sessionId = args['session_id'];
|
||||||
|
final tabWindowId = args['tab_window_id'];
|
||||||
|
final display = args['display'];
|
||||||
|
final displays = args['displays'];
|
||||||
|
final screenRect = parseParamScreenRect(args);
|
||||||
|
windowOnTop(windowId());
|
||||||
|
tryMoveToScreenAndSetFullscreen(screenRect);
|
||||||
|
if (tabController.length == 0) {
|
||||||
|
// Show the hidden window.
|
||||||
|
if (isMacOS && stateGlobal.closeOnFullscreen == true) {
|
||||||
|
stateGlobal.setFullscreen(true);
|
||||||
|
}
|
||||||
|
// Reset the state
|
||||||
|
stateGlobal.closeOnFullscreen = null;
|
||||||
|
}
|
||||||
|
ConnectionTypeState.init(id);
|
||||||
|
_toolbarState.setShow(
|
||||||
|
bind.mainGetUserDefaultOption(key: 'collapse_toolbar') != 'Y');
|
||||||
|
tabController.add(TabInfo(
|
||||||
|
key: id,
|
||||||
|
label: id,
|
||||||
|
selectedIcon: selectedIcon,
|
||||||
|
unselectedIcon: unselectedIcon,
|
||||||
|
onTabCloseButton: () => tabController.closeBy(id),
|
||||||
|
page: RemotePage(
|
||||||
|
key: ValueKey(id),
|
||||||
|
id: id,
|
||||||
|
sessionId: sessionId == null ? null : SessionID(sessionId),
|
||||||
|
tabWindowId: tabWindowId,
|
||||||
|
display: display,
|
||||||
|
displays: displays?.cast<int>(),
|
||||||
|
password: args['password'],
|
||||||
|
toolbarState: _toolbarState,
|
||||||
|
tabController: tabController,
|
||||||
|
switchUuid: switchUuid,
|
||||||
|
forceRelay: args['forceRelay'],
|
||||||
|
isSharedPassword: args['isSharedPassword'],
|
||||||
|
),
|
||||||
|
));
|
||||||
|
} else if (call.method == kWindowDisableGrabKeyboard) {
|
||||||
|
// ???
|
||||||
|
} else if (call.method == "onDestroy") {
|
||||||
|
tabController.clear();
|
||||||
|
} else if (call.method == kWindowActionRebuild) {
|
||||||
|
reloadCurrentWindow();
|
||||||
|
} else if (call.method == kWindowEventActiveSession) {
|
||||||
|
final jumpOk = tabController.jumpToByKey(call.arguments);
|
||||||
|
if (jumpOk) {
|
||||||
|
windowOnTop(windowId());
|
||||||
|
}
|
||||||
|
return jumpOk;
|
||||||
|
} else if (call.method == kWindowEventActiveDisplaySession) {
|
||||||
|
final args = jsonDecode(call.arguments);
|
||||||
|
final id = args['id'];
|
||||||
|
final display = args['display'];
|
||||||
|
final jumpOk = tabController.jumpToByKeyAndDisplay(id, display);
|
||||||
|
if (jumpOk) {
|
||||||
|
windowOnTop(windowId());
|
||||||
|
}
|
||||||
|
return jumpOk;
|
||||||
|
} else if (call.method == kWindowEventGetRemoteList) {
|
||||||
|
return tabController.state.value.tabs
|
||||||
|
.map((e) => e.key)
|
||||||
|
.toList()
|
||||||
|
.join(',');
|
||||||
|
} else if (call.method == kWindowEventGetSessionIdList) {
|
||||||
|
return tabController.state.value.tabs
|
||||||
|
.map((e) => '${e.key},${(e.page as RemotePage).ffi.sessionId}')
|
||||||
|
.toList()
|
||||||
|
.join(';');
|
||||||
|
} else if (call.method == kWindowEventGetCachedSessionData) {
|
||||||
|
// Ready to show new window and close old tab.
|
||||||
|
final args = jsonDecode(call.arguments);
|
||||||
|
final id = args['id'];
|
||||||
|
final close = args['close'];
|
||||||
|
try {
|
||||||
|
final remotePage = tabController.state.value.tabs
|
||||||
|
.firstWhere((tab) => tab.key == id)
|
||||||
|
.page as RemotePage;
|
||||||
|
returnValue = remotePage.ffi.ffiModel.cachedPeerData.toString();
|
||||||
|
} catch (e) {
|
||||||
|
debugPrint('Failed to get cached session data: $e');
|
||||||
|
}
|
||||||
|
if (close && returnValue != null) {
|
||||||
|
closeSessionOnDispose[id] = false;
|
||||||
|
tabController.closeBy(id);
|
||||||
|
}
|
||||||
|
} else if (call.method == kWindowEventRemoteWindowCoords) {
|
||||||
|
final remotePage =
|
||||||
|
tabController.state.value.selectedTabInfo.page as RemotePage;
|
||||||
|
final ffi = remotePage.ffi;
|
||||||
|
final displayRect = ffi.ffiModel.displaysRect();
|
||||||
|
if (displayRect != null) {
|
||||||
|
final wc = WindowController.fromWindowId(windowId());
|
||||||
|
Rect? frame;
|
||||||
|
try {
|
||||||
|
frame = await wc.getFrame();
|
||||||
|
} catch (e) {
|
||||||
|
debugPrint(
|
||||||
|
"Failed to get frame of window $windowId, it may be hidden");
|
||||||
|
}
|
||||||
|
if (frame != null) {
|
||||||
|
ffi.cursorModel.moveLocal(0, 0);
|
||||||
|
final coords = RemoteWindowCoords(
|
||||||
|
frame,
|
||||||
|
CanvasCoords.fromCanvasModel(ffi.canvasModel),
|
||||||
|
CursorCoords.fromCursorModel(ffi.cursorModel),
|
||||||
|
displayRect);
|
||||||
|
returnValue = jsonEncode(coords.toJson());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_update_remote_count();
|
||||||
|
return returnValue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1045,7 +1045,6 @@ class _DisplayMenuState extends State<_DisplayMenu> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
_screenAdjustor.updateScreen();
|
_screenAdjustor.updateScreen();
|
||||||
|
|
||||||
menuChildrenGetter() {
|
menuChildrenGetter() {
|
||||||
final menuChildren = <Widget>[
|
final menuChildren = <Widget>[
|
||||||
_screenAdjustor.adjustWindow(context),
|
_screenAdjustor.adjustWindow(context),
|
||||||
@ -1069,6 +1068,8 @@ class _DisplayMenuState extends State<_DisplayMenu> {
|
|||||||
ffi: widget.ffi,
|
ffi: widget.ffi,
|
||||||
),
|
),
|
||||||
Divider(),
|
Divider(),
|
||||||
|
cursorToggles(),
|
||||||
|
Divider(),
|
||||||
toggles(),
|
toggles(),
|
||||||
];
|
];
|
||||||
// privacy mode
|
// privacy mode
|
||||||
@ -1212,6 +1213,23 @@ class _DisplayMenuState extends State<_DisplayMenu> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cursorToggles() {
|
||||||
|
return futureBuilder(
|
||||||
|
future: toolbarCursor(context, id, ffi),
|
||||||
|
hasData: (data) {
|
||||||
|
final v = data as List<TToggleMenu>;
|
||||||
|
if (v.isEmpty) return Offstage();
|
||||||
|
return Column(
|
||||||
|
children: v
|
||||||
|
.map((e) => CkbMenuButton(
|
||||||
|
value: e.value,
|
||||||
|
onChanged: e.onChanged,
|
||||||
|
child: e.child,
|
||||||
|
ffi: ffi))
|
||||||
|
.toList());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
toggles() {
|
toggles() {
|
||||||
return futureBuilder(
|
return futureBuilder(
|
||||||
future: toolbarDisplayToggle(context, id, ffi),
|
future: toolbarDisplayToggle(context, id, ffi),
|
||||||
|
|||||||
@ -836,6 +836,7 @@ void showOptions(
|
|||||||
List<TRadioMenu<String>> imageQualityRadios =
|
List<TRadioMenu<String>> imageQualityRadios =
|
||||||
await toolbarImageQuality(context, id, gFFI);
|
await toolbarImageQuality(context, id, gFFI);
|
||||||
List<TRadioMenu<String>> codecRadios = await toolbarCodec(context, id, gFFI);
|
List<TRadioMenu<String>> codecRadios = await toolbarCodec(context, id, gFFI);
|
||||||
|
List<TToggleMenu> cursorToggles = await toolbarCursor(context, id, gFFI);
|
||||||
List<TToggleMenu> displayToggles =
|
List<TToggleMenu> displayToggles =
|
||||||
await toolbarDisplayToggle(context, id, gFFI);
|
await toolbarDisplayToggle(context, id, gFFI);
|
||||||
|
|
||||||
@ -876,8 +877,23 @@ void showOptions(
|
|||||||
})),
|
})),
|
||||||
if (codecRadios.isNotEmpty) const Divider(color: MyTheme.border),
|
if (codecRadios.isNotEmpty) const Divider(color: MyTheme.border),
|
||||||
];
|
];
|
||||||
|
final rxCursorToggleValues = cursorToggles.map((e) => e.value.obs).toList();
|
||||||
|
final cursorTogglesList = cursorToggles
|
||||||
|
.asMap()
|
||||||
|
.entries
|
||||||
|
.map((e) => Obx(() => CheckboxListTile(
|
||||||
|
contentPadding: EdgeInsets.zero,
|
||||||
|
visualDensity: VisualDensity.compact,
|
||||||
|
value: rxCursorToggleValues[e.key].value,
|
||||||
|
onChanged: (v) {
|
||||||
|
e.value.onChanged?.call(v);
|
||||||
|
if (v != null) rxCursorToggleValues[e.key].value = v;
|
||||||
|
},
|
||||||
|
title: e.value.child)))
|
||||||
|
.toList();
|
||||||
|
|
||||||
final rxToggleValues = displayToggles.map((e) => e.value.obs).toList();
|
final rxToggleValues = displayToggles.map((e) => e.value.obs).toList();
|
||||||
final toggles = displayToggles
|
final displayTogglesList = displayToggles
|
||||||
.asMap()
|
.asMap()
|
||||||
.entries
|
.entries
|
||||||
.map((e) => Obx(() => CheckboxListTile(
|
.map((e) => Obx(() => CheckboxListTile(
|
||||||
@ -890,6 +906,11 @@ void showOptions(
|
|||||||
},
|
},
|
||||||
title: e.value.child)))
|
title: e.value.child)))
|
||||||
.toList();
|
.toList();
|
||||||
|
final toggles = [
|
||||||
|
...cursorTogglesList,
|
||||||
|
if (cursorToggles.isNotEmpty) const Divider(color: MyTheme.border),
|
||||||
|
...displayTogglesList,
|
||||||
|
];
|
||||||
|
|
||||||
Widget privacyModeWidget = Offstage();
|
Widget privacyModeWidget = Offstage();
|
||||||
if (privacyModeList.length > 1) {
|
if (privacyModeList.length > 1) {
|
||||||
|
|||||||
@ -283,12 +283,3 @@ void setPrivacyModeDialog(
|
|||||||
);
|
);
|
||||||
}, backDismiss: true, clickMaskDismiss: true);
|
}, backDismiss: true, clickMaskDismiss: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<String?> validateAsync(String value) async {
|
|
||||||
value = value.trim();
|
|
||||||
if (value.isEmpty) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
final res = await bind.mainTestIfValidServer(server: value);
|
|
||||||
return res.isEmpty ? null : res;
|
|
||||||
}
|
|
||||||
|
|||||||
@ -10,8 +10,8 @@ import 'package:flutter_hbb/models/peer_model.dart';
|
|||||||
import 'package:flutter_hbb/models/platform_model.dart';
|
import 'package:flutter_hbb/models/platform_model.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:bot_toast/bot_toast.dart';
|
import 'package:bot_toast/bot_toast.dart';
|
||||||
import 'package:http/http.dart' as http;
|
|
||||||
|
|
||||||
|
import '../utils/http_service.dart' as http;
|
||||||
import '../common.dart';
|
import '../common.dart';
|
||||||
|
|
||||||
final syncAbOption = 'sync-ab-with-recent-sessions';
|
final syncAbOption = 'sync-ab-with-recent-sessions';
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_gpu_texture_renderer/flutter_gpu_texture_renderer.dart';
|
import 'package:flutter_gpu_texture_renderer/flutter_gpu_texture_renderer.dart';
|
||||||
|
import 'package:flutter_hbb/common/shared_state.dart';
|
||||||
import 'package:flutter_hbb/consts.dart';
|
import 'package:flutter_hbb/consts.dart';
|
||||||
import 'package:flutter_hbb/models/model.dart';
|
import 'package:flutter_hbb/models/model.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
@ -152,40 +153,36 @@ class TextureModel {
|
|||||||
TextureModel(this.parent);
|
TextureModel(this.parent);
|
||||||
|
|
||||||
setTextureType({required int display, required bool gpuTexture}) {
|
setTextureType({required int display, required bool gpuTexture}) {
|
||||||
debugPrint("setTextureType: display:$display, isGpuTexture:$gpuTexture");
|
debugPrint("setTextureType: display=$display, isGpuTexture=$gpuTexture");
|
||||||
var texture = _control[display];
|
ensureControl(display);
|
||||||
if (texture == null) {
|
_control[display]?.setTextureType(gpuTexture: gpuTexture);
|
||||||
texture = _Control();
|
// For versions that do not support multiple displays, the display parameter is always 0, need set type of current display
|
||||||
_control[display] = texture;
|
final ffi = parent.target;
|
||||||
|
if (ffi == null) return;
|
||||||
|
if (!ffi.ffiModel.pi.isSupportMultiDisplay) {
|
||||||
|
final currentDisplay = CurrentDisplayState.find(ffi.id).value;
|
||||||
|
if (currentDisplay != display) {
|
||||||
|
debugPrint(
|
||||||
|
"setTextureType: currentDisplay=$currentDisplay, isGpuTexture=$gpuTexture");
|
||||||
|
ensureControl(currentDisplay);
|
||||||
|
_control[currentDisplay]?.setTextureType(gpuTexture: gpuTexture);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
texture.setTextureType(gpuTexture: gpuTexture);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setRgbaTextureId({required int display, required int id}) {
|
setRgbaTextureId({required int display, required int id}) {
|
||||||
var ctl = _control[display];
|
ensureControl(display);
|
||||||
if (ctl == null) {
|
_control[display]?.setRgbaTextureId(id);
|
||||||
ctl = _Control();
|
|
||||||
_control[display] = ctl;
|
|
||||||
}
|
|
||||||
ctl.setRgbaTextureId(id);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setGpuTextureId({required int display, required int id}) {
|
setGpuTextureId({required int display, required int id}) {
|
||||||
var ctl = _control[display];
|
ensureControl(display);
|
||||||
if (ctl == null) {
|
_control[display]?.setGpuTextureId(id);
|
||||||
ctl = _Control();
|
|
||||||
_control[display] = ctl;
|
|
||||||
}
|
|
||||||
ctl.setGpuTextureId(id);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
RxInt getTextureId(int display) {
|
RxInt getTextureId(int display) {
|
||||||
var ctl = _control[display];
|
ensureControl(display);
|
||||||
if (ctl == null) {
|
return _control[display]!.textureID;
|
||||||
ctl = _Control();
|
|
||||||
_control[display] = ctl;
|
|
||||||
}
|
|
||||||
return ctl.textureID;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
updateCurrentDisplay(int curDisplay) {
|
updateCurrentDisplay(int curDisplay) {
|
||||||
@ -241,4 +238,12 @@ class TextureModel {
|
|||||||
await texture.destroy(ffi);
|
await texture.destroy(ffi);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ensureControl(int display) {
|
||||||
|
var ctl = _control[display];
|
||||||
|
if (ctl == null) {
|
||||||
|
ctl = _Control();
|
||||||
|
_control[display] = ctl;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,7 +7,7 @@ import 'package:flutter_hbb/models/peer_model.dart';
|
|||||||
import 'package:flutter_hbb/models/platform_model.dart';
|
import 'package:flutter_hbb/models/platform_model.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'package:http/http.dart' as http;
|
import '../utils/http_service.dart' as http;
|
||||||
|
|
||||||
class GroupModel {
|
class GroupModel {
|
||||||
final RxBool groupLoading = false.obs;
|
final RxBool groupLoading = false.obs;
|
||||||
|
|||||||
@ -4,9 +4,12 @@ import 'dart:io';
|
|||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
import 'dart:ui' as ui;
|
import 'dart:ui' as ui;
|
||||||
|
|
||||||
|
import 'package:desktop_multi_window/desktop_multi_window.dart';
|
||||||
import 'package:flutter/gestures.dart';
|
import 'package:flutter/gestures.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
|
import 'package:flutter_hbb/main.dart';
|
||||||
|
import 'package:flutter_hbb/utils/multi_window_manager.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
|
|
||||||
import '../../models/model.dart';
|
import '../../models/model.dart';
|
||||||
@ -21,6 +24,128 @@ const _kMouseEventDown = 'mousedown';
|
|||||||
const _kMouseEventUp = 'mouseup';
|
const _kMouseEventUp = 'mouseup';
|
||||||
const _kMouseEventMove = 'mousemove';
|
const _kMouseEventMove = 'mousemove';
|
||||||
|
|
||||||
|
class CanvasCoords {
|
||||||
|
double x = 0;
|
||||||
|
double y = 0;
|
||||||
|
double scale = 1.0;
|
||||||
|
double scrollX = 0;
|
||||||
|
double scrollY = 0;
|
||||||
|
ScrollStyle scrollStyle = ScrollStyle.scrollauto;
|
||||||
|
Size size = Size.zero;
|
||||||
|
|
||||||
|
CanvasCoords();
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return {
|
||||||
|
'x': x,
|
||||||
|
'y': y,
|
||||||
|
'scale': scale,
|
||||||
|
'scrollX': scrollX,
|
||||||
|
'scrollY': scrollY,
|
||||||
|
'scrollStyle':
|
||||||
|
scrollStyle == ScrollStyle.scrollauto ? 'scrollauto' : 'scrollbar',
|
||||||
|
'size': {
|
||||||
|
'w': size.width,
|
||||||
|
'h': size.height,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static CanvasCoords fromJson(Map<String, dynamic> json) {
|
||||||
|
final model = CanvasCoords();
|
||||||
|
model.x = json['x'];
|
||||||
|
model.y = json['y'];
|
||||||
|
model.scale = json['scale'];
|
||||||
|
model.scrollX = json['scrollX'];
|
||||||
|
model.scrollY = json['scrollY'];
|
||||||
|
model.scrollStyle = json['scrollStyle'] == 'scrollauto'
|
||||||
|
? ScrollStyle.scrollauto
|
||||||
|
: ScrollStyle.scrollbar;
|
||||||
|
model.size = Size(json['size']['w'], json['size']['h']);
|
||||||
|
return model;
|
||||||
|
}
|
||||||
|
|
||||||
|
static CanvasCoords fromCanvasModel(CanvasModel model) {
|
||||||
|
final coords = CanvasCoords();
|
||||||
|
coords.x = model.x;
|
||||||
|
coords.y = model.y;
|
||||||
|
coords.scale = model.scale;
|
||||||
|
coords.scrollX = model.scrollX;
|
||||||
|
coords.scrollY = model.scrollY;
|
||||||
|
coords.scrollStyle = model.scrollStyle;
|
||||||
|
coords.size = model.size;
|
||||||
|
return coords;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class CursorCoords {
|
||||||
|
Offset offset = Offset.zero;
|
||||||
|
|
||||||
|
CursorCoords();
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return {
|
||||||
|
'offset_x': offset.dx,
|
||||||
|
'offset_y': offset.dy,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static CursorCoords fromJson(Map<String, dynamic> json) {
|
||||||
|
final model = CursorCoords();
|
||||||
|
model.offset = Offset(json['offset_x'], json['offset_y']);
|
||||||
|
return model;
|
||||||
|
}
|
||||||
|
|
||||||
|
static CursorCoords fromCursorModel(CursorModel model) {
|
||||||
|
final coords = CursorCoords();
|
||||||
|
coords.offset = model.offset;
|
||||||
|
return coords;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class RemoteWindowCoords {
|
||||||
|
RemoteWindowCoords(
|
||||||
|
this.windowRect, this.canvas, this.cursor, this.remoteRect);
|
||||||
|
Rect windowRect;
|
||||||
|
CanvasCoords canvas;
|
||||||
|
CursorCoords cursor;
|
||||||
|
Rect remoteRect;
|
||||||
|
Offset relativeOffset = Offset.zero;
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return {
|
||||||
|
'canvas': canvas.toJson(),
|
||||||
|
'cursor': cursor.toJson(),
|
||||||
|
'windowRect': rectToJson(windowRect),
|
||||||
|
'remoteRect': rectToJson(remoteRect),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static Map<String, dynamic> rectToJson(Rect r) {
|
||||||
|
return {
|
||||||
|
'l': r.left,
|
||||||
|
't': r.top,
|
||||||
|
'w': r.width,
|
||||||
|
'h': r.height,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static Rect rectFromJson(Map<String, dynamic> json) {
|
||||||
|
return Rect.fromLTWH(
|
||||||
|
json['l'],
|
||||||
|
json['t'],
|
||||||
|
json['w'],
|
||||||
|
json['h'],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
RemoteWindowCoords.fromJson(Map<String, dynamic> json)
|
||||||
|
: windowRect = rectFromJson(json['windowRect']),
|
||||||
|
canvas = CanvasCoords.fromJson(json['canvas']),
|
||||||
|
cursor = CursorCoords.fromJson(json['cursor']),
|
||||||
|
remoteRect = rectFromJson(json['remoteRect']);
|
||||||
|
}
|
||||||
|
|
||||||
extension ToString on MouseButtons {
|
extension ToString on MouseButtons {
|
||||||
String get value {
|
String get value {
|
||||||
switch (this) {
|
switch (this) {
|
||||||
@ -188,12 +313,17 @@ class InputModel {
|
|||||||
int _lastButtons = 0;
|
int _lastButtons = 0;
|
||||||
Offset lastMousePos = Offset.zero;
|
Offset lastMousePos = Offset.zero;
|
||||||
|
|
||||||
|
bool _queryOtherWindowCoords = false;
|
||||||
|
Rect? _windowRect;
|
||||||
|
List<RemoteWindowCoords> _remoteWindowCoords = [];
|
||||||
|
|
||||||
late final SessionID sessionId;
|
late final SessionID sessionId;
|
||||||
|
|
||||||
bool get keyboardPerm => parent.target!.ffiModel.keyboard;
|
bool get keyboardPerm => parent.target!.ffiModel.keyboard;
|
||||||
String get id => parent.target?.id ?? '';
|
String get id => parent.target?.id ?? '';
|
||||||
String? get peerPlatform => parent.target?.ffiModel.pi.platform;
|
String? get peerPlatform => parent.target?.ffiModel.pi.platform;
|
||||||
bool get isViewOnly => parent.target!.ffiModel.viewOnly;
|
bool get isViewOnly => parent.target!.ffiModel.viewOnly;
|
||||||
|
double get devicePixelRatio => parent.target!.canvasModel.devicePixelRatio;
|
||||||
|
|
||||||
InputModel(this.parent) {
|
InputModel(this.parent) {
|
||||||
sessionId = parent.target!.sessionId;
|
sessionId = parent.target!.sessionId;
|
||||||
@ -616,6 +746,9 @@ class InputModel {
|
|||||||
void onPointDownImage(PointerDownEvent e) {
|
void onPointDownImage(PointerDownEvent e) {
|
||||||
debugPrint("onPointDownImage ${e.kind}");
|
debugPrint("onPointDownImage ${e.kind}");
|
||||||
_stopFling = true;
|
_stopFling = true;
|
||||||
|
if (isDesktop) _queryOtherWindowCoords = true;
|
||||||
|
_remoteWindowCoords = [];
|
||||||
|
_windowRect = null;
|
||||||
if (isViewOnly) return;
|
if (isViewOnly) return;
|
||||||
if (e.kind != ui.PointerDeviceKind.mouse) {
|
if (e.kind != ui.PointerDeviceKind.mouse) {
|
||||||
if (isPhysicalMouse.value) {
|
if (isPhysicalMouse.value) {
|
||||||
@ -628,6 +761,7 @@ class InputModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void onPointUpImage(PointerUpEvent e) {
|
void onPointUpImage(PointerUpEvent e) {
|
||||||
|
if (isDesktop) _queryOtherWindowCoords = false;
|
||||||
if (isViewOnly) return;
|
if (isViewOnly) return;
|
||||||
if (e.kind != ui.PointerDeviceKind.mouse) return;
|
if (e.kind != ui.PointerDeviceKind.mouse) return;
|
||||||
if (isPhysicalMouse.value) {
|
if (isPhysicalMouse.value) {
|
||||||
@ -638,11 +772,37 @@ class InputModel {
|
|||||||
void onPointMoveImage(PointerMoveEvent e) {
|
void onPointMoveImage(PointerMoveEvent e) {
|
||||||
if (isViewOnly) return;
|
if (isViewOnly) return;
|
||||||
if (e.kind != ui.PointerDeviceKind.mouse) return;
|
if (e.kind != ui.PointerDeviceKind.mouse) return;
|
||||||
|
if (_queryOtherWindowCoords) {
|
||||||
|
Future.delayed(Duration.zero, () async {
|
||||||
|
_windowRect = await fillRemoteCoordsAndGetCurFrame(_remoteWindowCoords);
|
||||||
|
});
|
||||||
|
_queryOtherWindowCoords = false;
|
||||||
|
}
|
||||||
if (isPhysicalMouse.value) {
|
if (isPhysicalMouse.value) {
|
||||||
handleMouse(_getMouseEvent(e, _kMouseEventMove), e.position);
|
handleMouse(_getMouseEvent(e, _kMouseEventMove), e.position);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static Future<Rect?> fillRemoteCoordsAndGetCurFrame(
|
||||||
|
List<RemoteWindowCoords> remoteWindowCoords) async {
|
||||||
|
final coords =
|
||||||
|
await rustDeskWinManager.getOtherRemoteWindowCoordsFromMain();
|
||||||
|
final wc = WindowController.fromWindowId(kWindowId!);
|
||||||
|
try {
|
||||||
|
final frame = await wc.getFrame();
|
||||||
|
for (final c in coords) {
|
||||||
|
c.relativeOffset = Offset(
|
||||||
|
c.windowRect.left - frame.left, c.windowRect.top - frame.top);
|
||||||
|
remoteWindowCoords.add(c);
|
||||||
|
}
|
||||||
|
return frame;
|
||||||
|
} catch (e) {
|
||||||
|
// Unreachable code
|
||||||
|
debugPrint("Failed to get frame of window $kWindowId, it may be hidden");
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
void onPointerSignalImage(PointerSignalEvent e) {
|
void onPointerSignalImage(PointerSignalEvent e) {
|
||||||
if (isViewOnly) return;
|
if (isViewOnly) return;
|
||||||
if (e is PointerScrollEvent) {
|
if (e is PointerScrollEvent) {
|
||||||
@ -843,43 +1003,107 @@ class InputModel {
|
|||||||
bool onExit = false,
|
bool onExit = false,
|
||||||
int buttons = kPrimaryMouseButton,
|
int buttons = kPrimaryMouseButton,
|
||||||
}) {
|
}) {
|
||||||
y -= CanvasModel.topToEdge;
|
|
||||||
x -= CanvasModel.leftToEdge;
|
|
||||||
final canvasModel = parent.target!.canvasModel;
|
|
||||||
final ffiModel = parent.target!.ffiModel;
|
final ffiModel = parent.target!.ffiModel;
|
||||||
|
CanvasCoords canvas =
|
||||||
|
CanvasCoords.fromCanvasModel(parent.target!.canvasModel);
|
||||||
|
Rect? rect = ffiModel.rect;
|
||||||
|
|
||||||
if (isMove) {
|
if (isMove) {
|
||||||
canvasModel.moveDesktopMouse(x, y);
|
if (_remoteWindowCoords.isNotEmpty &&
|
||||||
|
_windowRect != null &&
|
||||||
|
!_isInCurrentWindow(x, y)) {
|
||||||
|
final coords =
|
||||||
|
findRemoteCoords(x, y, _remoteWindowCoords, devicePixelRatio);
|
||||||
|
if (coords != null) {
|
||||||
|
isMove = false;
|
||||||
|
canvas = coords.canvas;
|
||||||
|
rect = coords.remoteRect;
|
||||||
|
x -= coords.relativeOffset.dx / devicePixelRatio;
|
||||||
|
y -= coords.relativeOffset.dy / devicePixelRatio;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final nearThr = 3;
|
y -= CanvasModel.topToEdge;
|
||||||
var nearRight = (canvasModel.size.width - x) < nearThr;
|
x -= CanvasModel.leftToEdge;
|
||||||
var nearBottom = (canvasModel.size.height - y) < nearThr;
|
if (isMove) {
|
||||||
final rect = ffiModel.rect;
|
parent.target!.canvasModel.moveDesktopMouse(x, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
return _handlePointerDevicePos(
|
||||||
|
kind,
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
isMove,
|
||||||
|
canvas,
|
||||||
|
rect,
|
||||||
|
evtType,
|
||||||
|
onExit: onExit,
|
||||||
|
buttons: buttons,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool _isInCurrentWindow(double x, double y) {
|
||||||
|
final w = _windowRect!.width / devicePixelRatio;
|
||||||
|
final h = _windowRect!.width / 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;
|
||||||
|
for (final c in remoteWindowCoords) {
|
||||||
|
if (x >= c.relativeOffset.dx &&
|
||||||
|
y >= c.relativeOffset.dy &&
|
||||||
|
x <= c.relativeOffset.dx + c.windowRect.width &&
|
||||||
|
y <= c.relativeOffset.dy + c.windowRect.height) {
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Point? _handlePointerDevicePos(
|
||||||
|
String kind,
|
||||||
|
double x,
|
||||||
|
double y,
|
||||||
|
bool moveInCanvas,
|
||||||
|
CanvasCoords canvas,
|
||||||
|
Rect? rect,
|
||||||
|
String evtType, {
|
||||||
|
bool onExit = false,
|
||||||
|
int buttons = kPrimaryMouseButton,
|
||||||
|
}) {
|
||||||
if (rect == null) {
|
if (rect == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
final imageWidth = rect.width * canvasModel.scale;
|
|
||||||
final imageHeight = rect.height * canvasModel.scale;
|
final nearThr = 3;
|
||||||
if (canvasModel.scrollStyle == ScrollStyle.scrollbar) {
|
var nearRight = (canvas.size.width - x) < nearThr;
|
||||||
x += imageWidth * canvasModel.scrollX;
|
var nearBottom = (canvas.size.height - y) < nearThr;
|
||||||
y += imageHeight * canvasModel.scrollY;
|
final imageWidth = rect.width * canvas.scale;
|
||||||
|
final imageHeight = rect.height * canvas.scale;
|
||||||
|
if (canvas.scrollStyle == ScrollStyle.scrollbar) {
|
||||||
|
x += imageWidth * canvas.scrollX;
|
||||||
|
y += imageHeight * canvas.scrollY;
|
||||||
|
|
||||||
// boxed size is a center widget
|
// boxed size is a center widget
|
||||||
if (canvasModel.size.width > imageWidth) {
|
if (canvas.size.width > imageWidth) {
|
||||||
x -= ((canvasModel.size.width - imageWidth) / 2);
|
x -= ((canvas.size.width - imageWidth) / 2);
|
||||||
}
|
}
|
||||||
if (canvasModel.size.height > imageHeight) {
|
if (canvas.size.height > imageHeight) {
|
||||||
y -= ((canvasModel.size.height - imageHeight) / 2);
|
y -= ((canvas.size.height - imageHeight) / 2);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
x -= canvasModel.x;
|
x -= canvas.x;
|
||||||
y -= canvasModel.y;
|
y -= canvas.y;
|
||||||
}
|
}
|
||||||
|
|
||||||
x /= canvasModel.scale;
|
x /= canvas.scale;
|
||||||
y /= canvasModel.scale;
|
y /= canvas.scale;
|
||||||
if (canvasModel.scale > 0 && canvasModel.scale < 1) {
|
if (canvas.scale > 0 && canvas.scale < 1) {
|
||||||
final step = 1.0 / canvasModel.scale - 1;
|
final step = 1.0 / canvas.scale - 1;
|
||||||
if (nearRight) {
|
if (nearRight) {
|
||||||
x += step;
|
x += step;
|
||||||
}
|
}
|
||||||
@ -902,8 +1126,7 @@ class InputModel {
|
|||||||
evtX = x.round();
|
evtX = x.round();
|
||||||
evtY = y.round();
|
evtY = y.round();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
debugPrintStack(
|
debugPrintStack(label: 'canvas.scale value ${canvas.scale}, $e');
|
||||||
label: 'canvasModel.scale value ${canvasModel.scale}, $e');
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -50,6 +50,8 @@ class CachedPeerData {
|
|||||||
Map<String, dynamic> peerInfo = {};
|
Map<String, dynamic> peerInfo = {};
|
||||||
List<Map<String, dynamic>> cursorDataList = [];
|
List<Map<String, dynamic>> cursorDataList = [];
|
||||||
Map<String, dynamic> lastCursorId = {};
|
Map<String, dynamic> lastCursorId = {};
|
||||||
|
Map<String, bool> permissions = {};
|
||||||
|
|
||||||
bool secure = false;
|
bool secure = false;
|
||||||
bool direct = false;
|
bool direct = false;
|
||||||
|
|
||||||
@ -62,6 +64,7 @@ class CachedPeerData {
|
|||||||
'peerInfo': peerInfo,
|
'peerInfo': peerInfo,
|
||||||
'cursorDataList': cursorDataList,
|
'cursorDataList': cursorDataList,
|
||||||
'lastCursorId': lastCursorId,
|
'lastCursorId': lastCursorId,
|
||||||
|
'permissions': permissions,
|
||||||
'secure': secure,
|
'secure': secure,
|
||||||
'direct': direct,
|
'direct': direct,
|
||||||
});
|
});
|
||||||
@ -77,6 +80,9 @@ class CachedPeerData {
|
|||||||
data.cursorDataList.add(cursorData);
|
data.cursorDataList.add(cursorData);
|
||||||
}
|
}
|
||||||
data.lastCursorId = map['lastCursorId'];
|
data.lastCursorId = map['lastCursorId'];
|
||||||
|
map['permissions'].forEach((key, value) {
|
||||||
|
data.permissions[key] = value;
|
||||||
|
});
|
||||||
data.secure = map['secure'];
|
data.secure = map['secure'];
|
||||||
data.direct = map['direct'];
|
data.direct = map['direct'];
|
||||||
return data;
|
return data;
|
||||||
@ -116,6 +122,10 @@ class FfiModel with ChangeNotifier {
|
|||||||
_pi.tryGetDisplayIfNotAllDisplay()?.isOriginalResolution ?? false;
|
_pi.tryGetDisplayIfNotAllDisplay()?.isOriginalResolution ?? false;
|
||||||
|
|
||||||
Map<String, bool> get permissions => _permissions;
|
Map<String, bool> get permissions => _permissions;
|
||||||
|
setPermissions(Map<String, bool> permissions) {
|
||||||
|
_permissions.clear();
|
||||||
|
_permissions.addAll(permissions);
|
||||||
|
}
|
||||||
|
|
||||||
bool? get secure => _secure;
|
bool? get secure => _secure;
|
||||||
|
|
||||||
@ -138,6 +148,7 @@ class FfiModel with ChangeNotifier {
|
|||||||
FfiModel(this.parent) {
|
FfiModel(this.parent) {
|
||||||
clear();
|
clear();
|
||||||
sessionId = parent.target!.sessionId;
|
sessionId = parent.target!.sessionId;
|
||||||
|
cachedPeerData.permissions = _permissions;
|
||||||
}
|
}
|
||||||
|
|
||||||
Rect? globalDisplaysRect() => _getDisplaysRect(_pi.displays, true);
|
Rect? globalDisplaysRect() => _getDisplaysRect(_pi.displays, true);
|
||||||
@ -367,6 +378,8 @@ class FfiModel with ChangeNotifier {
|
|||||||
}
|
}
|
||||||
} else if (name == 'sync_peer_option') {
|
} else if (name == 'sync_peer_option') {
|
||||||
_handleSyncPeerOption(evt, peerId);
|
_handleSyncPeerOption(evt, peerId);
|
||||||
|
} else if (name == 'follow_current_display') {
|
||||||
|
handleFollowCurrentDisplay(evt, sessionId, peerId);
|
||||||
} else {
|
} else {
|
||||||
debugPrint('Unknown event name: $name');
|
debugPrint('Unknown event name: $name');
|
||||||
}
|
}
|
||||||
@ -440,7 +453,7 @@ class FfiModel with ChangeNotifier {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
updateCurDisplay(SessionID sessionId, {updateCursorPos = true}) {
|
updateCurDisplay(SessionID sessionId, {updateCursorPos = false}) {
|
||||||
final newRect = displaysRect();
|
final newRect = displaysRect();
|
||||||
if (newRect == null) {
|
if (newRect == null) {
|
||||||
return;
|
return;
|
||||||
@ -1004,6 +1017,8 @@ class FfiModel with ChangeNotifier {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
parent.target!.canvasModel
|
||||||
|
.tryUpdateScrollStyle(Duration(milliseconds: 300), null);
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1040,9 +1055,30 @@ class FfiModel with ChangeNotifier {
|
|||||||
json.encode(_pi.platformAdditions);
|
json.encode(_pi.platformAdditions);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleFollowCurrentDisplay(
|
||||||
|
Map<String, dynamic> evt, SessionID sessionId, String peerId) async {
|
||||||
|
if (evt['display_idx'] != null) {
|
||||||
|
if (pi.currentDisplay == kAllDisplayValue) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_pi.currentDisplay = int.parse(evt['display_idx']);
|
||||||
|
try {
|
||||||
|
CurrentDisplayState.find(peerId).value = _pi.currentDisplay;
|
||||||
|
} catch (e) {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
bind.sessionSwitchDisplay(
|
||||||
|
isDesktop: isDesktop,
|
||||||
|
sessionId: sessionId,
|
||||||
|
value: Int32List.fromList([_pi.currentDisplay]),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
// Directly switch to the new display without waiting for the response.
|
// Directly switch to the new display without waiting for the response.
|
||||||
switchToNewDisplay(int display, SessionID sessionId, String peerId,
|
switchToNewDisplay(int display, SessionID sessionId, String peerId,
|
||||||
{bool updateCursorPos = true}) {
|
{bool updateCursorPos = false}) {
|
||||||
// VideoHandler creation is upon when video frames are received, so either caching commands(don't know next width/height) or stopping recording when switching displays.
|
// VideoHandler creation is upon when video frames are received, so either caching commands(don't know next width/height) or stopping recording when switching displays.
|
||||||
parent.target?.recordingModel.onClose();
|
parent.target?.recordingModel.onClose();
|
||||||
// no need to wait for the response
|
// no need to wait for the response
|
||||||
@ -1378,10 +1414,20 @@ class CanvasModel with ChangeNotifier {
|
|||||||
if (refreshMousePos) {
|
if (refreshMousePos) {
|
||||||
parent.target?.inputModel.refreshMousePos();
|
parent.target?.inputModel.refreshMousePos();
|
||||||
}
|
}
|
||||||
if (style == kRemoteViewStyleOriginal &&
|
tryUpdateScrollStyle(Duration.zero, style);
|
||||||
_scrollStyle == ScrollStyle.scrollbar) {
|
}
|
||||||
updateScrollPercent();
|
|
||||||
|
tryUpdateScrollStyle(Duration duration, String? style) async {
|
||||||
|
if (_scrollStyle != ScrollStyle.scrollbar) return;
|
||||||
|
style ??= await bind.sessionGetViewStyle(sessionId: sessionId);
|
||||||
|
if (style != kRemoteViewStyleOriginal) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_resetScroll();
|
||||||
|
Future.delayed(duration, () async {
|
||||||
|
updateScrollPercent();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
updateScrollStyle() async {
|
updateScrollStyle() async {
|
||||||
@ -1695,6 +1741,8 @@ class CursorModel with ChangeNotifier {
|
|||||||
double _displayOriginX = 0;
|
double _displayOriginX = 0;
|
||||||
double _displayOriginY = 0;
|
double _displayOriginY = 0;
|
||||||
DateTime? _firstUpdateMouseTime;
|
DateTime? _firstUpdateMouseTime;
|
||||||
|
Rect? _windowRect;
|
||||||
|
List<RemoteWindowCoords> _remoteWindowCoords = [];
|
||||||
bool gotMouseControl = true;
|
bool gotMouseControl = true;
|
||||||
DateTime _lastPeerMouse = DateTime.now()
|
DateTime _lastPeerMouse = DateTime.now()
|
||||||
.subtract(Duration(milliseconds: 3000 * kMouseControlTimeoutMSec));
|
.subtract(Duration(milliseconds: 3000 * kMouseControlTimeoutMSec));
|
||||||
@ -1707,6 +1755,8 @@ class CursorModel with ChangeNotifier {
|
|||||||
double get x => _x - _displayOriginX;
|
double get x => _x - _displayOriginX;
|
||||||
double get y => _y - _displayOriginY;
|
double get y => _y - _displayOriginY;
|
||||||
|
|
||||||
|
double get devicePixelRatio => parent.target!.canvasModel.devicePixelRatio;
|
||||||
|
|
||||||
Offset get offset => Offset(_x, _y);
|
Offset get offset => Offset(_x, _y);
|
||||||
|
|
||||||
double get hotx => _hotx;
|
double get hotx => _hotx;
|
||||||
@ -1776,15 +1826,13 @@ class CursorModel with ChangeNotifier {
|
|||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
updatePan(double dx, double dy, bool touchMode) {
|
updatePan(Offset delta, Offset localPosition, bool touchMode) {
|
||||||
if (touchMode) {
|
if (touchMode) {
|
||||||
final scale = parent.target?.canvasModel.scale ?? 1.0;
|
_handleTouchMode(delta, localPosition);
|
||||||
_x += dx / scale;
|
|
||||||
_y += dy / scale;
|
|
||||||
parent.target?.inputModel.moveMouse(_x, _y);
|
|
||||||
notifyListeners();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
double dx = delta.dx;
|
||||||
|
double dy = delta.dy;
|
||||||
if (parent.target?.imageModel.image == null) return;
|
if (parent.target?.imageModel.image == null) return;
|
||||||
final scale = parent.target?.canvasModel.scale ?? 1.0;
|
final scale = parent.target?.canvasModel.scale ?? 1.0;
|
||||||
dx /= scale;
|
dx /= scale;
|
||||||
@ -1851,6 +1899,41 @@ class CursorModel with ChangeNotifier {
|
|||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool _isInCurrentWindow(double x, double y) {
|
||||||
|
final w = _windowRect!.width / devicePixelRatio;
|
||||||
|
final h = _windowRect!.width / devicePixelRatio;
|
||||||
|
return x >= 0 && y >= 0 && x <= w && y <= h;
|
||||||
|
}
|
||||||
|
|
||||||
|
_handleTouchMode(Offset delta, Offset localPosition) {
|
||||||
|
bool isMoved = false;
|
||||||
|
if (_remoteWindowCoords.isNotEmpty &&
|
||||||
|
_windowRect != null &&
|
||||||
|
!_isInCurrentWindow(localPosition.dx, localPosition.dy)) {
|
||||||
|
final coords = InputModel.findRemoteCoords(localPosition.dx,
|
||||||
|
localPosition.dy, _remoteWindowCoords, devicePixelRatio);
|
||||||
|
if (coords != null) {
|
||||||
|
double x2 =
|
||||||
|
(localPosition.dx - coords.relativeOffset.dx / devicePixelRatio) /
|
||||||
|
coords.canvas.scale;
|
||||||
|
double y2 =
|
||||||
|
(localPosition.dy - coords.relativeOffset.dy / devicePixelRatio) /
|
||||||
|
coords.canvas.scale;
|
||||||
|
x2 += coords.cursor.offset.dx;
|
||||||
|
y2 += coords.cursor.offset.dy;
|
||||||
|
parent.target?.inputModel.moveMouse(x2, y2);
|
||||||
|
isMoved = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!isMoved) {
|
||||||
|
final scale = parent.target?.canvasModel.scale ?? 1.0;
|
||||||
|
_x += delta.dx / scale;
|
||||||
|
_y += delta.dy / scale;
|
||||||
|
parent.target?.inputModel.moveMouse(_x, _y);
|
||||||
|
}
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
updateCursorData(Map<String, dynamic> evt) async {
|
updateCursorData(Map<String, dynamic> evt) async {
|
||||||
final id = int.parse(evt['id']);
|
final id = int.parse(evt['id']);
|
||||||
final hotx = double.parse(evt['hotx']);
|
final hotx = double.parse(evt['hotx']);
|
||||||
@ -1990,6 +2073,18 @@ class CursorModel with ChangeNotifier {
|
|||||||
deleteCustomCursor(k);
|
deleteCustomCursor(k);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
trySetRemoteWindowCoords() {
|
||||||
|
Future.delayed(Duration.zero, () async {
|
||||||
|
_windowRect =
|
||||||
|
await InputModel.fillRemoteCoordsAndGetCurFrame(_remoteWindowCoords);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
clearRemoteWindowCoords() {
|
||||||
|
_windowRect = null;
|
||||||
|
_remoteWindowCoords.clear();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class QualityMonitorData {
|
class QualityMonitorData {
|
||||||
@ -2319,6 +2414,7 @@ class FFI {
|
|||||||
debugPrint('Unreachable, the cached data cannot be decoded.');
|
debugPrint('Unreachable, the cached data cannot be decoded.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
ffiModel.setPermissions(data.permissions);
|
||||||
await ffiModel.handleCachedPeerData(data, id);
|
await ffiModel.handleCachedPeerData(data, id);
|
||||||
await sessionRefreshVideo(sessionId, ffiModel.pi);
|
await sessionRefreshVideo(sessionId, ffiModel.pi);
|
||||||
await bind.sessionRequestNewDisplayInitMsgs(
|
await bind.sessionRequestNewDisplayInitMsgs(
|
||||||
|
|||||||
@ -6,9 +6,9 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_hbb/common/hbbs/hbbs.dart';
|
import 'package:flutter_hbb/common/hbbs/hbbs.dart';
|
||||||
import 'package:flutter_hbb/models/ab_model.dart';
|
import 'package:flutter_hbb/models/ab_model.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:http/http.dart' as http;
|
|
||||||
|
|
||||||
import '../common.dart';
|
import '../common.dart';
|
||||||
|
import '../utils/http_service.dart' as http;
|
||||||
import 'model.dart';
|
import 'model.dart';
|
||||||
import 'platform_model.dart';
|
import 'platform_model.dart';
|
||||||
|
|
||||||
@ -136,7 +136,6 @@ class UserModel {
|
|||||||
Future<LoginResponse> login(LoginRequest loginRequest) async {
|
Future<LoginResponse> login(LoginRequest loginRequest) async {
|
||||||
final url = await bind.mainGetApiServer();
|
final url = await bind.mainGetApiServer();
|
||||||
final resp = await http.post(Uri.parse('$url/api/login'),
|
final resp = await http.post(Uri.parse('$url/api/login'),
|
||||||
headers: {'Content-Type': 'application/json'},
|
|
||||||
body: jsonEncode(loginRequest.toJson()));
|
body: jsonEncode(loginRequest.toJson()));
|
||||||
|
|
||||||
final Map<String, dynamic> body;
|
final Map<String, dynamic> body;
|
||||||
|
|||||||
115
flutter/lib/utils/http_service.dart
Normal file
115
flutter/lib/utils/http_service.dart
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:http/http.dart' as http;
|
||||||
|
import '../models/platform_model.dart';
|
||||||
|
export 'package:http/http.dart' show Response;
|
||||||
|
|
||||||
|
enum HttpMethod { get, post, put, delete }
|
||||||
|
|
||||||
|
class HttpService {
|
||||||
|
Future<http.Response> sendRequest(
|
||||||
|
Uri url,
|
||||||
|
HttpMethod method, {
|
||||||
|
Map<String, String>? headers,
|
||||||
|
dynamic body,
|
||||||
|
}) async {
|
||||||
|
headers ??= {'Content-Type': 'application/json'};
|
||||||
|
|
||||||
|
// Determine if there is currently a proxy setting, and if so, use FFI to call the Rust HTTP method.
|
||||||
|
final isProxy = await bind.mainGetProxyStatus();
|
||||||
|
|
||||||
|
if (!isProxy) {
|
||||||
|
return await _pollFultterHttp(url, method, headers: headers, body: body);
|
||||||
|
}
|
||||||
|
|
||||||
|
String headersJson = jsonEncode(headers);
|
||||||
|
String methodName = method.toString().split('.').last;
|
||||||
|
await bind.mainHttpRequest(
|
||||||
|
url: url.toString(),
|
||||||
|
method: methodName.toLowerCase(),
|
||||||
|
body: body,
|
||||||
|
header: headersJson);
|
||||||
|
|
||||||
|
var resJson = await _pollForResponse(url.toString());
|
||||||
|
return _parseHttpResponse(resJson);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<http.Response> _pollFultterHttp(
|
||||||
|
Uri url,
|
||||||
|
HttpMethod method, {
|
||||||
|
Map<String, String>? headers,
|
||||||
|
dynamic body,
|
||||||
|
}) async {
|
||||||
|
var response = http.Response('', 400);
|
||||||
|
|
||||||
|
switch (method) {
|
||||||
|
case HttpMethod.get:
|
||||||
|
response = await http.get(url, headers: headers);
|
||||||
|
break;
|
||||||
|
case HttpMethod.post:
|
||||||
|
response = await http.post(url, headers: headers, body: body);
|
||||||
|
break;
|
||||||
|
case HttpMethod.put:
|
||||||
|
response = await http.put(url, headers: headers, body: body);
|
||||||
|
break;
|
||||||
|
case HttpMethod.delete:
|
||||||
|
response = await http.delete(url, headers: headers, body: body);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw Exception('Unsupported HTTP method');
|
||||||
|
}
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<String> _pollForResponse(String url) async {
|
||||||
|
String? responseJson = " ";
|
||||||
|
while (responseJson == " ") {
|
||||||
|
responseJson = await bind.mainGetHttpStatus(url: url);
|
||||||
|
if (responseJson == null) {
|
||||||
|
throw Exception('The HTTP request failed');
|
||||||
|
}
|
||||||
|
if (responseJson == " ") {
|
||||||
|
await Future.delayed(const Duration(milliseconds: 100));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return responseJson!;
|
||||||
|
}
|
||||||
|
|
||||||
|
http.Response _parseHttpResponse(String responseJson) {
|
||||||
|
try {
|
||||||
|
var parsedJson = jsonDecode(responseJson);
|
||||||
|
String body = parsedJson['body'];
|
||||||
|
Map<String, String> headers = {};
|
||||||
|
for (var key in parsedJson['headers'].keys) {
|
||||||
|
headers[key] = parsedJson['headers'][key];
|
||||||
|
}
|
||||||
|
int statusCode = parsedJson['status_code'];
|
||||||
|
return http.Response(body, statusCode, headers: headers);
|
||||||
|
} catch (e) {
|
||||||
|
throw Exception('Failed to parse response: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<http.Response> get(Uri url, {Map<String, String>? headers}) async {
|
||||||
|
return await HttpService().sendRequest(url, HttpMethod.get, headers: headers);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<http.Response> post(Uri url,
|
||||||
|
{Map<String, String>? headers, Object? body, Encoding? encoding}) async {
|
||||||
|
return await HttpService()
|
||||||
|
.sendRequest(url, HttpMethod.post, body: body, headers: headers);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<http.Response> put(Uri url,
|
||||||
|
{Map<String, String>? headers, Object? body, Encoding? encoding}) async {
|
||||||
|
return await HttpService()
|
||||||
|
.sendRequest(url, HttpMethod.put, body: body, headers: headers);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<http.Response> delete(Uri url,
|
||||||
|
{Map<String, String>? headers, Object? body, Encoding? encoding}) async {
|
||||||
|
return await HttpService()
|
||||||
|
.sendRequest(url, HttpMethod.delete, body: body, headers: headers);
|
||||||
|
}
|
||||||
@ -6,6 +6,8 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_hbb/consts.dart';
|
import 'package:flutter_hbb/consts.dart';
|
||||||
import 'package:flutter_hbb/common.dart';
|
import 'package:flutter_hbb/common.dart';
|
||||||
|
import 'package:flutter_hbb/main.dart';
|
||||||
|
import 'package:flutter_hbb/models/input_model.dart';
|
||||||
|
|
||||||
/// must keep the order
|
/// must keep the order
|
||||||
// ignore: constant_identifier_names
|
// ignore: constant_identifier_names
|
||||||
@ -431,6 +433,39 @@ class RustDeskMultiWindowManager {
|
|||||||
void unregisterActiveWindowListener(AsyncCallback callback) {
|
void unregisterActiveWindowListener(AsyncCallback callback) {
|
||||||
_windowActiveCallbacks.remove(callback);
|
_windowActiveCallbacks.remove(callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This function is called from the main window.
|
||||||
|
// It will query the active remote windows to get their coords.
|
||||||
|
Future<List<String>> getOtherRemoteWindowCoords(int wId) async {
|
||||||
|
List<String> coords = [];
|
||||||
|
for (final windowId in _remoteDesktopWindows) {
|
||||||
|
if (windowId != wId) {
|
||||||
|
if (_activeWindows.contains(windowId)) {
|
||||||
|
final res = await DesktopMultiWindow.invokeMethod(
|
||||||
|
windowId, kWindowEventRemoteWindowCoords, '');
|
||||||
|
if (res != null) {
|
||||||
|
coords.add(res);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return coords;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This function is called from one remote window.
|
||||||
|
// Only the main window knows `_remoteDesktopWindows` and `_activeWindows`.
|
||||||
|
// So we need to call the main window to get the other remote windows' coords.
|
||||||
|
Future<List<RemoteWindowCoords>> getOtherRemoteWindowCoordsFromMain() async {
|
||||||
|
List<RemoteWindowCoords> coords = [];
|
||||||
|
// Call the main window to get the coords of other remote windows.
|
||||||
|
String res = await DesktopMultiWindow.invokeMethod(
|
||||||
|
kMainWindowId, kWindowEventRemoteWindowCoords, kWindowId.toString());
|
||||||
|
List<dynamic> list = jsonDecode(res);
|
||||||
|
for (var item in list) {
|
||||||
|
coords.add(RemoteWindowCoords.fromJson(jsonDecode(item)));
|
||||||
|
}
|
||||||
|
return coords;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final rustDeskWinManager = RustDeskMultiWindowManager.instance;
|
final rustDeskWinManager = RustDeskMultiWindowManager.instance;
|
||||||
|
|||||||
@ -346,6 +346,10 @@ class RustdeskImpl {
|
|||||||
return mode == kKeyLegacyMode;
|
return mode == kKeyLegacyMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool sessionIsMultiUiSession({required UuidValue sessionId, dynamic hint}) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> sessionSetCustomImageQuality(
|
Future<void> sessionSetCustomImageQuality(
|
||||||
{required UuidValue sessionId, required int value, dynamic hint}) {
|
{required UuidValue sessionId, required int value, dynamic hint}) {
|
||||||
return Future(() => js.context.callMethod('setByName', [
|
return Future(() => js.context.callMethod('setByName', [
|
||||||
@ -676,7 +680,8 @@ class RustdeskImpl {
|
|||||||
return Future(() => js.context.callMethod('setByName', ['options', json]));
|
return Future(() => js.context.callMethod('setByName', ['options', json]));
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<String> mainTestIfValidServer({required String server, dynamic hint}) {
|
Future<String> mainTestIfValidServer(
|
||||||
|
{required String server, required bool testWithProxy, dynamic hint}) {
|
||||||
// TODO: implement
|
// TODO: implement
|
||||||
return Future.value('');
|
return Future.value('');
|
||||||
}
|
}
|
||||||
@ -770,6 +775,24 @@ class RustdeskImpl {
|
|||||||
throw UnimplementedError();
|
throw UnimplementedError();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<bool> mainGetProxyStatus({dynamic hint}) {
|
||||||
|
return Future(() => false);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> mainHttpRequest({
|
||||||
|
required String url,
|
||||||
|
required String method,
|
||||||
|
String? body,
|
||||||
|
required String header,
|
||||||
|
dynamic hint,
|
||||||
|
}) {
|
||||||
|
throw UnimplementedError();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<String?> mainGetHttpStatus({required String url, dynamic hint}) {
|
||||||
|
throw UnimplementedError();
|
||||||
|
}
|
||||||
|
|
||||||
String mainGetLocalOption({required String key, dynamic hint}) {
|
String mainGetLocalOption({required String key, dynamic hint}) {
|
||||||
return js.context.callMethod('getByName', ['option:local', key]);
|
return js.context.callMethod('getByName', ['option:local', key]);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -41,10 +41,19 @@ uuid = { version = "1.3", features = ["v4"] }
|
|||||||
# crash, versions >= 0.29.1 are affected by #GuillaumeGomez/sysinfo/1052
|
# crash, versions >= 0.29.1 are affected by #GuillaumeGomez/sysinfo/1052
|
||||||
sysinfo = { git = "https://github.com/rustdesk-org/sysinfo" }
|
sysinfo = { git = "https://github.com/rustdesk-org/sysinfo" }
|
||||||
thiserror = "1.0"
|
thiserror = "1.0"
|
||||||
|
httparse = "1.5"
|
||||||
|
base64 = "0.22"
|
||||||
|
url = "2.2"
|
||||||
|
|
||||||
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
|
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
|
||||||
mac_address = "1.1"
|
mac_address = "1.1"
|
||||||
machine-uid = { git = "https://github.com/21pages/machine-uid" }
|
machine-uid = { git = "https://github.com/21pages/machine-uid" }
|
||||||
|
[target.'cfg(not(any(target_os = "macos", target_os = "windows")))'.dependencies]
|
||||||
|
tokio-rustls = { version = "0.26", features = ["logging", "tls12", "ring"], default-features = false }
|
||||||
|
rustls-platform-verifier = "0.3.1"
|
||||||
|
rustls-pki-types = "1.4"
|
||||||
|
[target.'cfg(any(target_os = "macos", target_os = "windows"))'.dependencies]
|
||||||
|
tokio-native-tls ="0.3"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
quic = []
|
quic = []
|
||||||
|
|||||||
@ -603,6 +603,8 @@ message OptionMessage {
|
|||||||
// Resolution custom_resolution = 13;
|
// Resolution custom_resolution = 13;
|
||||||
// BoolOption support_windows_specific_session = 14;
|
// BoolOption support_windows_specific_session = 14;
|
||||||
// starting from 15 please, do not use removed fields
|
// starting from 15 please, do not use removed fields
|
||||||
|
BoolOption follow_remote_cursor = 15;
|
||||||
|
BoolOption follow_remote_window = 16;
|
||||||
}
|
}
|
||||||
|
|
||||||
message TestDelay {
|
message TestDelay {
|
||||||
@ -765,6 +767,7 @@ message Misc {
|
|||||||
uint32 selected_sid = 35;
|
uint32 selected_sid = 35;
|
||||||
DisplayResolution change_display_resolution = 36;
|
DisplayResolution change_display_resolution = 36;
|
||||||
MessageQuery message_query = 37;
|
MessageQuery message_query = 37;
|
||||||
|
int32 follow_current_display = 38;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -281,6 +281,10 @@ pub struct PeerConfig {
|
|||||||
pub enable_file_transfer: EnableFileTransfer,
|
pub enable_file_transfer: EnableFileTransfer,
|
||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
pub show_quality_monitor: ShowQualityMonitor,
|
pub show_quality_monitor: ShowQualityMonitor,
|
||||||
|
#[serde(flatten)]
|
||||||
|
pub follow_remote_cursor: FollowRemoteCursor,
|
||||||
|
#[serde(flatten)]
|
||||||
|
pub follow_remote_window: FollowRemoteWindow,
|
||||||
#[serde(
|
#[serde(
|
||||||
default,
|
default,
|
||||||
deserialize_with = "deserialize_string",
|
deserialize_with = "deserialize_string",
|
||||||
@ -353,6 +357,8 @@ impl Default for PeerConfig {
|
|||||||
disable_clipboard: Default::default(),
|
disable_clipboard: Default::default(),
|
||||||
enable_file_transfer: Default::default(),
|
enable_file_transfer: Default::default(),
|
||||||
show_quality_monitor: Default::default(),
|
show_quality_monitor: Default::default(),
|
||||||
|
follow_remote_cursor: Default::default(),
|
||||||
|
follow_remote_window: Default::default(),
|
||||||
keyboard_mode: Default::default(),
|
keyboard_mode: Default::default(),
|
||||||
view_only: Default::default(),
|
view_only: Default::default(),
|
||||||
reverse_mouse_wheel: Self::default_reverse_mouse_wheel(),
|
reverse_mouse_wheel: Self::default_reverse_mouse_wheel(),
|
||||||
@ -1258,6 +1264,19 @@ serde_field_bool!(
|
|||||||
default_show_remote_cursor,
|
default_show_remote_cursor,
|
||||||
"ShowRemoteCursor::default_show_remote_cursor"
|
"ShowRemoteCursor::default_show_remote_cursor"
|
||||||
);
|
);
|
||||||
|
serde_field_bool!(
|
||||||
|
FollowRemoteCursor,
|
||||||
|
"follow_remote_cursor",
|
||||||
|
default_follow_remote_cursor,
|
||||||
|
"FollowRemoteCursor::default_follow_remote_cursor"
|
||||||
|
);
|
||||||
|
|
||||||
|
serde_field_bool!(
|
||||||
|
FollowRemoteWindow,
|
||||||
|
"follow_remote_window",
|
||||||
|
default_follow_remote_window,
|
||||||
|
"FollowRemoteWindow::default_follow_remote_window"
|
||||||
|
);
|
||||||
serde_field_bool!(
|
serde_field_bool!(
|
||||||
ShowQualityMonitor,
|
ShowQualityMonitor,
|
||||||
"show_quality_monitor",
|
"show_quality_monitor",
|
||||||
|
|||||||
@ -16,6 +16,7 @@ use std::{
|
|||||||
};
|
};
|
||||||
pub use tokio;
|
pub use tokio;
|
||||||
pub use tokio_util;
|
pub use tokio_util;
|
||||||
|
pub mod proxy;
|
||||||
pub mod socket_client;
|
pub mod socket_client;
|
||||||
pub mod tcp;
|
pub mod tcp;
|
||||||
pub mod udp;
|
pub mod udp;
|
||||||
@ -51,6 +52,7 @@ pub use serde_json;
|
|||||||
pub use sysinfo;
|
pub use sysinfo;
|
||||||
pub use toml;
|
pub use toml;
|
||||||
pub use uuid;
|
pub use uuid;
|
||||||
|
pub use base64;
|
||||||
pub use thiserror;
|
pub use thiserror;
|
||||||
|
|
||||||
#[cfg(feature = "quic")]
|
#[cfg(feature = "quic")]
|
||||||
|
|||||||
561
libs/hbb_common/src/proxy.rs
Normal file
561
libs/hbb_common/src/proxy.rs
Normal file
@ -0,0 +1,561 @@
|
|||||||
|
use std::{
|
||||||
|
io::Error as IoError,
|
||||||
|
net::{SocketAddr, ToSocketAddrs},
|
||||||
|
};
|
||||||
|
|
||||||
|
use base64::{engine::general_purpose, Engine};
|
||||||
|
use httparse::{Error as HttpParseError, Response, EMPTY_HEADER};
|
||||||
|
use log::info;
|
||||||
|
use thiserror::Error as ThisError;
|
||||||
|
use tokio::io::{AsyncRead, AsyncWrite, AsyncWriteExt, BufStream};
|
||||||
|
#[cfg(any(target_os = "windows", target_os = "macos"))]
|
||||||
|
use tokio_native_tls::{native_tls, TlsConnector, TlsStream};
|
||||||
|
#[cfg(not(any(target_os = "windows", target_os = "macos")))]
|
||||||
|
use tokio_rustls::{client::TlsStream, TlsConnector};
|
||||||
|
use tokio_socks::{tcp::Socks5Stream, IntoTargetAddr};
|
||||||
|
use tokio_util::codec::Framed;
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
bytes_codec::BytesCodec,
|
||||||
|
config::Socks5Server,
|
||||||
|
tcp::{DynTcpStream, FramedStream},
|
||||||
|
ResultType,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, ThisError)]
|
||||||
|
pub enum ProxyError {
|
||||||
|
#[error("IO Error: {0}")]
|
||||||
|
IoError(#[from] IoError),
|
||||||
|
#[error("Target parse error: {0}")]
|
||||||
|
TargetParseError(String),
|
||||||
|
#[error("HTTP parse error: {0}")]
|
||||||
|
HttpParseError(#[from] HttpParseError),
|
||||||
|
#[error("The maximum response header length is exceeded: {0}")]
|
||||||
|
MaximumResponseHeaderLengthExceeded(usize),
|
||||||
|
#[error("The end of file is reached")]
|
||||||
|
EndOfFile,
|
||||||
|
#[error("The url is error: {0}")]
|
||||||
|
UrlBadScheme(String),
|
||||||
|
#[error("The url parse error: {0}")]
|
||||||
|
UrlParseScheme(#[from] url::ParseError),
|
||||||
|
#[error("No HTTP code was found in the response")]
|
||||||
|
NoHttpCode,
|
||||||
|
#[error("The HTTP code is not equal 200: {0}")]
|
||||||
|
HttpCode200(u16),
|
||||||
|
#[error("The proxy address resolution failed: {0}")]
|
||||||
|
AddressResolutionFailed(String),
|
||||||
|
#[cfg(any(target_os = "windows", target_os = "macos"))]
|
||||||
|
#[error("The native tls error: {0}")]
|
||||||
|
NativeTlsError(#[from] tokio_native_tls::native_tls::Error),
|
||||||
|
}
|
||||||
|
|
||||||
|
const MAXIMUM_RESPONSE_HEADER_LENGTH: usize = 4096;
|
||||||
|
/// The maximum HTTP Headers, which can be parsed.
|
||||||
|
const MAXIMUM_RESPONSE_HEADERS: usize = 16;
|
||||||
|
const DEFINE_TIME_OUT: u64 = 600;
|
||||||
|
|
||||||
|
pub trait IntoUrl {
|
||||||
|
|
||||||
|
// Besides parsing as a valid `Url`, the `Url` must be a valid
|
||||||
|
// `http::Uri`, in that it makes sense to use in a network request.
|
||||||
|
fn into_url(self) -> Result<Url, ProxyError>;
|
||||||
|
|
||||||
|
fn as_str(&self) -> &str;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoUrl for Url {
|
||||||
|
fn into_url(self) -> Result<Url, ProxyError> {
|
||||||
|
if self.has_host() {
|
||||||
|
Ok(self)
|
||||||
|
} else {
|
||||||
|
Err(ProxyError::UrlBadScheme(self.to_string()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_str(&self) -> &str {
|
||||||
|
self.as_ref()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> IntoUrl for &'a str {
|
||||||
|
fn into_url(self) -> Result<Url, ProxyError> {
|
||||||
|
Url::parse(self)
|
||||||
|
.map_err(ProxyError::UrlParseScheme)?
|
||||||
|
.into_url()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_str(&self) -> &str {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> IntoUrl for &'a String {
|
||||||
|
fn into_url(self) -> Result<Url, ProxyError> {
|
||||||
|
(&**self).into_url()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_str(&self) -> &str {
|
||||||
|
self.as_ref()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> IntoUrl for String {
|
||||||
|
fn into_url(self) -> Result<Url, ProxyError> {
|
||||||
|
(&*self).into_url()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_str(&self) -> &str {
|
||||||
|
self.as_ref()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Auth {
|
||||||
|
user_name: String,
|
||||||
|
password: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Auth {
|
||||||
|
fn get_proxy_authorization(&self) -> String {
|
||||||
|
format!(
|
||||||
|
"Proxy-Authorization: Basic {}\r\n",
|
||||||
|
self.get_basic_authorization()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_basic_authorization(&self) -> String {
|
||||||
|
let authorization = format!("{}:{}", &self.user_name, &self.password);
|
||||||
|
general_purpose::STANDARD.encode(authorization.as_bytes())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub enum ProxyScheme {
|
||||||
|
Http {
|
||||||
|
auth: Option<Auth>,
|
||||||
|
host: String,
|
||||||
|
},
|
||||||
|
Https {
|
||||||
|
auth: Option<Auth>,
|
||||||
|
host: String,
|
||||||
|
},
|
||||||
|
Socks5 {
|
||||||
|
addr: SocketAddr,
|
||||||
|
auth: Option<Auth>,
|
||||||
|
remote_dns: bool,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ProxyScheme {
|
||||||
|
pub fn maybe_auth(&self) -> Option<&Auth> {
|
||||||
|
match self {
|
||||||
|
ProxyScheme::Http { auth, .. }
|
||||||
|
| ProxyScheme::Https { auth, .. }
|
||||||
|
| ProxyScheme::Socks5 { auth, .. } => auth.as_ref(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn socks5(addr: SocketAddr) -> Result<Self, ProxyError> {
|
||||||
|
Ok(ProxyScheme::Socks5 {
|
||||||
|
addr,
|
||||||
|
auth: None,
|
||||||
|
remote_dns: false,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn http(host: &str) -> Result<Self, ProxyError> {
|
||||||
|
Ok(ProxyScheme::Http {
|
||||||
|
auth: None,
|
||||||
|
host: host.to_string(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
fn https(host: &str) -> Result<Self, ProxyError> {
|
||||||
|
Ok(ProxyScheme::Https {
|
||||||
|
auth: None,
|
||||||
|
host: host.to_string(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_basic_auth<T: Into<String>, U: Into<String>>(&mut self, username: T, password: U) {
|
||||||
|
let auth = Auth {
|
||||||
|
user_name: username.into(),
|
||||||
|
password: password.into(),
|
||||||
|
};
|
||||||
|
match self {
|
||||||
|
ProxyScheme::Http { auth: a, .. } => *a = Some(auth),
|
||||||
|
ProxyScheme::Https { auth: a, .. } => *a = Some(auth),
|
||||||
|
ProxyScheme::Socks5 { auth: a, .. } => *a = Some(auth),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse(url: Url) -> Result<Self, ProxyError> {
|
||||||
|
use url::Position;
|
||||||
|
|
||||||
|
// Resolve URL to a host and port
|
||||||
|
let to_addr = || {
|
||||||
|
let addrs = url.socket_addrs(|| match url.scheme() {
|
||||||
|
"socks5" => Some(1080),
|
||||||
|
_ => None,
|
||||||
|
})?;
|
||||||
|
addrs
|
||||||
|
.into_iter()
|
||||||
|
.next()
|
||||||
|
.ok_or_else(|| ProxyError::UrlParseScheme(url::ParseError::EmptyHost))
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut scheme: Self = match url.scheme() {
|
||||||
|
"http" => Self::http(&url[Position::BeforeHost..Position::AfterPort])?,
|
||||||
|
"https" => Self::https(&url[Position::BeforeHost..Position::AfterPort])?,
|
||||||
|
"socks5" => Self::socks5(to_addr()?)?,
|
||||||
|
e => return Err(ProxyError::UrlBadScheme(e.to_string())),
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(pwd) = url.password() {
|
||||||
|
let username = url.username();
|
||||||
|
scheme.set_basic_auth(username, pwd);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(scheme)
|
||||||
|
}
|
||||||
|
pub async fn socket_addrs(&self) -> Result<SocketAddr, ProxyError> {
|
||||||
|
info!("Resolving socket address");
|
||||||
|
match self {
|
||||||
|
ProxyScheme::Http { host, .. } => self.resolve_host(host, 80).await,
|
||||||
|
ProxyScheme::Https { host, .. } => self.resolve_host(host, 443).await,
|
||||||
|
ProxyScheme::Socks5 { addr, .. } => Ok(addr.clone()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn resolve_host(&self, host: &str, default_port: u16) -> Result<SocketAddr, ProxyError> {
|
||||||
|
let (host_str, port) = match host.split_once(':') {
|
||||||
|
Some((h, p)) => (h, p.parse::<u16>().ok()),
|
||||||
|
None => (host, None),
|
||||||
|
};
|
||||||
|
let addr = (host_str, port.unwrap_or(default_port))
|
||||||
|
.to_socket_addrs()?
|
||||||
|
.next()
|
||||||
|
.ok_or_else(|| ProxyError::AddressResolutionFailed(host.to_string()))?;
|
||||||
|
Ok(addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_domain(&self) -> Result<String, ProxyError> {
|
||||||
|
match self {
|
||||||
|
ProxyScheme::Http { host, .. } | ProxyScheme::Https { host, .. } => {
|
||||||
|
let domain = host
|
||||||
|
.split(':')
|
||||||
|
.next()
|
||||||
|
.ok_or_else(|| ProxyError::AddressResolutionFailed(host.clone()))?;
|
||||||
|
Ok(domain.to_string())
|
||||||
|
}
|
||||||
|
ProxyScheme::Socks5 { addr, .. } => match addr {
|
||||||
|
SocketAddr::V4(addr_v4) => Ok(addr_v4.ip().to_string()),
|
||||||
|
SocketAddr::V6(addr_v6) => Ok(addr_v6.ip().to_string()),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn get_host_and_port(&self) -> Result<String, ProxyError> {
|
||||||
|
match self {
|
||||||
|
ProxyScheme::Http { host, .. } => Ok(self.append_default_port(host, 80)),
|
||||||
|
ProxyScheme::Https { host, .. } => Ok(self.append_default_port(host, 443)),
|
||||||
|
ProxyScheme::Socks5 { addr, .. } => Ok(format!("{}", addr)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn append_default_port(&self, host: &str, default_port: u16) -> String {
|
||||||
|
if host.contains(':') {
|
||||||
|
host.to_string()
|
||||||
|
} else {
|
||||||
|
format!("{}:{}", host, default_port)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait IntoProxyScheme {
|
||||||
|
fn into_proxy_scheme(self) -> Result<ProxyScheme, ProxyError>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S: IntoUrl> IntoProxyScheme for S {
|
||||||
|
fn into_proxy_scheme(self) -> Result<ProxyScheme, ProxyError> {
|
||||||
|
// validate the URL
|
||||||
|
let url = match self.as_str().into_url() {
|
||||||
|
Ok(ok) => ok,
|
||||||
|
Err(e) => {
|
||||||
|
match e {
|
||||||
|
// If the string does not contain protocol headers, try to parse it using the socks5 protocol
|
||||||
|
ProxyError::UrlParseScheme(_source) => {
|
||||||
|
let try_this = format!("socks5://{}", self.as_str());
|
||||||
|
try_this.into_url()?
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
return Err(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
ProxyScheme::parse(url)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoProxyScheme for ProxyScheme {
|
||||||
|
fn into_proxy_scheme(self) -> Result<ProxyScheme, ProxyError> {
|
||||||
|
Ok(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Proxy {
|
||||||
|
pub intercept: ProxyScheme,
|
||||||
|
ms_timeout: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Proxy {
|
||||||
|
pub fn new<U: IntoProxyScheme>(proxy_scheme: U, ms_timeout: u64) -> Result<Self, ProxyError> {
|
||||||
|
Ok(Self {
|
||||||
|
intercept: proxy_scheme.into_proxy_scheme()?,
|
||||||
|
ms_timeout,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_http_or_https(&self) -> bool {
|
||||||
|
return match self.intercept {
|
||||||
|
ProxyScheme::Socks5 { .. } => false,
|
||||||
|
_ => true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_conf(conf: &Socks5Server, ms_timeout: Option<u64>) -> Result<Self, ProxyError> {
|
||||||
|
let mut proxy;
|
||||||
|
match ms_timeout {
|
||||||
|
None => {
|
||||||
|
proxy = Self::new(&conf.proxy, DEFINE_TIME_OUT)?;
|
||||||
|
}
|
||||||
|
Some(time_out) => {
|
||||||
|
proxy = Self::new(&conf.proxy, time_out)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !conf.password.is_empty() && !conf.username.is_empty() {
|
||||||
|
proxy = proxy.basic_auth(&conf.username, &conf.password);
|
||||||
|
}
|
||||||
|
Ok(proxy)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn proxy_addrs(&self) -> Result<SocketAddr, ProxyError> {
|
||||||
|
self.intercept.socket_addrs().await
|
||||||
|
}
|
||||||
|
|
||||||
|
fn basic_auth(mut self, username: &str, password: &str) -> Proxy {
|
||||||
|
self.intercept.set_basic_auth(username, password);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn connect<'t, T>(
|
||||||
|
self,
|
||||||
|
target: T,
|
||||||
|
local_addr: Option<SocketAddr>,
|
||||||
|
) -> ResultType<FramedStream>
|
||||||
|
where
|
||||||
|
T: IntoTargetAddr<'t>,
|
||||||
|
{
|
||||||
|
info!("Connect to proxy server");
|
||||||
|
let proxy = self.proxy_addrs().await?;
|
||||||
|
|
||||||
|
let local = if let Some(addr) = local_addr {
|
||||||
|
addr
|
||||||
|
} else {
|
||||||
|
crate::config::Config::get_any_listen_addr(proxy.is_ipv4())
|
||||||
|
};
|
||||||
|
|
||||||
|
let stream = super::timeout(
|
||||||
|
self.ms_timeout,
|
||||||
|
crate::tcp::new_socket(local, true)?.connect(proxy),
|
||||||
|
)
|
||||||
|
.await??;
|
||||||
|
stream.set_nodelay(true).ok();
|
||||||
|
|
||||||
|
let addr = stream.local_addr()?;
|
||||||
|
|
||||||
|
return match self.intercept {
|
||||||
|
ProxyScheme::Http { .. } => {
|
||||||
|
info!("Connect to remote http proxy server: {}", proxy);
|
||||||
|
let stream =
|
||||||
|
super::timeout(self.ms_timeout, self.http_connect(stream, target)).await??;
|
||||||
|
Ok(FramedStream(
|
||||||
|
Framed::new(DynTcpStream(Box::new(stream)), BytesCodec::new()),
|
||||||
|
addr,
|
||||||
|
None,
|
||||||
|
0,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
ProxyScheme::Https { .. } => {
|
||||||
|
info!("Connect to remote https proxy server: {}", proxy);
|
||||||
|
let stream =
|
||||||
|
super::timeout(self.ms_timeout, self.https_connect(stream, target)).await??;
|
||||||
|
Ok(FramedStream(
|
||||||
|
Framed::new(DynTcpStream(Box::new(stream)), BytesCodec::new()),
|
||||||
|
addr,
|
||||||
|
None,
|
||||||
|
0,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
ProxyScheme::Socks5 { .. } => {
|
||||||
|
info!("Connect to remote socket5 proxy server: {}", proxy);
|
||||||
|
let stream = if let Some(auth) = self.intercept.maybe_auth() {
|
||||||
|
super::timeout(
|
||||||
|
self.ms_timeout,
|
||||||
|
Socks5Stream::connect_with_password_and_socket(
|
||||||
|
stream,
|
||||||
|
target,
|
||||||
|
&auth.user_name,
|
||||||
|
&auth.password,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.await??
|
||||||
|
} else {
|
||||||
|
super::timeout(
|
||||||
|
self.ms_timeout,
|
||||||
|
Socks5Stream::connect_with_socket(stream, target),
|
||||||
|
)
|
||||||
|
.await??
|
||||||
|
};
|
||||||
|
Ok(FramedStream(
|
||||||
|
Framed::new(DynTcpStream(Box::new(stream)), BytesCodec::new()),
|
||||||
|
addr,
|
||||||
|
None,
|
||||||
|
0,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(any(target_os = "windows", target_os = "macos"))]
|
||||||
|
pub async fn https_connect<'a, Input, T>(
|
||||||
|
self,
|
||||||
|
io: Input,
|
||||||
|
target: T,
|
||||||
|
) -> Result<BufStream<TlsStream<Input>>, ProxyError>
|
||||||
|
where
|
||||||
|
Input: AsyncRead + AsyncWrite + Unpin,
|
||||||
|
T: IntoTargetAddr<'a>,
|
||||||
|
{
|
||||||
|
let tls_connector = TlsConnector::from(native_tls::TlsConnector::new()?);
|
||||||
|
let stream = tls_connector
|
||||||
|
.connect(&self.intercept.get_domain()?, io)
|
||||||
|
.await?;
|
||||||
|
self.http_connect(stream, target).await
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(any(target_os = "windows", target_os = "macos")))]
|
||||||
|
pub async fn https_connect<'a, Input, T>(
|
||||||
|
self,
|
||||||
|
io: Input,
|
||||||
|
target: T,
|
||||||
|
) -> Result<BufStream<TlsStream<Input>>, ProxyError>
|
||||||
|
where
|
||||||
|
Input: AsyncRead + AsyncWrite + Unpin,
|
||||||
|
T: IntoTargetAddr<'a>,
|
||||||
|
{
|
||||||
|
use std::convert::TryFrom;
|
||||||
|
let verifier = rustls_platform_verifier::tls_config();
|
||||||
|
let url_domain = self.intercept.get_domain()?;
|
||||||
|
|
||||||
|
let domain = rustls_pki_types::ServerName::try_from(url_domain.as_str())
|
||||||
|
.map_err(|e| ProxyError::AddressResolutionFailed(e.to_string()))?
|
||||||
|
.to_owned();
|
||||||
|
|
||||||
|
let tls_connector = TlsConnector::from(std::sync::Arc::new(verifier));
|
||||||
|
let stream = tls_connector.connect(domain, io).await?;
|
||||||
|
self.http_connect(stream, target).await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn http_connect<'a, Input, T>(
|
||||||
|
self,
|
||||||
|
io: Input,
|
||||||
|
target: T,
|
||||||
|
) -> Result<BufStream<Input>, ProxyError>
|
||||||
|
where
|
||||||
|
Input: AsyncRead + AsyncWrite + Unpin,
|
||||||
|
T: IntoTargetAddr<'a>,
|
||||||
|
{
|
||||||
|
let mut stream = BufStream::new(io);
|
||||||
|
let (domain, port) = get_domain_and_port(target)?;
|
||||||
|
|
||||||
|
let request = self.make_request(&domain, port);
|
||||||
|
stream.write_all(request.as_bytes()).await?;
|
||||||
|
stream.flush().await?;
|
||||||
|
recv_and_check_response(&mut stream).await?;
|
||||||
|
Ok(stream)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn make_request(&self, host: &str, port: u16) -> String {
|
||||||
|
let mut request = format!(
|
||||||
|
"CONNECT {host}:{port} HTTP/1.1\r\nHost: {host}:{port}\r\n",
|
||||||
|
host = host,
|
||||||
|
port = port
|
||||||
|
);
|
||||||
|
|
||||||
|
if let Some(auth) = self.intercept.maybe_auth() {
|
||||||
|
request = format!("{}{}", request, auth.get_proxy_authorization());
|
||||||
|
}
|
||||||
|
|
||||||
|
request.push_str("\r\n");
|
||||||
|
request
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_domain_and_port<'a, T: IntoTargetAddr<'a>>(target: T) -> Result<(String, u16), ProxyError> {
|
||||||
|
let target_addr = target
|
||||||
|
.into_target_addr()
|
||||||
|
.map_err(|e| ProxyError::TargetParseError(e.to_string()))?;
|
||||||
|
match target_addr {
|
||||||
|
tokio_socks::TargetAddr::Ip(addr) => Ok((addr.ip().to_string(), addr.port())),
|
||||||
|
tokio_socks::TargetAddr::Domain(name, port) => Ok((name.to_string(), port)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_response<IO>(stream: &mut BufStream<IO>) -> Result<String, ProxyError>
|
||||||
|
where
|
||||||
|
IO: AsyncRead + AsyncWrite + Unpin,
|
||||||
|
{
|
||||||
|
use tokio::io::AsyncBufReadExt;
|
||||||
|
let mut response = String::new();
|
||||||
|
|
||||||
|
loop {
|
||||||
|
if stream.read_line(&mut response).await? == 0 {
|
||||||
|
return Err(ProxyError::EndOfFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
if MAXIMUM_RESPONSE_HEADER_LENGTH < response.len() {
|
||||||
|
return Err(ProxyError::MaximumResponseHeaderLengthExceeded(
|
||||||
|
response.len(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
if response.ends_with("\r\n\r\n") {
|
||||||
|
return Ok(response);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn recv_and_check_response<IO>(stream: &mut BufStream<IO>) -> Result<(), ProxyError>
|
||||||
|
where
|
||||||
|
IO: AsyncRead + AsyncWrite + Unpin,
|
||||||
|
{
|
||||||
|
let response_string = get_response(stream).await?;
|
||||||
|
|
||||||
|
let mut response_headers = [EMPTY_HEADER; MAXIMUM_RESPONSE_HEADERS];
|
||||||
|
let mut response = Response::new(&mut response_headers);
|
||||||
|
let response_bytes = response_string.into_bytes();
|
||||||
|
response.parse(&response_bytes)?;
|
||||||
|
|
||||||
|
return match response.code {
|
||||||
|
Some(code) => {
|
||||||
|
if code == 200 {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(ProxyError::HttpCode200(code))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => Err(ProxyError::NoHttpCode),
|
||||||
|
};
|
||||||
|
}
|
||||||
@ -49,19 +49,27 @@ pub fn increase_port<T: std::string::ToString>(host: T, offset: i32) -> String {
|
|||||||
host
|
host
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn test_if_valid_server(host: &str) -> String {
|
pub fn test_if_valid_server(host: &str, test_with_proxy: bool) -> String {
|
||||||
let host = check_port(host, 0);
|
let host = check_port(host, 0);
|
||||||
|
|
||||||
use std::net::ToSocketAddrs;
|
use std::net::ToSocketAddrs;
|
||||||
match Config::get_network_type() {
|
|
||||||
NetworkType::Direct => match host.to_socket_addrs() {
|
if test_with_proxy && NetworkType::ProxySocks == Config::get_network_type() {
|
||||||
|
test_if_valid_server_for_proxy_(&host)
|
||||||
|
} else {
|
||||||
|
match host.to_socket_addrs() {
|
||||||
Err(err) => err.to_string(),
|
Err(err) => err.to_string(),
|
||||||
Ok(_) => "".to_owned(),
|
Ok(_) => "".to_owned(),
|
||||||
},
|
}
|
||||||
NetworkType::ProxySocks => match &host.into_target_addr() {
|
}
|
||||||
Err(err) => err.to_string(),
|
}
|
||||||
Ok(_) => "".to_owned(),
|
|
||||||
},
|
#[inline]
|
||||||
|
pub fn test_if_valid_server_for_proxy_(host: &str) -> String {
|
||||||
|
// `&host.into_target_addr()` is defined in `tokio-socs`, but is a common pattern for testing,
|
||||||
|
// it can be used for both `socks` and `http` proxy.
|
||||||
|
match &host.into_target_addr() {
|
||||||
|
Err(err) => err.to_string(),
|
||||||
|
Ok(_) => "".to_owned(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -107,15 +115,7 @@ pub async fn connect_tcp_local<
|
|||||||
ms_timeout: u64,
|
ms_timeout: u64,
|
||||||
) -> ResultType<FramedStream> {
|
) -> ResultType<FramedStream> {
|
||||||
if let Some(conf) = Config::get_socks() {
|
if let Some(conf) = Config::get_socks() {
|
||||||
return FramedStream::connect(
|
return FramedStream::connect(target, local, &conf, ms_timeout).await;
|
||||||
conf.proxy.as_str(),
|
|
||||||
target,
|
|
||||||
local,
|
|
||||||
conf.username.as_str(),
|
|
||||||
conf.password.as_str(),
|
|
||||||
ms_timeout,
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
}
|
}
|
||||||
if let Some(target) = target.resolve() {
|
if let Some(target) = target.resolve() {
|
||||||
if let Some(local) = local {
|
if let Some(local) = local {
|
||||||
@ -255,10 +255,20 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_test_if_valid_server() {
|
fn test_test_if_valid_server() {
|
||||||
assert!(!test_if_valid_server("a").is_empty());
|
assert!(!test_if_valid_server("a", false).is_empty());
|
||||||
// on Linux, "1" is resolved to "0.0.0.1"
|
// on Linux, "1" is resolved to "0.0.0.1"
|
||||||
assert!(test_if_valid_server("1.1.1.1").is_empty());
|
assert!(test_if_valid_server("1.1.1.1", false).is_empty());
|
||||||
assert!(test_if_valid_server("1.1.1.1:1").is_empty());
|
assert!(test_if_valid_server("1.1.1.1:1", false).is_empty());
|
||||||
|
assert!(test_if_valid_server("abcd.com", false).is_empty());
|
||||||
|
assert!(test_if_valid_server("abcd.com:1", false).is_empty());
|
||||||
|
|
||||||
|
// with proxy
|
||||||
|
// `:0` indicates `let host = check_port(host, 0);` is called.
|
||||||
|
assert!(test_if_valid_server_for_proxy_("a:0").is_empty());
|
||||||
|
assert!(test_if_valid_server_for_proxy_("1.1.1.1:0").is_empty());
|
||||||
|
assert!(test_if_valid_server_for_proxy_("1.1.1.1:1").is_empty());
|
||||||
|
assert!(test_if_valid_server_for_proxy_("abc.com:0").is_empty());
|
||||||
|
assert!(test_if_valid_server_for_proxy_("abcd.com:1").is_empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
use crate::{bail, bytes_codec::BytesCodec, ResultType};
|
use crate::{bail, bytes_codec::BytesCodec, ResultType, config::Socks5Server, proxy::Proxy};
|
||||||
use anyhow::Context as AnyhowCtx;
|
use anyhow::Context as AnyhowCtx;
|
||||||
use bytes::{BufMut, Bytes, BytesMut};
|
use bytes::{BufMut, Bytes, BytesMut};
|
||||||
use futures::{SinkExt, StreamExt};
|
use futures::{SinkExt, StreamExt};
|
||||||
@ -18,20 +18,20 @@ use tokio::{
|
|||||||
io::{AsyncRead, AsyncWrite, ReadBuf},
|
io::{AsyncRead, AsyncWrite, ReadBuf},
|
||||||
net::{lookup_host, TcpListener, TcpSocket, ToSocketAddrs},
|
net::{lookup_host, TcpListener, TcpSocket, ToSocketAddrs},
|
||||||
};
|
};
|
||||||
use tokio_socks::{tcp::Socks5Stream, IntoTargetAddr, ToProxyAddrs};
|
use tokio_socks::IntoTargetAddr;
|
||||||
use tokio_util::codec::Framed;
|
use tokio_util::codec::Framed;
|
||||||
|
|
||||||
pub trait TcpStreamTrait: AsyncRead + AsyncWrite + Unpin {}
|
pub trait TcpStreamTrait: AsyncRead + AsyncWrite + Unpin {}
|
||||||
pub struct DynTcpStream(Box<dyn TcpStreamTrait + Send + Sync>);
|
pub struct DynTcpStream(pub(crate) Box<dyn TcpStreamTrait + Send + Sync>);
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Encrypt(Key, u64, u64);
|
pub struct Encrypt(Key, u64, u64);
|
||||||
|
|
||||||
pub struct FramedStream(
|
pub struct FramedStream(
|
||||||
Framed<DynTcpStream, BytesCodec>,
|
pub(crate) Framed<DynTcpStream, BytesCodec>,
|
||||||
SocketAddr,
|
pub(crate) SocketAddr,
|
||||||
Option<Encrypt>,
|
pub(crate) Option<Encrypt>,
|
||||||
u64,
|
pub(crate) u64,
|
||||||
);
|
);
|
||||||
|
|
||||||
impl Deref for FramedStream {
|
impl Deref for FramedStream {
|
||||||
@ -62,7 +62,7 @@ impl DerefMut for DynTcpStream {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn new_socket(addr: std::net::SocketAddr, reuse: bool) -> Result<TcpSocket, std::io::Error> {
|
pub(crate) fn new_socket(addr: std::net::SocketAddr, reuse: bool) -> Result<TcpSocket, std::io::Error> {
|
||||||
let socket = match addr {
|
let socket = match addr {
|
||||||
std::net::SocketAddr::V4(..) => TcpSocket::new_v4()?,
|
std::net::SocketAddr::V4(..) => TcpSocket::new_v4()?,
|
||||||
std::net::SocketAddr::V6(..) => TcpSocket::new_v6()?,
|
std::net::SocketAddr::V6(..) => TcpSocket::new_v6()?,
|
||||||
@ -109,51 +109,17 @@ impl FramedStream {
|
|||||||
bail!(format!("Failed to connect to {remote_addr}"));
|
bail!(format!("Failed to connect to {remote_addr}"));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn connect<'a, 't, P, T>(
|
pub async fn connect<'t, T>(
|
||||||
proxy: P,
|
|
||||||
target: T,
|
target: T,
|
||||||
local_addr: Option<SocketAddr>,
|
local_addr: Option<SocketAddr>,
|
||||||
username: &'a str,
|
proxy_conf: &Socks5Server,
|
||||||
password: &'a str,
|
|
||||||
ms_timeout: u64,
|
ms_timeout: u64,
|
||||||
) -> ResultType<Self>
|
) -> ResultType<Self>
|
||||||
where
|
where
|
||||||
P: ToProxyAddrs,
|
|
||||||
T: IntoTargetAddr<'t>,
|
T: IntoTargetAddr<'t>,
|
||||||
{
|
{
|
||||||
if let Some(Ok(proxy)) = proxy.to_proxy_addrs().next().await {
|
let proxy = Proxy::from_conf(proxy_conf, Some(ms_timeout))?;
|
||||||
let local = if let Some(addr) = local_addr {
|
proxy.connect::<T>(target, local_addr).await
|
||||||
addr
|
|
||||||
} else {
|
|
||||||
crate::config::Config::get_any_listen_addr(proxy.is_ipv4())
|
|
||||||
};
|
|
||||||
let stream =
|
|
||||||
super::timeout(ms_timeout, new_socket(local, true)?.connect(proxy)).await??;
|
|
||||||
stream.set_nodelay(true).ok();
|
|
||||||
let stream = if username.trim().is_empty() {
|
|
||||||
super::timeout(
|
|
||||||
ms_timeout,
|
|
||||||
Socks5Stream::connect_with_socket(stream, target),
|
|
||||||
)
|
|
||||||
.await??
|
|
||||||
} else {
|
|
||||||
super::timeout(
|
|
||||||
ms_timeout,
|
|
||||||
Socks5Stream::connect_with_password_and_socket(
|
|
||||||
stream, target, username, password,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.await??
|
|
||||||
};
|
|
||||||
let addr = stream.local_addr()?;
|
|
||||||
return Ok(Self(
|
|
||||||
Framed::new(DynTcpStream(Box::new(stream)), BytesCodec::new()),
|
|
||||||
addr,
|
|
||||||
None,
|
|
||||||
0,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
bail!("could not resolve to any address");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn local_addr(&self) -> SocketAddr {
|
pub fn local_addr(&self) -> SocketAddr {
|
||||||
|
|||||||
@ -221,7 +221,6 @@ impl Encoder {
|
|||||||
let h265_useable =
|
let h265_useable =
|
||||||
_all_support_h265_decoding && (h265vram_encoding || h265hw_encoding.is_some());
|
_all_support_h265_decoding && (h265vram_encoding || h265hw_encoding.is_some());
|
||||||
let mut name = ENCODE_CODEC_NAME.lock().unwrap();
|
let mut name = ENCODE_CODEC_NAME.lock().unwrap();
|
||||||
let mut preference = PreferCodec::Auto;
|
|
||||||
let preferences: Vec<_> = decodings
|
let preferences: Vec<_> = decodings
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|(_, s)| {
|
.filter(|(_, s)| {
|
||||||
@ -233,9 +232,20 @@ impl Encoder {
|
|||||||
})
|
})
|
||||||
.map(|(_, s)| s.prefer)
|
.map(|(_, s)| s.prefer)
|
||||||
.collect();
|
.collect();
|
||||||
if preferences.len() > 0 && preferences.iter().all(|&p| p == preferences[0]) {
|
// find the most frequent preference
|
||||||
preference = preferences[0].enum_value_or(PreferCodec::Auto);
|
let mut counts = Vec::new();
|
||||||
|
for pref in &preferences {
|
||||||
|
match counts.iter_mut().find(|(p, _)| p == pref) {
|
||||||
|
Some((_, count)) => *count += 1,
|
||||||
|
None => counts.push((pref.clone(), 1)),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
let max_count = counts.iter().map(|(_, count)| *count).max().unwrap_or(0);
|
||||||
|
let (most_frequent, _) = counts
|
||||||
|
.into_iter()
|
||||||
|
.find(|(_, count)| *count == max_count)
|
||||||
|
.unwrap_or((PreferCodec::Auto.into(), 0));
|
||||||
|
let preference = most_frequent.enum_value_or(PreferCodec::Auto);
|
||||||
|
|
||||||
#[allow(unused_mut)]
|
#[allow(unused_mut)]
|
||||||
let mut auto_codec = CodecName::VP9;
|
let mut auto_codec = CodecName::VP9;
|
||||||
|
|||||||
@ -17,15 +17,13 @@ use hbb_common::{
|
|||||||
};
|
};
|
||||||
use hwcodec::{
|
use hwcodec::{
|
||||||
common::{DataFormat, Driver, MAX_GOP},
|
common::{DataFormat, Driver, MAX_GOP},
|
||||||
native::{
|
vram::{
|
||||||
decode::{self, DecodeFrame, Decoder},
|
decode::{self, DecodeFrame, Decoder},
|
||||||
encode::{self, EncodeFrame, Encoder},
|
encode::{self, EncodeFrame, Encoder},
|
||||||
Available, DecodeContext, DynamicContext, EncodeContext, FeatureContext,
|
Available, DecodeContext, DynamicContext, EncodeContext, FeatureContext,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const OUTPUT_SHARED_HANDLE: bool = false;
|
|
||||||
|
|
||||||
// https://www.reddit.com/r/buildapc/comments/d2m4ny/two_graphics_cards_two_monitors/
|
// https://www.reddit.com/r/buildapc/comments/d2m4ny/two_graphics_cards_two_monitors/
|
||||||
// https://www.reddit.com/r/techsupport/comments/t2v9u6/dual_monitor_setup_with_dual_gpu/
|
// https://www.reddit.com/r/techsupport/comments/t2v9u6/dual_monitor_setup_with_dual_gpu/
|
||||||
// https://cybersided.com/two-monitors-two-gpus/
|
// https://cybersided.com/two-monitors-two-gpus/
|
||||||
@ -294,6 +292,10 @@ impl VRamDecoder {
|
|||||||
pub fn try_get(format: CodecFormat, luid: Option<i64>) -> Option<DecodeContext> {
|
pub fn try_get(format: CodecFormat, luid: Option<i64>) -> Option<DecodeContext> {
|
||||||
let v: Vec<_> = Self::available(format, luid);
|
let v: Vec<_> = Self::available(format, luid);
|
||||||
if v.len() > 0 {
|
if v.len() > 0 {
|
||||||
|
// prefer ffmpeg
|
||||||
|
if let Some(ctx) = v.iter().find(|c| c.driver == Driver::FFMPEG) {
|
||||||
|
return Some(ctx.clone());
|
||||||
|
}
|
||||||
Some(v[0].clone())
|
Some(v[0].clone())
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
@ -327,8 +329,8 @@ impl VRamDecoder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn new(format: CodecFormat, luid: Option<i64>) -> ResultType<Self> {
|
pub fn new(format: CodecFormat, luid: Option<i64>) -> ResultType<Self> {
|
||||||
log::info!("try create {format:?} vram decoder, luid: {luid:?}");
|
|
||||||
let ctx = Self::try_get(format, luid).ok_or(anyhow!("Failed to get decode context"))?;
|
let ctx = Self::try_get(format, luid).ok_or(anyhow!("Failed to get decode context"))?;
|
||||||
|
log::info!("try create vram decoder: {ctx:?}");
|
||||||
match Decoder::new(ctx) {
|
match Decoder::new(ctx) {
|
||||||
Ok(decoder) => Ok(Self { decoder }),
|
Ok(decoder) => Ok(Self { decoder }),
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
@ -372,7 +374,7 @@ pub(crate) fn check_available_vram() -> String {
|
|||||||
gop: MAX_GOP as _,
|
gop: MAX_GOP as _,
|
||||||
};
|
};
|
||||||
let encoders = encode::available(d);
|
let encoders = encode::available(d);
|
||||||
let decoders = decode::available(OUTPUT_SHARED_HANDLE);
|
let decoders = decode::available();
|
||||||
let available = Available {
|
let available = Available {
|
||||||
e: encoders,
|
e: encoders,
|
||||||
d: decoders,
|
d: decoders,
|
||||||
|
|||||||
39
res/job.py
39
res/job.py
@ -23,17 +23,21 @@ SECRET_KEY = os.getenv("SECRET_KEY") or "worldpeace2024"
|
|||||||
# The headers for API requests
|
# The headers for API requests
|
||||||
HEADERS = {"Authorization": f"Bearer {SECRET_KEY}"}
|
HEADERS = {"Authorization": f"Bearer {SECRET_KEY}"}
|
||||||
|
|
||||||
TIMEOUT = int(os.getenv("TIMEOUT") or "30")
|
SIGN_TIMEOUT = int(os.getenv("SIGN_TIMEOUT") or "30")
|
||||||
|
TIMEOUT = float(os.getenv("TIMEOUT") or "900")
|
||||||
|
|
||||||
|
|
||||||
def create(task_name, file_path=None):
|
def create(task_name, file_path=None):
|
||||||
if file_path is None:
|
if file_path is None:
|
||||||
response = requests.post(f"{BASE_URL}/tasks/{task_name}", headers=HEADERS)
|
response = requests.post(
|
||||||
|
f"{BASE_URL}/tasks/{task_name}", timeout=TIMEOUT, headers=HEADERS
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
with open(file_path, "rb") as f:
|
with open(file_path, "rb") as f:
|
||||||
files = {"file": f}
|
files = {"file": f}
|
||||||
response = requests.post(
|
response = requests.post(
|
||||||
f"{BASE_URL}/tasks/{task_name}",
|
f"{BASE_URL}/tasks/{task_name}",
|
||||||
|
timeout=TIMEOUT,
|
||||||
headers=HEADERS,
|
headers=HEADERS,
|
||||||
files=files,
|
files=files,
|
||||||
)
|
)
|
||||||
@ -44,19 +48,27 @@ def upload_file(task_id, file_path):
|
|||||||
with open(file_path, "rb") as f:
|
with open(file_path, "rb") as f:
|
||||||
files = {"file": f}
|
files = {"file": f}
|
||||||
response = requests.post(
|
response = requests.post(
|
||||||
f"{BASE_URL}/tasks/{task_id}/files", headers=HEADERS, files=files
|
f"{BASE_URL}/tasks/{task_id}/files",
|
||||||
|
timeout=TIMEOUT,
|
||||||
|
headers=HEADERS,
|
||||||
|
files=files,
|
||||||
)
|
)
|
||||||
return get_json(response)
|
return get_json(response)
|
||||||
|
|
||||||
|
|
||||||
def get_status(task_id):
|
def get_status(task_id):
|
||||||
response = requests.get(f"{BASE_URL}/tasks/{task_id}/status", headers=HEADERS)
|
response = requests.get(
|
||||||
|
f"{BASE_URL}/tasks/{task_id}/status", timeout=TIMEOUT, headers=HEADERS
|
||||||
|
)
|
||||||
return get_json(response)
|
return get_json(response)
|
||||||
|
|
||||||
|
|
||||||
def download_files(task_id, output_dir, fn=None):
|
def download_files(task_id, output_dir, fn=None):
|
||||||
response = requests.get(
|
response = requests.get(
|
||||||
f"{BASE_URL}/tasks/{task_id}/files", headers=HEADERS, stream=True
|
f"{BASE_URL}/tasks/{task_id}/files",
|
||||||
|
timeout=TIMEOUT,
|
||||||
|
headers=HEADERS,
|
||||||
|
stream=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Check if the request was successful
|
# Check if the request was successful
|
||||||
@ -73,7 +85,10 @@ def download_files(task_id, output_dir, fn=None):
|
|||||||
|
|
||||||
def download_one_file(task_id, file_id, output_dir):
|
def download_one_file(task_id, file_id, output_dir):
|
||||||
response = requests.get(
|
response = requests.get(
|
||||||
f"{BASE_URL}/tasks/{task_id}/files/{file_id}", headers=HEADERS, stream=True
|
f"{BASE_URL}/tasks/{task_id}/files/{file_id}",
|
||||||
|
timeout=TIMEOUT,
|
||||||
|
headers=HEADERS,
|
||||||
|
stream=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Check if the request was successful
|
# Check if the request was successful
|
||||||
@ -86,14 +101,19 @@ def download_one_file(task_id, file_id, output_dir):
|
|||||||
return response.ok
|
return response.ok
|
||||||
|
|
||||||
|
|
||||||
def fetch():
|
def fetch(tag=None):
|
||||||
response = requests.get(f"{BASE_URL}/tasks/fetch_task", headers=HEADERS)
|
response = requests.get(
|
||||||
|
f"{BASE_URL}/tasks/fetch_task" + ("?tag=%s" % tag if tag else ""),
|
||||||
|
timeout=TIMEOUT,
|
||||||
|
headers=HEADERS,
|
||||||
|
)
|
||||||
return get_json(response)
|
return get_json(response)
|
||||||
|
|
||||||
|
|
||||||
def update_status(task_id, status):
|
def update_status(task_id, status):
|
||||||
response = requests.patch(
|
response = requests.patch(
|
||||||
f"{BASE_URL}/tasks/{task_id}/status",
|
f"{BASE_URL}/tasks/{task_id}/status",
|
||||||
|
timeout=TIMEOUT,
|
||||||
headers=HEADERS,
|
headers=HEADERS,
|
||||||
json=status,
|
json=status,
|
||||||
)
|
)
|
||||||
@ -103,6 +123,7 @@ def update_status(task_id, status):
|
|||||||
def delete_task(task_id):
|
def delete_task(task_id):
|
||||||
response = requests.delete(
|
response = requests.delete(
|
||||||
f"{BASE_URL}/tasks/{task_id}",
|
f"{BASE_URL}/tasks/{task_id}",
|
||||||
|
timeout=TIMEOUT,
|
||||||
headers=HEADERS,
|
headers=HEADERS,
|
||||||
)
|
)
|
||||||
return get_json(response)
|
return get_json(response)
|
||||||
@ -135,7 +156,7 @@ def sign_one_file(file_path):
|
|||||||
task_id = res["id"]
|
task_id = res["id"]
|
||||||
n = 0
|
n = 0
|
||||||
while True:
|
while True:
|
||||||
if n >= TIMEOUT:
|
if n >= SIGN_TIMEOUT:
|
||||||
delete_task(task_id)
|
delete_task(task_id)
|
||||||
logging.error(f"Failed to sign {file_path}")
|
logging.error(f"Failed to sign {file_path}")
|
||||||
break
|
break
|
||||||
|
|||||||
2
res/msi/.gitignore
vendored
2
res/msi/.gitignore
vendored
@ -9,3 +9,5 @@ packages
|
|||||||
CustomActions/x64
|
CustomActions/x64
|
||||||
CustomActions/*.user
|
CustomActions/*.user
|
||||||
CustomActions/*.filters
|
CustomActions/*.filters
|
||||||
|
|
||||||
|
Package/Resources
|
||||||
|
|||||||
@ -16,7 +16,7 @@
|
|||||||
<Icon Id="AppIcon" SourceFile="Resources\icon.ico" />
|
<Icon Id="AppIcon" SourceFile="Resources\icon.ico" />
|
||||||
|
|
||||||
<!-- User Interface -->
|
<!-- User Interface -->
|
||||||
<WixVariable Id="WixUILicenseRtf" Value="RustDesk License.rtf" />
|
<WixVariable Id="WixUILicenseRtf" Value="License.rtf" />
|
||||||
|
|
||||||
<ui:WixUI Id="WixUI_InstallDir" InstallDirectory="INSTALLFOLDER" />
|
<ui:WixUI Id="WixUI_InstallDir" InstallDirectory="INSTALLFOLDER" />
|
||||||
<UIRef Id="WixUI_ErrorProgressText" />
|
<UIRef Id="WixUI_ErrorProgressText" />
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 97 KiB |
@ -9,6 +9,7 @@ import datetime
|
|||||||
import subprocess
|
import subprocess
|
||||||
import re
|
import re
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
import shutil
|
||||||
|
|
||||||
g_indent_unit = "\t"
|
g_indent_unit = "\t"
|
||||||
g_version = ""
|
g_version = ""
|
||||||
@ -391,6 +392,19 @@ def gen_content_between_tags(filename, tag_start, tag_end, func):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def prepare_resources():
|
||||||
|
icon_src = Path(sys.argv[0]).parent.joinpath("../icon.ico")
|
||||||
|
icon_dst = Path(sys.argv[0]).parent.joinpath("Package/Resources/icon.ico")
|
||||||
|
if icon_src.exists():
|
||||||
|
icon_dst.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
shutil.copy(icon_src, icon_dst)
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
# unreachable
|
||||||
|
print(f"Error: icon.ico not found in {icon_src}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
def init_global_vars(dist_dir, app_name, args):
|
def init_global_vars(dist_dir, app_name, args):
|
||||||
dist_app = dist_dir.joinpath(app_name + ".exe")
|
dist_app = dist_dir.joinpath(app_name + ".exe")
|
||||||
|
|
||||||
@ -423,6 +437,19 @@ def init_global_vars(dist_dir, app_name, args):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def update_license_file(app_name):
|
||||||
|
if app_name == "RustDesk":
|
||||||
|
return
|
||||||
|
license_file = Path(sys.argv[0]).parent.joinpath("Package/License.rtf")
|
||||||
|
with open(license_file, "r") as f:
|
||||||
|
license_content = f.read()
|
||||||
|
license_content = license_content.replace("website rustdesk.com and other ", "")
|
||||||
|
license_content = license_content.replace("RustDesk", app_name)
|
||||||
|
license_content = re.sub("Purslane Ltd", app_name, license_content, flags=re.IGNORECASE)
|
||||||
|
with open(license_file, "w") as f:
|
||||||
|
f.write(license_content)
|
||||||
|
|
||||||
|
|
||||||
def replace_component_guids_in_wxs():
|
def replace_component_guids_in_wxs():
|
||||||
langs_dir = Path(sys.argv[0]).parent.joinpath("Package")
|
langs_dir = Path(sys.argv[0]).parent.joinpath("Package")
|
||||||
for file_path in langs_dir.glob("**/*.wxs"):
|
for file_path in langs_dir.glob("**/*.wxs"):
|
||||||
@ -446,9 +473,14 @@ if __name__ == "__main__":
|
|||||||
app_name = args.app_name
|
app_name = args.app_name
|
||||||
dist_dir = Path(sys.argv[0]).parent.joinpath(args.dist_dir).resolve()
|
dist_dir = Path(sys.argv[0]).parent.joinpath(args.dist_dir).resolve()
|
||||||
|
|
||||||
|
if not prepare_resources():
|
||||||
|
sys.exit(-1)
|
||||||
|
|
||||||
if not init_global_vars(dist_dir, app_name, args):
|
if not init_global_vars(dist_dir, app_name, args):
|
||||||
sys.exit(-1)
|
sys.exit(-1)
|
||||||
|
|
||||||
|
update_license_file(app_name)
|
||||||
|
|
||||||
if not gen_pre_vars(args, dist_dir):
|
if not gen_pre_vars(args, dist_dir):
|
||||||
sys.exit(-1)
|
sys.exit(-1)
|
||||||
|
|
||||||
|
|||||||
@ -1456,6 +1456,22 @@ impl LoginConfigHandler {
|
|||||||
BoolOption::No
|
BoolOption::No
|
||||||
})
|
})
|
||||||
.into();
|
.into();
|
||||||
|
} else if name == "follow-remote-cursor" {
|
||||||
|
config.follow_remote_cursor.v = !config.follow_remote_cursor.v;
|
||||||
|
option.follow_remote_cursor = (if config.follow_remote_cursor.v {
|
||||||
|
BoolOption::Yes
|
||||||
|
} else {
|
||||||
|
BoolOption::No
|
||||||
|
})
|
||||||
|
.into();
|
||||||
|
} else if name == "follow-remote-window" {
|
||||||
|
config.follow_remote_window.v = !config.follow_remote_window.v;
|
||||||
|
option.follow_remote_window = (if config.follow_remote_window.v {
|
||||||
|
BoolOption::Yes
|
||||||
|
} else {
|
||||||
|
BoolOption::No
|
||||||
|
})
|
||||||
|
.into();
|
||||||
} else if name == "disable-audio" {
|
} else if name == "disable-audio" {
|
||||||
config.disable_audio.v = !config.disable_audio.v;
|
config.disable_audio.v = !config.disable_audio.v;
|
||||||
option.disable_audio = (if config.disable_audio.v {
|
option.disable_audio = (if config.disable_audio.v {
|
||||||
@ -1564,7 +1580,10 @@ impl LoginConfigHandler {
|
|||||||
///
|
///
|
||||||
/// * `ignore_default` - If `true`, ignore the default value of the option.
|
/// * `ignore_default` - If `true`, ignore the default value of the option.
|
||||||
fn get_option_message(&self, ignore_default: bool) -> Option<OptionMessage> {
|
fn get_option_message(&self, ignore_default: bool) -> Option<OptionMessage> {
|
||||||
if self.conn_type.eq(&ConnType::PORT_FORWARD) || self.conn_type.eq(&ConnType::RDP) || self.conn_type.eq(&ConnType::FILE_TRANSFER) {
|
if self.conn_type.eq(&ConnType::PORT_FORWARD)
|
||||||
|
|| self.conn_type.eq(&ConnType::RDP)
|
||||||
|
|| self.conn_type.eq(&ConnType::FILE_TRANSFER)
|
||||||
|
{
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
let mut msg = OptionMessage::new();
|
let mut msg = OptionMessage::new();
|
||||||
@ -1601,6 +1620,12 @@ impl LoginConfigHandler {
|
|||||||
if view_only || self.get_toggle_option("show-remote-cursor") {
|
if view_only || self.get_toggle_option("show-remote-cursor") {
|
||||||
msg.show_remote_cursor = BoolOption::Yes.into();
|
msg.show_remote_cursor = BoolOption::Yes.into();
|
||||||
}
|
}
|
||||||
|
if self.get_toggle_option("follow-remote-cursor") {
|
||||||
|
msg.follow_remote_cursor = BoolOption::Yes.into();
|
||||||
|
}
|
||||||
|
if self.get_toggle_option("follow-remote-window") {
|
||||||
|
msg.follow_remote_window = BoolOption::Yes.into();
|
||||||
|
}
|
||||||
if !view_only && self.get_toggle_option("lock-after-session-end") {
|
if !view_only && self.get_toggle_option("lock-after-session-end") {
|
||||||
msg.lock_after_session_end = BoolOption::Yes.into();
|
msg.lock_after_session_end = BoolOption::Yes.into();
|
||||||
}
|
}
|
||||||
@ -1692,6 +1717,10 @@ impl LoginConfigHandler {
|
|||||||
self.config.allow_swap_key.v
|
self.config.allow_swap_key.v
|
||||||
} else if name == "view-only" {
|
} else if name == "view-only" {
|
||||||
self.config.view_only.v
|
self.config.view_only.v
|
||||||
|
} else if name == "follow-remote-cursor" {
|
||||||
|
self.config.follow_remote_cursor.v
|
||||||
|
} else if name == "follow-remote-window" {
|
||||||
|
self.config.follow_remote_window.v
|
||||||
} else {
|
} else {
|
||||||
!self.get_option(name).is_empty()
|
!self.get_option(name).is_empty()
|
||||||
}
|
}
|
||||||
@ -2084,7 +2113,7 @@ where
|
|||||||
std::thread::spawn(move || {
|
std::thread::spawn(move || {
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
sync_cpu_usage();
|
sync_cpu_usage();
|
||||||
let mut handler_controller_map = Vec::new();
|
let mut handler_controller_map = HashMap::new();
|
||||||
// let mut count = Vec::new();
|
// let mut count = Vec::new();
|
||||||
// let mut duration = std::time::Duration::ZERO;
|
// let mut duration = std::time::Duration::ZERO;
|
||||||
// let mut skip_beginning = Vec::new();
|
// let mut skip_beginning = Vec::new();
|
||||||
@ -2115,17 +2144,18 @@ where
|
|||||||
let display = vf.display as usize;
|
let display = vf.display as usize;
|
||||||
let start = std::time::Instant::now();
|
let start = std::time::Instant::now();
|
||||||
let format = CodecFormat::from(&vf);
|
let format = CodecFormat::from(&vf);
|
||||||
if handler_controller_map.len() <= display {
|
if !handler_controller_map.contains_key(&display) {
|
||||||
for _i in handler_controller_map.len()..=display {
|
handler_controller_map.insert(
|
||||||
handler_controller_map.push(VideoHandlerController {
|
display,
|
||||||
handler: VideoHandler::new(format, _i),
|
VideoHandlerController {
|
||||||
|
handler: VideoHandler::new(format, display),
|
||||||
count: 0,
|
count: 0,
|
||||||
duration: std::time::Duration::ZERO,
|
duration: std::time::Duration::ZERO,
|
||||||
skip_beginning: 0,
|
skip_beginning: 0,
|
||||||
});
|
},
|
||||||
}
|
);
|
||||||
}
|
}
|
||||||
if let Some(handler_controller) = handler_controller_map.get_mut(display) {
|
if let Some(handler_controller) = handler_controller_map.get_mut(&display) {
|
||||||
let mut pixelbuffer = true;
|
let mut pixelbuffer = true;
|
||||||
let mut tmp_chroma = None;
|
let mut tmp_chroma = None;
|
||||||
match handler_controller.handler.handle_frame(
|
match handler_controller.handler.handle_frame(
|
||||||
@ -2193,7 +2223,7 @@ where
|
|||||||
let mut should_update_supported = false;
|
let mut should_update_supported = false;
|
||||||
handler_controller_map
|
handler_controller_map
|
||||||
.iter()
|
.iter()
|
||||||
.map(|h| {
|
.map(|(_, h)| {
|
||||||
if !h.handler.decoder.valid() || h.handler.fail_counter >= MAX_DECODE_FAIL_COUNTER {
|
if !h.handler.decoder.valid() || h.handler.fail_counter >= MAX_DECODE_FAIL_COUNTER {
|
||||||
let mut lc = session.lc.write().unwrap();
|
let mut lc = session.lc.write().unwrap();
|
||||||
let format = h.handler.decoder.format();
|
let format = h.handler.decoder.format();
|
||||||
@ -2212,21 +2242,20 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
MediaData::Reset(display) => {
|
MediaData::Reset(display) => {
|
||||||
if let Some(handler_controler) = handler_controller_map.get_mut(display) {
|
if let Some(handler_controler) = handler_controller_map.get_mut(&display) {
|
||||||
handler_controler.handler.reset(None);
|
handler_controler.handler.reset(None);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
MediaData::RecordScreen(start, display, w, h, id) => {
|
MediaData::RecordScreen(start, display, w, h, id) => {
|
||||||
log::info!("record screen command: start: {start}, display: {display}");
|
log::info!("record screen command: start: {start}, display: {display}");
|
||||||
if handler_controller_map.len() == 1 {
|
// Compatible with the sciter version(single ui session).
|
||||||
// Compatible with the sciter version(single ui session).
|
// For the sciter version, there're no multi-ui-sessions for one connection.
|
||||||
// For the sciter version, there're no multi-ui-sessions for one connection.
|
// The display is always 0, video_handler_controllers.len() is always 1. So we use the first video handler.
|
||||||
// The display is always 0, video_handler_controllers.len() is always 1. So we use the first video handler.
|
if let Some(handler_controler) = handler_controller_map.get_mut(&display) {
|
||||||
handler_controller_map[0]
|
handler_controler.handler.record_screen(start, w, h, id);
|
||||||
.handler
|
} else if handler_controller_map.len() == 1 {
|
||||||
.record_screen(start, w, h, id);
|
if let Some(handler_controler) =
|
||||||
} else {
|
handler_controller_map.values_mut().next()
|
||||||
if let Some(handler_controler) = handler_controller_map.get_mut(display)
|
|
||||||
{
|
{
|
||||||
handler_controler.handler.record_screen(start, w, h, id);
|
handler_controler.handler.record_screen(start, w, h, id);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1504,7 +1504,9 @@ impl<T: InvokeUiSession> Remote<T> {
|
|||||||
log::info!("update supported encoding:{:?}", e);
|
log::info!("update supported encoding:{:?}", e);
|
||||||
self.handler.lc.write().unwrap().supported_encoding = e;
|
self.handler.lc.write().unwrap().supported_encoding = e;
|
||||||
}
|
}
|
||||||
|
Some(misc::Union::FollowCurrentDisplay(d_idx)) => {
|
||||||
|
self.handler.set_current_display(d_idx);
|
||||||
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
},
|
},
|
||||||
Some(message::Union::TestDelay(t)) => {
|
Some(message::Union::TestDelay(t)) => {
|
||||||
|
|||||||
@ -5,6 +5,8 @@ use std::{
|
|||||||
task::Poll,
|
task::Poll,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use serde_json::Value;
|
||||||
|
|
||||||
#[derive(Debug, Eq, PartialEq)]
|
#[derive(Debug, Eq, PartialEq)]
|
||||||
pub enum GrabState {
|
pub enum GrabState {
|
||||||
Ready,
|
Ready,
|
||||||
@ -123,7 +125,7 @@ use hbb_common::compress::decompress;
|
|||||||
use hbb_common::{
|
use hbb_common::{
|
||||||
allow_err,
|
allow_err,
|
||||||
anyhow::{anyhow, Context},
|
anyhow::{anyhow, Context},
|
||||||
bail,
|
bail, base64,
|
||||||
bytes::Bytes,
|
bytes::Bytes,
|
||||||
compress::compress as compress_func,
|
compress::compress as compress_func,
|
||||||
config::{self, Config, CONNECT_TIMEOUT, READ_TIMEOUT},
|
config::{self, Config, CONNECT_TIMEOUT, READ_TIMEOUT},
|
||||||
@ -145,7 +147,10 @@ use hbb_common::{
|
|||||||
// #[cfg(any(target_os = "android", target_os = "ios", feature = "cli"))]
|
// #[cfg(any(target_os = "android", target_os = "ios", feature = "cli"))]
|
||||||
use hbb_common::{config::RENDEZVOUS_PORT, futures::future::join_all};
|
use hbb_common::{config::RENDEZVOUS_PORT, futures::future::join_all};
|
||||||
|
|
||||||
use crate::ui_interface::{get_option, set_option};
|
use crate::{
|
||||||
|
hbbs_http::create_http_client_async,
|
||||||
|
ui_interface::{get_option, set_option},
|
||||||
|
};
|
||||||
|
|
||||||
pub type NotifyMessageBox = fn(String, String, String, String) -> dyn Future<Output = ()>;
|
pub type NotifyMessageBox = fn(String, String, String, String) -> dyn Future<Output = ()>;
|
||||||
|
|
||||||
@ -972,7 +977,7 @@ pub fn check_software_update() {
|
|||||||
#[tokio::main(flavor = "current_thread")]
|
#[tokio::main(flavor = "current_thread")]
|
||||||
async fn check_software_update_() -> hbb_common::ResultType<()> {
|
async fn check_software_update_() -> hbb_common::ResultType<()> {
|
||||||
let url = "https://github.com/rustdesk/rustdesk/releases/latest";
|
let url = "https://github.com/rustdesk/rustdesk/releases/latest";
|
||||||
let latest_release_response = reqwest::get(url).await?;
|
let latest_release_response = create_http_client_async().get(url).send().await?;
|
||||||
let latest_release_version = latest_release_response
|
let latest_release_version = latest_release_response
|
||||||
.url()
|
.url()
|
||||||
.path()
|
.path()
|
||||||
@ -1067,7 +1072,7 @@ pub fn get_audit_server(api: String, custom: String, typ: String) -> String {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn post_request(url: String, body: String, header: &str) -> ResultType<String> {
|
pub async fn post_request(url: String, body: String, header: &str) -> ResultType<String> {
|
||||||
let mut req = reqwest::Client::new().post(url);
|
let mut req = create_http_client_async().post(url);
|
||||||
if !header.is_empty() {
|
if !header.is_empty() {
|
||||||
let tmp: Vec<&str> = header.split(": ").collect();
|
let tmp: Vec<&str> = header.split(": ").collect();
|
||||||
if tmp.len() == 2 {
|
if tmp.len() == 2 {
|
||||||
@ -1084,6 +1089,65 @@ pub async fn post_request_sync(url: String, body: String, header: &str) -> Resul
|
|||||||
post_request(url, body, header).await
|
post_request(url, body, header).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::main(flavor = "current_thread")]
|
||||||
|
pub async fn http_request_sync(
|
||||||
|
url: String,
|
||||||
|
method: String,
|
||||||
|
body: Option<String>,
|
||||||
|
header: String,
|
||||||
|
) -> ResultType<String> {
|
||||||
|
let http_client = create_http_client_async();
|
||||||
|
let mut http_client = match method.as_str() {
|
||||||
|
"get" => http_client.get(url),
|
||||||
|
"post" => http_client.post(url),
|
||||||
|
"put" => http_client.put(url),
|
||||||
|
"delete" => http_client.delete(url),
|
||||||
|
_ => return Err(anyhow!("The HTTP request method is not supported!")),
|
||||||
|
};
|
||||||
|
let v = serde_json::from_str(header.as_str())?;
|
||||||
|
|
||||||
|
if let Value::Object(obj) = v {
|
||||||
|
for (key, value) in obj.iter() {
|
||||||
|
http_client = http_client.header(key, value.as_str().unwrap_or_default());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Err(anyhow!("HTTP header information parsing failed!"));
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(b) = body {
|
||||||
|
http_client = http_client.body(b);
|
||||||
|
}
|
||||||
|
|
||||||
|
let response = http_client
|
||||||
|
.timeout(std::time::Duration::from_secs(12))
|
||||||
|
.send()
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
// Serialize response headers
|
||||||
|
let mut response_headers = serde_json::map::Map::new();
|
||||||
|
for (key, value) in response.headers() {
|
||||||
|
response_headers.insert(
|
||||||
|
key.to_string(),
|
||||||
|
serde_json::json!(value.to_str().unwrap_or("")),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let status_code = response.status().as_u16();
|
||||||
|
let response_body = response.text().await?;
|
||||||
|
|
||||||
|
// Construct the JSON object
|
||||||
|
let mut result = serde_json::map::Map::new();
|
||||||
|
result.insert("status_code".to_string(), serde_json::json!(status_code));
|
||||||
|
result.insert(
|
||||||
|
"headers".to_string(),
|
||||||
|
serde_json::Value::Object(response_headers),
|
||||||
|
);
|
||||||
|
result.insert("body".to_string(), serde_json::json!(response_body));
|
||||||
|
|
||||||
|
// Convert map to JSON string
|
||||||
|
serde_json::to_string(&result).map_err(|e| anyhow!("Failed to serialize response: {}", e))
|
||||||
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn make_privacy_mode_msg_with_details(
|
pub fn make_privacy_mode_msg_with_details(
|
||||||
state: back_notification::PrivacyModeState,
|
state: back_notification::PrivacyModeState,
|
||||||
|
|||||||
@ -1,5 +1,9 @@
|
|||||||
use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine as _};
|
use hbb_common::{
|
||||||
use hbb_common::{bail, sodiumoxide::crypto::sign, ResultType};
|
bail,
|
||||||
|
base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine as _},
|
||||||
|
sodiumoxide::crypto::sign,
|
||||||
|
ResultType,
|
||||||
|
};
|
||||||
use serde_derive::{Deserialize, Serialize};
|
use serde_derive::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Default, Serialize, Deserialize, Clone)]
|
#[derive(Debug, PartialEq, Default, Serialize, Deserialize, Clone)]
|
||||||
|
|||||||
@ -882,6 +882,21 @@ impl InvokeUiSession for FlutterHandler {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn is_multi_ui_session(&self) -> bool {
|
||||||
|
self.session_handlers.read().unwrap().len() > 1
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_current_display(&self, disp_idx: i32) {
|
||||||
|
if self.is_multi_ui_session() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
self.push_event(
|
||||||
|
"follow_current_display",
|
||||||
|
&[("display_idx", &disp_idx.to_string())],
|
||||||
|
&[],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
fn on_connected(&self, _conn_type: ConnType) {}
|
fn on_connected(&self, _conn_type: ConnType) {}
|
||||||
|
|
||||||
fn msgbox(&self, msgtype: &str, title: &str, text: &str, link: &str, retry: bool) {
|
fn msgbox(&self, msgtype: &str, title: &str, text: &str, link: &str, retry: bool) {
|
||||||
|
|||||||
@ -213,6 +213,14 @@ pub fn session_refresh(session_id: SessionID, display: usize) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn session_is_multi_ui_session(session_id: SessionID) -> SyncReturn<bool> {
|
||||||
|
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||||
|
SyncReturn(session.is_multi_ui_session())
|
||||||
|
} else {
|
||||||
|
SyncReturn(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn session_record_screen(
|
pub fn session_record_screen(
|
||||||
session_id: SessionID,
|
session_id: SessionID,
|
||||||
start: bool,
|
start: bool,
|
||||||
@ -749,6 +757,10 @@ pub fn main_get_async_status() -> String {
|
|||||||
get_async_job_status()
|
get_async_job_status()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn main_get_http_status(url: String) -> Option<String> {
|
||||||
|
get_async_http_status(url)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn main_get_option(key: String) -> String {
|
pub fn main_get_option(key: String) -> String {
|
||||||
get_option(key)
|
get_option(key)
|
||||||
}
|
}
|
||||||
@ -797,14 +809,18 @@ pub fn main_set_options(json: String) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn main_test_if_valid_server(server: String) -> String {
|
pub fn main_test_if_valid_server(server: String, test_with_proxy: bool) -> String {
|
||||||
test_if_valid_server(server)
|
test_if_valid_server(server, test_with_proxy)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn main_set_socks(proxy: String, username: String, password: String) {
|
pub fn main_set_socks(proxy: String, username: String, password: String) {
|
||||||
set_socks(proxy, username, password)
|
set_socks(proxy, username, password)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn main_get_proxy_status() -> bool {
|
||||||
|
get_proxy_status()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn main_get_socks() -> Vec<String> {
|
pub fn main_get_socks() -> Vec<String> {
|
||||||
get_socks()
|
get_socks()
|
||||||
}
|
}
|
||||||
@ -878,9 +894,8 @@ pub fn main_get_api_server() -> String {
|
|||||||
get_api_server()
|
get_api_server()
|
||||||
}
|
}
|
||||||
|
|
||||||
// This function doesn't seem to be used.
|
pub fn main_http_request(url: String, method: String, body: Option<String>, header: String) {
|
||||||
pub fn main_post_request(url: String, body: String, header: String) {
|
http_request(url, method, body, header)
|
||||||
post_request(url, body, header)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn main_get_local_option(key: String) -> SyncReturn<String> {
|
pub fn main_get_local_option(key: String) -> SyncReturn<String> {
|
||||||
|
|||||||
@ -4,8 +4,11 @@ use serde_json::{Map, Value};
|
|||||||
|
|
||||||
#[cfg(feature = "flutter")]
|
#[cfg(feature = "flutter")]
|
||||||
pub mod account;
|
pub mod account;
|
||||||
|
mod http_client;
|
||||||
pub mod record_upload;
|
pub mod record_upload;
|
||||||
pub mod sync;
|
pub mod sync;
|
||||||
|
pub use http_client::create_http_client;
|
||||||
|
pub use http_client::create_http_client_async;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum HbbHttpResponse<T> {
|
pub enum HbbHttpResponse<T> {
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
use super::HbbHttpResponse;
|
use super::HbbHttpResponse;
|
||||||
|
use crate::hbbs_http::create_http_client;
|
||||||
use hbb_common::{config::LocalConfig, log, ResultType};
|
use hbb_common::{config::LocalConfig, log, ResultType};
|
||||||
use reqwest::blocking::Client;
|
use reqwest::blocking::Client;
|
||||||
use serde_derive::{Deserialize, Serialize};
|
use serde_derive::{Deserialize, Serialize};
|
||||||
@ -130,7 +131,7 @@ impl Default for UserStatus {
|
|||||||
impl OidcSession {
|
impl OidcSession {
|
||||||
fn new() -> Self {
|
fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
client: Client::new(),
|
client: create_http_client(),
|
||||||
state_msg: REQUESTING_ACCOUNT_AUTH,
|
state_msg: REQUESTING_ACCOUNT_AUTH,
|
||||||
failed_msg: "".to_owned(),
|
failed_msg: "".to_owned(),
|
||||||
code_url: None,
|
code_url: None,
|
||||||
@ -168,7 +169,7 @@ impl OidcSession {
|
|||||||
id: &str,
|
id: &str,
|
||||||
uuid: &str,
|
uuid: &str,
|
||||||
) -> ResultType<HbbHttpResponse<AuthBody>> {
|
) -> ResultType<HbbHttpResponse<AuthBody>> {
|
||||||
let url = reqwest::Url::parse_with_params(
|
let url = Url::parse_with_params(
|
||||||
&format!("{}/api/oidc/auth-query", api_server),
|
&format!("{}/api/oidc/auth-query", api_server),
|
||||||
&[("code", code), ("id", id), ("uuid", uuid)],
|
&[("code", code), ("id", id), ("uuid", uuid)],
|
||||||
)?;
|
)?;
|
||||||
|
|||||||
71
src/hbbs_http/http_client.rs
Normal file
71
src/hbbs_http/http_client.rs
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
use hbb_common::config::Config;
|
||||||
|
use hbb_common::log::info;
|
||||||
|
use hbb_common::proxy::{Proxy, ProxyScheme};
|
||||||
|
use reqwest::blocking::Client as SyncClient;
|
||||||
|
use reqwest::Client as AsyncClient;
|
||||||
|
|
||||||
|
macro_rules! configure_http_client {
|
||||||
|
($builder:expr, $Client: ty) => {{
|
||||||
|
let mut builder = $builder;
|
||||||
|
let client = if let Some(conf) = Config::get_socks() {
|
||||||
|
let proxy_result = Proxy::from_conf(&conf, None);
|
||||||
|
|
||||||
|
match proxy_result {
|
||||||
|
Ok(proxy) => {
|
||||||
|
let proxy_setup = match &proxy.intercept {
|
||||||
|
ProxyScheme::Http { host, .. } =>{ reqwest::Proxy::http(format!("http://{}", host))},
|
||||||
|
ProxyScheme::Https { host, .. } => {reqwest::Proxy::https(format!("https://{}", host))},
|
||||||
|
ProxyScheme::Socks5 { addr, .. } => { reqwest::Proxy::all(&format!("socks5://{}", addr)) }
|
||||||
|
};
|
||||||
|
|
||||||
|
match proxy_setup {
|
||||||
|
Ok(p) => {
|
||||||
|
builder = builder.proxy(p);
|
||||||
|
if let Some(auth) = proxy.intercept.maybe_auth() {
|
||||||
|
let basic_auth =
|
||||||
|
format!("Basic {}", auth.get_basic_authorization());
|
||||||
|
builder = builder.default_headers(
|
||||||
|
vec![(
|
||||||
|
reqwest::header::PROXY_AUTHORIZATION,
|
||||||
|
basic_auth.parse().unwrap(),
|
||||||
|
)]
|
||||||
|
.into_iter()
|
||||||
|
.collect(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
builder.build().unwrap_or_else(|e| {
|
||||||
|
info!("Failed to create a proxied client: {}", e);
|
||||||
|
<$Client>::new()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
info!("Failed to set up proxy: {}", e);
|
||||||
|
<$Client>::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
info!("Failed to configure proxy: {}", e);
|
||||||
|
<$Client>::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
builder.build().unwrap_or_else(|e| {
|
||||||
|
info!("Failed to create a client: {}", e);
|
||||||
|
<$Client>::new()
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
client
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_http_client() -> SyncClient {
|
||||||
|
let builder = SyncClient::builder();
|
||||||
|
configure_http_client!(builder, SyncClient)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_http_client_async() -> AsyncClient {
|
||||||
|
let builder = AsyncClient::builder();
|
||||||
|
configure_http_client!(builder, AsyncClient)
|
||||||
|
}
|
||||||
@ -1,3 +1,4 @@
|
|||||||
|
use crate::hbbs_http::create_http_client;
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use hbb_common::{bail, config::Config, lazy_static, log, ResultType};
|
use hbb_common::{bail, config::Config, lazy_static, log, ResultType};
|
||||||
use reqwest::blocking::{Body, Client};
|
use reqwest::blocking::{Body, Client};
|
||||||
@ -25,7 +26,7 @@ pub fn is_enable() -> bool {
|
|||||||
|
|
||||||
pub fn run(rx: Receiver<RecordState>) {
|
pub fn run(rx: Receiver<RecordState>) {
|
||||||
let mut uploader = RecordUploader {
|
let mut uploader = RecordUploader {
|
||||||
client: Client::new(),
|
client: create_http_client(),
|
||||||
api_server: crate::get_api_server(
|
api_server: crate::get_api_server(
|
||||||
Config::get_option("api-server"),
|
Config::get_option("api-server"),
|
||||||
Config::get_option("custom-rendezvous-server"),
|
Config::get_option("custom-rendezvous-server"),
|
||||||
|
|||||||
13
src/ipc.rs
13
src/ipc.rs
@ -233,6 +233,7 @@ pub enum Data {
|
|||||||
ControlledSessionCount(usize),
|
ControlledSessionCount(usize),
|
||||||
CmErr(String),
|
CmErr(String),
|
||||||
CheckHwcodec,
|
CheckHwcodec,
|
||||||
|
VideoConnCount(Option<usize>),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::main(flavor = "current_thread")]
|
#[tokio::main(flavor = "current_thread")]
|
||||||
@ -382,6 +383,15 @@ async fn handle(data: Data, stream: &mut Connection) {
|
|||||||
log::info!("socks updated");
|
log::info!("socks updated");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
Data::VideoConnCount(None) => {
|
||||||
|
let n = crate::server::AUTHED_CONNS
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.iter()
|
||||||
|
.filter(|x| x.1 == crate::server::AuthConnType::Remote)
|
||||||
|
.count();
|
||||||
|
allow_err!(stream.send(&Data::VideoConnCount(Some(n))).await);
|
||||||
|
}
|
||||||
Data::Config((name, value)) => match value {
|
Data::Config((name, value)) => match value {
|
||||||
None => {
|
None => {
|
||||||
let value;
|
let value;
|
||||||
@ -904,6 +914,9 @@ pub async fn set_socks(value: config::Socks5Server) -> ResultType<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_proxy_status() -> bool {
|
||||||
|
Config::get_socks().is_some()
|
||||||
|
}
|
||||||
#[tokio::main(flavor = "current_thread")]
|
#[tokio::main(flavor = "current_thread")]
|
||||||
pub async fn test_rendezvous_server() -> ResultType<()> {
|
pub async fn test_rendezvous_server() -> ResultType<()> {
|
||||||
let mut c = connect(1000, "").await?;
|
let mut c = connect(1000, "").await?;
|
||||||
|
|||||||
@ -239,6 +239,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Empty", "فارغ"),
|
("Empty", "فارغ"),
|
||||||
("Invalid folder name", "اسم المجلد غير صحيح"),
|
("Invalid folder name", "اسم المجلد غير صحيح"),
|
||||||
("Socks5 Proxy", "وكيل Socks5"),
|
("Socks5 Proxy", "وكيل Socks5"),
|
||||||
|
("Socks5/Http(s) Proxy", "وكيل Socks5/Http(s)"),
|
||||||
("Discovered", "المكتشفة"),
|
("Discovered", "المكتشفة"),
|
||||||
("install_daemon_tip", "للبدء مع بدء تشغيل النظام. تحتاج الى تثبيت خدمة النظام."),
|
("install_daemon_tip", "للبدء مع بدء تشغيل النظام. تحتاج الى تثبيت خدمة النظام."),
|
||||||
("Remote ID", "المعرف البعيد"),
|
("Remote ID", "المعرف البعيد"),
|
||||||
@ -602,5 +603,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("ab_web_console_tip", ""),
|
("ab_web_console_tip", ""),
|
||||||
("allow-only-conn-window-open-tip", ""),
|
("allow-only-conn-window-open-tip", ""),
|
||||||
("no_need_privacy_mode_no_physical_displays_tip", ""),
|
("no_need_privacy_mode_no_physical_displays_tip", ""),
|
||||||
|
("Follow remote cursor", ""),
|
||||||
|
("Follow remote window focus", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -239,6 +239,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Empty", ""),
|
("Empty", ""),
|
||||||
("Invalid folder name", ""),
|
("Invalid folder name", ""),
|
||||||
("Socks5 Proxy", "Socks5 прокси"),
|
("Socks5 Proxy", "Socks5 прокси"),
|
||||||
|
("Socks5/Http(s) Proxy", "Socks5/Http(s) прокси"),
|
||||||
("Discovered", ""),
|
("Discovered", ""),
|
||||||
("install_daemon_tip", "За стартиране с компютъра трябва да инсталирате системна услуга."),
|
("install_daemon_tip", "За стартиране с компютъра трябва да инсталирате системна услуга."),
|
||||||
("Remote ID", ""),
|
("Remote ID", ""),
|
||||||
@ -602,5 +603,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("ab_web_console_tip", ""),
|
("ab_web_console_tip", ""),
|
||||||
("allow-only-conn-window-open-tip", ""),
|
("allow-only-conn-window-open-tip", ""),
|
||||||
("no_need_privacy_mode_no_physical_displays_tip", ""),
|
("no_need_privacy_mode_no_physical_displays_tip", ""),
|
||||||
|
("Follow remote cursor", ""),
|
||||||
|
("Follow remote window focus", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -239,6 +239,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Empty", "Buit"),
|
("Empty", "Buit"),
|
||||||
("Invalid folder name", "Nom de carpeta incorrecte"),
|
("Invalid folder name", "Nom de carpeta incorrecte"),
|
||||||
("Socks5 Proxy", "Proxy Socks5"),
|
("Socks5 Proxy", "Proxy Socks5"),
|
||||||
|
("Socks5/Http(s) Proxy", "Proxy Socks5/Http(s)"),
|
||||||
("Discovered", "Descobert"),
|
("Discovered", "Descobert"),
|
||||||
("install_daemon_tip", ""),
|
("install_daemon_tip", ""),
|
||||||
("Remote ID", "ID remot"),
|
("Remote ID", "ID remot"),
|
||||||
@ -602,5 +603,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("ab_web_console_tip", ""),
|
("ab_web_console_tip", ""),
|
||||||
("allow-only-conn-window-open-tip", ""),
|
("allow-only-conn-window-open-tip", ""),
|
||||||
("no_need_privacy_mode_no_physical_displays_tip", ""),
|
("no_need_privacy_mode_no_physical_displays_tip", ""),
|
||||||
|
("Follow remote cursor", ""),
|
||||||
|
("Follow remote window focus", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -239,6 +239,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Empty", "空空如也"),
|
("Empty", "空空如也"),
|
||||||
("Invalid folder name", "无效文件夹名称"),
|
("Invalid folder name", "无效文件夹名称"),
|
||||||
("Socks5 Proxy", "Socks5 代理"),
|
("Socks5 Proxy", "Socks5 代理"),
|
||||||
|
("Socks5/Http(s) Proxy", "Socks5/Http(s) 代理"),
|
||||||
|
("Default protocol and port are Socks5 and 1080", "默认代理协议及端口为Socks5和1080"),
|
||||||
("Discovered", "已发现"),
|
("Discovered", "已发现"),
|
||||||
("install_daemon_tip", "为了开机启动,请安装系统服务。"),
|
("install_daemon_tip", "为了开机启动,请安装系统服务。"),
|
||||||
("Remote ID", "远程 ID"),
|
("Remote ID", "远程 ID"),
|
||||||
@ -602,5 +604,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("ab_web_console_tip", "打开 Web 控制台以执行更多操作"),
|
("ab_web_console_tip", "打开 Web 控制台以执行更多操作"),
|
||||||
("allow-only-conn-window-open-tip", "仅当 RustDesk 窗口打开时允许连接"),
|
("allow-only-conn-window-open-tip", "仅当 RustDesk 窗口打开时允许连接"),
|
||||||
("no_need_privacy_mode_no_physical_displays_tip", "没有物理显示器,没必要使用隐私模式。"),
|
("no_need_privacy_mode_no_physical_displays_tip", "没有物理显示器,没必要使用隐私模式。"),
|
||||||
|
("Follow remote cursor", ""),
|
||||||
|
("Follow remote window focus", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -239,6 +239,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Empty", "Prázdné"),
|
("Empty", "Prázdné"),
|
||||||
("Invalid folder name", "Neplatný název složky"),
|
("Invalid folder name", "Neplatný název složky"),
|
||||||
("Socks5 Proxy", "Socks5 proxy"),
|
("Socks5 Proxy", "Socks5 proxy"),
|
||||||
|
("Socks5/Http(s) Proxy", "Socks5/Http(s) proxy"),
|
||||||
("Discovered", "Objeveno"),
|
("Discovered", "Objeveno"),
|
||||||
("install_daemon_tip", "Pokud má být spouštěno při startu systému, je třeba nainstalovat systémovou službu."),
|
("install_daemon_tip", "Pokud má být spouštěno při startu systému, je třeba nainstalovat systémovou službu."),
|
||||||
("Remote ID", "Vzdálené ID"),
|
("Remote ID", "Vzdálené ID"),
|
||||||
@ -602,5 +603,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("ab_web_console_tip", "Více na webové konzoli"),
|
("ab_web_console_tip", "Více na webové konzoli"),
|
||||||
("allow-only-conn-window-open-tip", "Povolit připojení pouze v případě, že je otevřené okno RustDesk"),
|
("allow-only-conn-window-open-tip", "Povolit připojení pouze v případě, že je otevřené okno RustDesk"),
|
||||||
("no_need_privacy_mode_no_physical_displays_tip", "Žádné fyzické displeje, není třeba používat režim soukromí."),
|
("no_need_privacy_mode_no_physical_displays_tip", "Žádné fyzické displeje, není třeba používat režim soukromí."),
|
||||||
|
("Follow remote cursor", "Sledovat dálkový kurzor"),
|
||||||
|
("Follow remote window focus", "Sledovat zaměření vzdáleného okna"),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -239,6 +239,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Empty", "Tom"),
|
("Empty", "Tom"),
|
||||||
("Invalid folder name", "Ugyldigt mappenavn"),
|
("Invalid folder name", "Ugyldigt mappenavn"),
|
||||||
("Socks5 Proxy", "Socks5 Proxy"),
|
("Socks5 Proxy", "Socks5 Proxy"),
|
||||||
|
("Socks5/Http(s) Proxy", "Socks5/Http(s) Proxy"),
|
||||||
("Discovered", "Fundet"),
|
("Discovered", "Fundet"),
|
||||||
("install_daemon_tip", "For at starte efter PC'en er startet op, skal du installere systemtjenesten"),
|
("install_daemon_tip", "For at starte efter PC'en er startet op, skal du installere systemtjenesten"),
|
||||||
("Remote ID", "Fjern-ID"),
|
("Remote ID", "Fjern-ID"),
|
||||||
@ -602,5 +603,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("ab_web_console_tip", ""),
|
("ab_web_console_tip", ""),
|
||||||
("allow-only-conn-window-open-tip", ""),
|
("allow-only-conn-window-open-tip", ""),
|
||||||
("no_need_privacy_mode_no_physical_displays_tip", ""),
|
("no_need_privacy_mode_no_physical_displays_tip", ""),
|
||||||
|
("Follow remote cursor", ""),
|
||||||
|
("Follow remote window focus", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -239,6 +239,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Empty", "Keine Einträge"),
|
("Empty", "Keine Einträge"),
|
||||||
("Invalid folder name", "Ungültiger Ordnername"),
|
("Invalid folder name", "Ungültiger Ordnername"),
|
||||||
("Socks5 Proxy", "SOCKS5-Proxy"),
|
("Socks5 Proxy", "SOCKS5-Proxy"),
|
||||||
|
("Socks5/Http(s) Proxy", "SOCKS5/HTTP(S)-Proxy"),
|
||||||
("Discovered", "Im LAN erkannt"),
|
("Discovered", "Im LAN erkannt"),
|
||||||
("install_daemon_tip", "Um mit System zu starten, muss der Systemdienst installiert sein."),
|
("install_daemon_tip", "Um mit System zu starten, muss der Systemdienst installiert sein."),
|
||||||
("Remote ID", "Entfernte ID"),
|
("Remote ID", "Entfernte ID"),
|
||||||
@ -602,5 +603,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("ab_web_console_tip", "Mehr über Webkonsole"),
|
("ab_web_console_tip", "Mehr über Webkonsole"),
|
||||||
("allow-only-conn-window-open-tip", "Verbindung nur zulassen, wenn das RustDesk-Fenster geöffnet ist"),
|
("allow-only-conn-window-open-tip", "Verbindung nur zulassen, wenn das RustDesk-Fenster geöffnet ist"),
|
||||||
("no_need_privacy_mode_no_physical_displays_tip", "Keine physischen Bildschirme; keine Notwendigkeit, den Datenschutzmodus zu verwenden."),
|
("no_need_privacy_mode_no_physical_displays_tip", "Keine physischen Bildschirme; keine Notwendigkeit, den Datenschutzmodus zu verwenden."),
|
||||||
|
("Follow remote cursor", "Dem entfernten Cursor folgen"),
|
||||||
|
("Follow remote window focus", "Dem Fokus des entfernten Fensters folgen"),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -239,6 +239,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Empty", "Άδειο"),
|
("Empty", "Άδειο"),
|
||||||
("Invalid folder name", "Μη έγκυρο όνομα φακέλου"),
|
("Invalid folder name", "Μη έγκυρο όνομα φακέλου"),
|
||||||
("Socks5 Proxy", "Διαμεσολαβητής Socks5"),
|
("Socks5 Proxy", "Διαμεσολαβητής Socks5"),
|
||||||
|
("Socks5/Http(s) Proxy", "Διαμεσολαβητής Socks5/Http(s)"),
|
||||||
("Discovered", "Ανακαλύφθηκαν"),
|
("Discovered", "Ανακαλύφθηκαν"),
|
||||||
("install_daemon_tip", "Για να ξεκινά με την εκκίνηση του υπολογιστή, πρέπει να εγκαταστήσετε την υπηρεσία συστήματος"),
|
("install_daemon_tip", "Για να ξεκινά με την εκκίνηση του υπολογιστή, πρέπει να εγκαταστήσετε την υπηρεσία συστήματος"),
|
||||||
("Remote ID", "Απομακρυσμένο ID"),
|
("Remote ID", "Απομακρυσμένο ID"),
|
||||||
@ -602,5 +603,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("ab_web_console_tip", ""),
|
("ab_web_console_tip", ""),
|
||||||
("allow-only-conn-window-open-tip", ""),
|
("allow-only-conn-window-open-tip", ""),
|
||||||
("no_need_privacy_mode_no_physical_displays_tip", ""),
|
("no_need_privacy_mode_no_physical_displays_tip", ""),
|
||||||
|
("Follow remote cursor", ""),
|
||||||
|
("Follow remote window focus", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -60,6 +60,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Add to Favorites", "Add to favorites"),
|
("Add to Favorites", "Add to favorites"),
|
||||||
("Remove from Favorites", "Remove from favorites"),
|
("Remove from Favorites", "Remove from favorites"),
|
||||||
("Socks5 Proxy", "Socks5 proxy"),
|
("Socks5 Proxy", "Socks5 proxy"),
|
||||||
|
("Socks5/Http(s) Proxy", "Socks5/Http(s) proxy"),
|
||||||
|
("Default protocol and port are Socks5 and 1080", "Default protocol and port are Socks5 and 1080"),
|
||||||
("install_daemon_tip", "For starting on boot, you need to install system service."),
|
("install_daemon_tip", "For starting on boot, you need to install system service."),
|
||||||
("Are you sure to close the connection?", "Are you sure you want to close the connection?"),
|
("Are you sure to close the connection?", "Are you sure you want to close the connection?"),
|
||||||
("One-Finger Tap", "One-finger tap"),
|
("One-Finger Tap", "One-finger tap"),
|
||||||
@ -220,5 +222,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("ab_web_console_tip", "More on web console"),
|
("ab_web_console_tip", "More on web console"),
|
||||||
("allow-only-conn-window-open-tip", "Only allow connection if RustDesk window is open"),
|
("allow-only-conn-window-open-tip", "Only allow connection if RustDesk window is open"),
|
||||||
("no_need_privacy_mode_no_physical_displays_tip", "No physical displays, no need to use the privacy mode."),
|
("no_need_privacy_mode_no_physical_displays_tip", "No physical displays, no need to use the privacy mode."),
|
||||||
|
("Follow remote cursor", ""),
|
||||||
|
("Follow remote window focus", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -239,6 +239,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Empty", "Malplena"),
|
("Empty", "Malplena"),
|
||||||
("Invalid folder name", "Dosiernomo nevalida"),
|
("Invalid folder name", "Dosiernomo nevalida"),
|
||||||
("Socks5 Proxy", "Socks5 prokura servilo"),
|
("Socks5 Proxy", "Socks5 prokura servilo"),
|
||||||
|
("Socks5/Http(s) Proxy", "Socks5/Http(s) prokura servilo"),
|
||||||
("Discovered", "Malkovritaj"),
|
("Discovered", "Malkovritaj"),
|
||||||
("install_daemon_tip", ""),
|
("install_daemon_tip", ""),
|
||||||
("Remote ID", "Fora identigilo"),
|
("Remote ID", "Fora identigilo"),
|
||||||
@ -602,5 +603,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("ab_web_console_tip", ""),
|
("ab_web_console_tip", ""),
|
||||||
("allow-only-conn-window-open-tip", ""),
|
("allow-only-conn-window-open-tip", ""),
|
||||||
("no_need_privacy_mode_no_physical_displays_tip", ""),
|
("no_need_privacy_mode_no_physical_displays_tip", ""),
|
||||||
|
("Follow remote cursor", ""),
|
||||||
|
("Follow remote window focus", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -239,6 +239,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Empty", "Vacío"),
|
("Empty", "Vacío"),
|
||||||
("Invalid folder name", "Nombre de carpeta incorrecto"),
|
("Invalid folder name", "Nombre de carpeta incorrecto"),
|
||||||
("Socks5 Proxy", "Proxy Socks5"),
|
("Socks5 Proxy", "Proxy Socks5"),
|
||||||
|
("Socks5/Http(s) Proxy", "Proxy Socks5/Http(s)"),
|
||||||
("Discovered", "Descubierto"),
|
("Discovered", "Descubierto"),
|
||||||
("install_daemon_tip", "Para comenzar en el encendido, debe instalar el servicio del sistema."),
|
("install_daemon_tip", "Para comenzar en el encendido, debe instalar el servicio del sistema."),
|
||||||
("Remote ID", "ID remoto"),
|
("Remote ID", "ID remoto"),
|
||||||
@ -602,5 +603,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("ab_web_console_tip", "Más en consola web"),
|
("ab_web_console_tip", "Más en consola web"),
|
||||||
("allow-only-conn-window-open-tip", "Permitir la conexión solo si la ventana RusDesk está abierta"),
|
("allow-only-conn-window-open-tip", "Permitir la conexión solo si la ventana RusDesk está abierta"),
|
||||||
("no_need_privacy_mode_no_physical_displays_tip", "No hay pantallas físicas, no es necesario usar el modo privado."),
|
("no_need_privacy_mode_no_physical_displays_tip", "No hay pantallas físicas, no es necesario usar el modo privado."),
|
||||||
|
("Follow remote cursor", ""),
|
||||||
|
("Follow remote window focus", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -239,6 +239,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Empty", ""),
|
("Empty", ""),
|
||||||
("Invalid folder name", ""),
|
("Invalid folder name", ""),
|
||||||
("Socks5 Proxy", "Socks5 proksi"),
|
("Socks5 Proxy", "Socks5 proksi"),
|
||||||
|
("Socks5/Http(s) Proxy", "Socks5/Http(s) proksi"),
|
||||||
("Discovered", ""),
|
("Discovered", ""),
|
||||||
("install_daemon_tip", "Süsteemikäivitusel käivitamiseks tuleb paigaldada süsteemiteenus."),
|
("install_daemon_tip", "Süsteemikäivitusel käivitamiseks tuleb paigaldada süsteemiteenus."),
|
||||||
("Remote ID", ""),
|
("Remote ID", ""),
|
||||||
@ -602,5 +603,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("ab_web_console_tip", ""),
|
("ab_web_console_tip", ""),
|
||||||
("allow-only-conn-window-open-tip", ""),
|
("allow-only-conn-window-open-tip", ""),
|
||||||
("no_need_privacy_mode_no_physical_displays_tip", ""),
|
("no_need_privacy_mode_no_physical_displays_tip", ""),
|
||||||
|
("Follow remote cursor", ""),
|
||||||
|
("Follow remote window focus", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -239,6 +239,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Empty", "موردی وجود ندارد"),
|
("Empty", "موردی وجود ندارد"),
|
||||||
("Invalid folder name", "نام پوشه نامعتبر است"),
|
("Invalid folder name", "نام پوشه نامعتبر است"),
|
||||||
("Socks5 Proxy", "Socks5 پروکسی"),
|
("Socks5 Proxy", "Socks5 پروکسی"),
|
||||||
|
("Socks5/Http(s) Proxy", "Socks5/Http(s) پروکسی"),
|
||||||
("Discovered", "پیدا شده"),
|
("Discovered", "پیدا شده"),
|
||||||
("install_daemon_tip", "برای شروع در هنگام راه اندازی، باید سرویس سیستم را نصب کنید"),
|
("install_daemon_tip", "برای شروع در هنگام راه اندازی، باید سرویس سیستم را نصب کنید"),
|
||||||
("Remote ID", "شناسه راه دور"),
|
("Remote ID", "شناسه راه دور"),
|
||||||
@ -602,5 +603,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("ab_web_console_tip", "اطلاعات بیشتر در کنسول وب"),
|
("ab_web_console_tip", "اطلاعات بیشتر در کنسول وب"),
|
||||||
("allow-only-conn-window-open-tip", ""),
|
("allow-only-conn-window-open-tip", ""),
|
||||||
("no_need_privacy_mode_no_physical_displays_tip", ""),
|
("no_need_privacy_mode_no_physical_displays_tip", ""),
|
||||||
|
("Follow remote cursor", ""),
|
||||||
|
("Follow remote window focus", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -239,6 +239,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Empty", "Vide"),
|
("Empty", "Vide"),
|
||||||
("Invalid folder name", "Nom de dossier invalide"),
|
("Invalid folder name", "Nom de dossier invalide"),
|
||||||
("Socks5 Proxy", "Socks5 Agents"),
|
("Socks5 Proxy", "Socks5 Agents"),
|
||||||
|
("Socks5/Http(s) Proxy", "Socks5/Http(s) Agents"),
|
||||||
("Discovered", "Découvert"),
|
("Discovered", "Découvert"),
|
||||||
("install_daemon_tip", "Pour une exécution au démarrage du système, vous devez installer le service système."),
|
("install_daemon_tip", "Pour une exécution au démarrage du système, vous devez installer le service système."),
|
||||||
("Remote ID", "ID de l'appareil distant"),
|
("Remote ID", "ID de l'appareil distant"),
|
||||||
@ -602,5 +603,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("ab_web_console_tip", ""),
|
("ab_web_console_tip", ""),
|
||||||
("allow-only-conn-window-open-tip", ""),
|
("allow-only-conn-window-open-tip", ""),
|
||||||
("no_need_privacy_mode_no_physical_displays_tip", ""),
|
("no_need_privacy_mode_no_physical_displays_tip", ""),
|
||||||
|
("Follow remote cursor", ""),
|
||||||
|
("Follow remote window focus", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -239,6 +239,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Empty", ""),
|
("Empty", ""),
|
||||||
("Invalid folder name", ""),
|
("Invalid folder name", ""),
|
||||||
("Socks5 Proxy", "פרוקסי Socks5"),
|
("Socks5 Proxy", "פרוקסי Socks5"),
|
||||||
|
("Socks5/Http(s) Proxy", "פרוקסי Socks5/Http(s)"),
|
||||||
("Discovered", ""),
|
("Discovered", ""),
|
||||||
("install_daemon_tip", "לצורך הפעלה בעת הפעלת המחשב, עליך להתקין שירות מערכת."),
|
("install_daemon_tip", "לצורך הפעלה בעת הפעלת המחשב, עליך להתקין שירות מערכת."),
|
||||||
("Remote ID", ""),
|
("Remote ID", ""),
|
||||||
@ -602,5 +603,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("ab_web_console_tip", ""),
|
("ab_web_console_tip", ""),
|
||||||
("allow-only-conn-window-open-tip", ""),
|
("allow-only-conn-window-open-tip", ""),
|
||||||
("no_need_privacy_mode_no_physical_displays_tip", ""),
|
("no_need_privacy_mode_no_physical_displays_tip", ""),
|
||||||
|
("Follow remote cursor", ""),
|
||||||
|
("Follow remote window focus", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -602,5 +602,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("ab_web_console_tip", "Više na web konzoli"),
|
("ab_web_console_tip", "Više na web konzoli"),
|
||||||
("allow-only-conn-window-open-tip", ""),
|
("allow-only-conn-window-open-tip", ""),
|
||||||
("no_need_privacy_mode_no_physical_displays_tip", ""),
|
("no_need_privacy_mode_no_physical_displays_tip", ""),
|
||||||
|
("Follow remote cursor", ""),
|
||||||
|
("Follow remote window focus", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -239,6 +239,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Empty", "Üres"),
|
("Empty", "Üres"),
|
||||||
("Invalid folder name", "Helytelen mappa név"),
|
("Invalid folder name", "Helytelen mappa név"),
|
||||||
("Socks5 Proxy", "Socks5 Proxy"),
|
("Socks5 Proxy", "Socks5 Proxy"),
|
||||||
|
("Socks5/Http(s) Proxy", "Socks5/Http(s) Proxy"),
|
||||||
("Discovered", "Felfedezett"),
|
("Discovered", "Felfedezett"),
|
||||||
("install_daemon_tip", "Az automatikus indításhoz szükséges a szolgáltatás telepítése"),
|
("install_daemon_tip", "Az automatikus indításhoz szükséges a szolgáltatás telepítése"),
|
||||||
("Remote ID", "Távoli azonosító"),
|
("Remote ID", "Távoli azonosító"),
|
||||||
@ -602,5 +603,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("ab_web_console_tip", ""),
|
("ab_web_console_tip", ""),
|
||||||
("allow-only-conn-window-open-tip", ""),
|
("allow-only-conn-window-open-tip", ""),
|
||||||
("no_need_privacy_mode_no_physical_displays_tip", ""),
|
("no_need_privacy_mode_no_physical_displays_tip", ""),
|
||||||
|
("Follow remote cursor", ""),
|
||||||
|
("Follow remote window focus", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -239,6 +239,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Empty", "Kosong"),
|
("Empty", "Kosong"),
|
||||||
("Invalid folder name", "Nama folder tidak valid"),
|
("Invalid folder name", "Nama folder tidak valid"),
|
||||||
("Socks5 Proxy", "Proksi Socks5"),
|
("Socks5 Proxy", "Proksi Socks5"),
|
||||||
|
("Socks5/Http(s) Proxy", "Proksi Socks5/Http(s)"),
|
||||||
("Discovered", "Telah ditemukan"),
|
("Discovered", "Telah ditemukan"),
|
||||||
("install_daemon_tip", "Untuk memulai saat boot, Anda perlu menginstal system service."),
|
("install_daemon_tip", "Untuk memulai saat boot, Anda perlu menginstal system service."),
|
||||||
("Remote ID", "ID Remote"),
|
("Remote ID", "ID Remote"),
|
||||||
@ -602,5 +603,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("ab_web_console_tip", ""),
|
("ab_web_console_tip", ""),
|
||||||
("allow-only-conn-window-open-tip", ""),
|
("allow-only-conn-window-open-tip", ""),
|
||||||
("no_need_privacy_mode_no_physical_displays_tip", ""),
|
("no_need_privacy_mode_no_physical_displays_tip", ""),
|
||||||
|
("Follow remote cursor", ""),
|
||||||
|
("Follow remote window focus", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -145,7 +145,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Failed to make direct connection to remote desktop", "Impossibile connettersi direttamente al desktop remoto"),
|
("Failed to make direct connection to remote desktop", "Impossibile connettersi direttamente al desktop remoto"),
|
||||||
("Set Password", "Imposta password"),
|
("Set Password", "Imposta password"),
|
||||||
("OS Password", "Password sistema operativo"),
|
("OS Password", "Password sistema operativo"),
|
||||||
("install_tip", "A causa del controllo account uUtente (UAC), RustDesk potrebbe non funzionare correttamente come desktop remoto.\nPer evitare questo problema, fai clic sul tasto qui sotto per installare RustDesk a livello di sistema."),
|
("install_tip", "A causa del Controllo Account Utente (UAC), RustDesk potrebbe non funzionare correttamente come desktop remoto.\nPer evitare questo problema, fai clic sul tasto qui sotto per installare RustDesk a livello di sistema."),
|
||||||
("Click to upgrade", "Aggiorna"),
|
("Click to upgrade", "Aggiorna"),
|
||||||
("Click to download", "Download"),
|
("Click to download", "Download"),
|
||||||
("Click to update", "Aggiorna"),
|
("Click to update", "Aggiorna"),
|
||||||
@ -239,6 +239,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Empty", "Vuoto"),
|
("Empty", "Vuoto"),
|
||||||
("Invalid folder name", "Nome della cartella non valido"),
|
("Invalid folder name", "Nome della cartella non valido"),
|
||||||
("Socks5 Proxy", "Proxy Socks5"),
|
("Socks5 Proxy", "Proxy Socks5"),
|
||||||
|
("Socks5/Http(s) Proxy", "Proxy Socks5/Http(s)"),
|
||||||
("Discovered", "Rilevate"),
|
("Discovered", "Rilevate"),
|
||||||
("install_daemon_tip", "Per avviare il programma all'accensione, è necessario installarlo come servizio di sistema."),
|
("install_daemon_tip", "Per avviare il programma all'accensione, è necessario installarlo come servizio di sistema."),
|
||||||
("Remote ID", "ID remoto"),
|
("Remote ID", "ID remoto"),
|
||||||
@ -602,5 +603,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("ab_web_console_tip", "Altre info sulla console web"),
|
("ab_web_console_tip", "Altre info sulla console web"),
|
||||||
("allow-only-conn-window-open-tip", "Consenti la connessione solo se la finestra RustDesk è aperta"),
|
("allow-only-conn-window-open-tip", "Consenti la connessione solo se la finestra RustDesk è aperta"),
|
||||||
("no_need_privacy_mode_no_physical_displays_tip", "Nessun display fisico, nessuna necessità di usare la modalità privacy."),
|
("no_need_privacy_mode_no_physical_displays_tip", "Nessun display fisico, nessuna necessità di usare la modalità privacy."),
|
||||||
|
("Follow remote cursor", "Segui cursore remoto"),
|
||||||
|
("Follow remote window focus", "Segui focus finestra remota"),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -239,6 +239,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Empty", "空"),
|
("Empty", "空"),
|
||||||
("Invalid folder name", "無効なフォルダ名"),
|
("Invalid folder name", "無効なフォルダ名"),
|
||||||
("Socks5 Proxy", "SOCKS5プロキシ"),
|
("Socks5 Proxy", "SOCKS5プロキシ"),
|
||||||
|
("Socks5/Http(s) Proxy", "Socks5/Http(s)プロキシ"),
|
||||||
("Discovered", "探知済み"),
|
("Discovered", "探知済み"),
|
||||||
("install_daemon_tip", "起動時に開始するには、システムサービスをインストールする必要があります。"),
|
("install_daemon_tip", "起動時に開始するには、システムサービスをインストールする必要があります。"),
|
||||||
("Remote ID", "リモートのID"),
|
("Remote ID", "リモートのID"),
|
||||||
@ -602,5 +603,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("ab_web_console_tip", ""),
|
("ab_web_console_tip", ""),
|
||||||
("allow-only-conn-window-open-tip", ""),
|
("allow-only-conn-window-open-tip", ""),
|
||||||
("no_need_privacy_mode_no_physical_displays_tip", ""),
|
("no_need_privacy_mode_no_physical_displays_tip", ""),
|
||||||
|
("Follow remote cursor", ""),
|
||||||
|
("Follow remote window focus", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -239,6 +239,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Empty", "비어 있음"),
|
("Empty", "비어 있음"),
|
||||||
("Invalid folder name", "유효하지 않은 폴더명"),
|
("Invalid folder name", "유효하지 않은 폴더명"),
|
||||||
("Socks5 Proxy", "Socks5 프록시"),
|
("Socks5 Proxy", "Socks5 프록시"),
|
||||||
|
("Socks5/Http(s) Proxy", "Socks5/Http(s) 프록시"),
|
||||||
("Discovered", "찾음"),
|
("Discovered", "찾음"),
|
||||||
("install_daemon_tip", "부팅된 이후 시스템 서비스에 설치해야 합니다."),
|
("install_daemon_tip", "부팅된 이후 시스템 서비스에 설치해야 합니다."),
|
||||||
("Remote ID", "원격 ID"),
|
("Remote ID", "원격 ID"),
|
||||||
@ -602,5 +603,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("ab_web_console_tip", ""),
|
("ab_web_console_tip", ""),
|
||||||
("allow-only-conn-window-open-tip", ""),
|
("allow-only-conn-window-open-tip", ""),
|
||||||
("no_need_privacy_mode_no_physical_displays_tip", ""),
|
("no_need_privacy_mode_no_physical_displays_tip", ""),
|
||||||
|
("Follow remote cursor", ""),
|
||||||
|
("Follow remote window focus", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -239,6 +239,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Empty", "Бос"),
|
("Empty", "Бос"),
|
||||||
("Invalid folder name", "Бұрыс бума атауы"),
|
("Invalid folder name", "Бұрыс бума атауы"),
|
||||||
("Socks5 Proxy", "Socks5 Proxy"),
|
("Socks5 Proxy", "Socks5 Proxy"),
|
||||||
|
("Socks5/Http(s) Proxy", "Socks5/Http(s) Proxy"),
|
||||||
("Discovered", "Табылды"),
|
("Discovered", "Табылды"),
|
||||||
("install_daemon_tip", "Бут кезінде қосылу үшін жүйелік сербесті орнатуыныз керек."),
|
("install_daemon_tip", "Бут кезінде қосылу үшін жүйелік сербесті орнатуыныз керек."),
|
||||||
("Remote ID", "Қашықтағы ID"),
|
("Remote ID", "Қашықтағы ID"),
|
||||||
@ -602,5 +603,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("ab_web_console_tip", ""),
|
("ab_web_console_tip", ""),
|
||||||
("allow-only-conn-window-open-tip", ""),
|
("allow-only-conn-window-open-tip", ""),
|
||||||
("no_need_privacy_mode_no_physical_displays_tip", ""),
|
("no_need_privacy_mode_no_physical_displays_tip", ""),
|
||||||
|
("Follow remote cursor", ""),
|
||||||
|
("Follow remote window focus", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -239,6 +239,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Empty", "Tuščia"),
|
("Empty", "Tuščia"),
|
||||||
("Invalid folder name", "Neteisingas aplanko pavadinimas"),
|
("Invalid folder name", "Neteisingas aplanko pavadinimas"),
|
||||||
("Socks5 Proxy", "Socks5 Proxy"),
|
("Socks5 Proxy", "Socks5 Proxy"),
|
||||||
|
("Socks5/Http(s) Proxy", "Socks5/Http(s) Proxy"),
|
||||||
("Discovered", "Aptikta tinkle"),
|
("Discovered", "Aptikta tinkle"),
|
||||||
("install_daemon_tip", "Norėdami, kad RustDesk startuotų automatiškai, turite ją įdiegti"),
|
("install_daemon_tip", "Norėdami, kad RustDesk startuotų automatiškai, turite ją įdiegti"),
|
||||||
("Remote ID", "Nuotolinis ID"),
|
("Remote ID", "Nuotolinis ID"),
|
||||||
@ -602,5 +603,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("ab_web_console_tip", ""),
|
("ab_web_console_tip", ""),
|
||||||
("allow-only-conn-window-open-tip", ""),
|
("allow-only-conn-window-open-tip", ""),
|
||||||
("no_need_privacy_mode_no_physical_displays_tip", ""),
|
("no_need_privacy_mode_no_physical_displays_tip", ""),
|
||||||
|
("Follow remote cursor", ""),
|
||||||
|
("Follow remote window focus", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -239,6 +239,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Empty", "Tukšs"),
|
("Empty", "Tukšs"),
|
||||||
("Invalid folder name", "Nederīgs mapes nosaukums"),
|
("Invalid folder name", "Nederīgs mapes nosaukums"),
|
||||||
("Socks5 Proxy", "Socks5 starpniekserveris"),
|
("Socks5 Proxy", "Socks5 starpniekserveris"),
|
||||||
|
("Socks5/Http(s) Proxy", "Socks5/Http(s) starpniekserveris"),
|
||||||
("Discovered", "Atklāts"),
|
("Discovered", "Atklāts"),
|
||||||
("install_daemon_tip", "Lai palaistu pie startēšanas, ir jāinstalē sistēmas serviss."),
|
("install_daemon_tip", "Lai palaistu pie startēšanas, ir jāinstalē sistēmas serviss."),
|
||||||
("Remote ID", "Attālais ID"),
|
("Remote ID", "Attālais ID"),
|
||||||
@ -602,5 +603,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("ab_web_console_tip", "Vairāk par tīmekļa konsoli"),
|
("ab_web_console_tip", "Vairāk par tīmekļa konsoli"),
|
||||||
("allow-only-conn-window-open-tip", "Atļaut savienojumu tikai tad, ja ir atvērts RustDesk logs"),
|
("allow-only-conn-window-open-tip", "Atļaut savienojumu tikai tad, ja ir atvērts RustDesk logs"),
|
||||||
("no_need_privacy_mode_no_physical_displays_tip", "Nav fizisku displeju, nav jāizmanto privātuma režīms."),
|
("no_need_privacy_mode_no_physical_displays_tip", "Nav fizisku displeju, nav jāizmanto privātuma režīms."),
|
||||||
|
("Follow remote cursor", "Sekot attālajam kursoram"),
|
||||||
|
("Follow remote window focus", "Sekot attālā loga fokusam"),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -239,6 +239,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Empty", "Tom"),
|
("Empty", "Tom"),
|
||||||
("Invalid folder name", "Ugyldig mappenavn"),
|
("Invalid folder name", "Ugyldig mappenavn"),
|
||||||
("Socks5 Proxy", "Socks5 Proxy"),
|
("Socks5 Proxy", "Socks5 Proxy"),
|
||||||
|
("Socks5/Http(s) Proxy", "Socks5/Http(s) Proxy"),
|
||||||
("Discovered", "Oppdaget"),
|
("Discovered", "Oppdaget"),
|
||||||
("install_daemon_tip", "For å starte når PC'en har startet opp, må du installere systemtjenesten"),
|
("install_daemon_tip", "For å starte når PC'en har startet opp, må du installere systemtjenesten"),
|
||||||
("Remote ID", "Fjern-ID"),
|
("Remote ID", "Fjern-ID"),
|
||||||
@ -602,5 +603,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("ab_web_console_tip", ""),
|
("ab_web_console_tip", ""),
|
||||||
("allow-only-conn-window-open-tip", ""),
|
("allow-only-conn-window-open-tip", ""),
|
||||||
("no_need_privacy_mode_no_physical_displays_tip", ""),
|
("no_need_privacy_mode_no_physical_displays_tip", ""),
|
||||||
|
("Follow remote cursor", ""),
|
||||||
|
("Follow remote window focus", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -239,6 +239,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Empty", "Leeg"),
|
("Empty", "Leeg"),
|
||||||
("Invalid folder name", "Ongeldige mapnaam"),
|
("Invalid folder name", "Ongeldige mapnaam"),
|
||||||
("Socks5 Proxy", "Socks5 Proxy"),
|
("Socks5 Proxy", "Socks5 Proxy"),
|
||||||
|
("Socks5/Http(s) Proxy", "Socks5/Http(s) Proxy"),
|
||||||
("Discovered", "Ontdekt"),
|
("Discovered", "Ontdekt"),
|
||||||
("install_daemon_tip", "Om bij het opstarten van de computer te kunnen beginnen, moet u de systeemservice installeren."),
|
("install_daemon_tip", "Om bij het opstarten van de computer te kunnen beginnen, moet u de systeemservice installeren."),
|
||||||
("Remote ID", "Externe ID"),
|
("Remote ID", "Externe ID"),
|
||||||
@ -601,6 +602,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Everyone", "Iedereen"),
|
("Everyone", "Iedereen"),
|
||||||
("ab_web_console_tip", "Meer over de webconsole"),
|
("ab_web_console_tip", "Meer over de webconsole"),
|
||||||
("allow-only-conn-window-open-tip", "Alleen verbindingen toestaan als het RustDesk-venster geopend is"),
|
("allow-only-conn-window-open-tip", "Alleen verbindingen toestaan als het RustDesk-venster geopend is"),
|
||||||
("no_need_privacy_mode_no_physical_displays_tip", ""),
|
("no_need_privacy_mode_no_physical_displays_tip", "Geen fysieke schermen, geen privémodus nodig."),
|
||||||
|
("Follow remote cursor", "Volg de cursor op afstand"),
|
||||||
|
("Follow remote window focus", "Volg de focus van het venster op afstand"),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -239,6 +239,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Empty", "Pusto"),
|
("Empty", "Pusto"),
|
||||||
("Invalid folder name", "Nieprawidłowa nazwa folderu"),
|
("Invalid folder name", "Nieprawidłowa nazwa folderu"),
|
||||||
("Socks5 Proxy", "Proxy Socks5"),
|
("Socks5 Proxy", "Proxy Socks5"),
|
||||||
|
("Socks5/Http(s) Proxy", "Proxy Socks5/Http(s)"),
|
||||||
("Discovered", "Wykryte"),
|
("Discovered", "Wykryte"),
|
||||||
("install_daemon_tip", "By uruchomić RustDesk przy starcie systemu, musisz zainstalować usługę systemową."),
|
("install_daemon_tip", "By uruchomić RustDesk przy starcie systemu, musisz zainstalować usługę systemową."),
|
||||||
("Remote ID", "Zdalne ID"),
|
("Remote ID", "Zdalne ID"),
|
||||||
@ -602,5 +603,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("ab_web_console_tip", "Więcej w konsoli web"),
|
("ab_web_console_tip", "Więcej w konsoli web"),
|
||||||
("allow-only-conn-window-open-tip", "Zezwalaj na połączenie tylko wtedy, gdy okno RustDesk jest otwarte"),
|
("allow-only-conn-window-open-tip", "Zezwalaj na połączenie tylko wtedy, gdy okno RustDesk jest otwarte"),
|
||||||
("no_need_privacy_mode_no_physical_displays_tip", ""),
|
("no_need_privacy_mode_no_physical_displays_tip", ""),
|
||||||
|
("Follow remote cursor", ""),
|
||||||
|
("Follow remote window focus", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -239,6 +239,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Empty", "Vazio"),
|
("Empty", "Vazio"),
|
||||||
("Invalid folder name", "Nome de diretório inválido"),
|
("Invalid folder name", "Nome de diretório inválido"),
|
||||||
("Socks5 Proxy", "Proxy Socks5"),
|
("Socks5 Proxy", "Proxy Socks5"),
|
||||||
|
("Socks5/Http(s) Proxy", "Proxy Socks5/Http(s)"),
|
||||||
("Discovered", "Descoberto"),
|
("Discovered", "Descoberto"),
|
||||||
("install_daemon_tip", "Para inicialização junto do sistema, deve instalar o serviço de sistema."),
|
("install_daemon_tip", "Para inicialização junto do sistema, deve instalar o serviço de sistema."),
|
||||||
("Remote ID", "ID Remoto"),
|
("Remote ID", "ID Remoto"),
|
||||||
@ -602,5 +603,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("ab_web_console_tip", ""),
|
("ab_web_console_tip", ""),
|
||||||
("allow-only-conn-window-open-tip", ""),
|
("allow-only-conn-window-open-tip", ""),
|
||||||
("no_need_privacy_mode_no_physical_displays_tip", ""),
|
("no_need_privacy_mode_no_physical_displays_tip", ""),
|
||||||
|
("Follow remote cursor", ""),
|
||||||
|
("Follow remote window focus", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -239,6 +239,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Empty", "Vazio"),
|
("Empty", "Vazio"),
|
||||||
("Invalid folder name", "Nome de diretório inválido"),
|
("Invalid folder name", "Nome de diretório inválido"),
|
||||||
("Socks5 Proxy", "Proxy Socks5"),
|
("Socks5 Proxy", "Proxy Socks5"),
|
||||||
|
("Socks5/Http(s) Proxy", "Proxy Socks5/Http(s)"),
|
||||||
("Discovered", "Descoberto"),
|
("Discovered", "Descoberto"),
|
||||||
("install_daemon_tip", "Para inicialização junto ao sistema, você deve instalar o serviço de sistema."),
|
("install_daemon_tip", "Para inicialização junto ao sistema, você deve instalar o serviço de sistema."),
|
||||||
("Remote ID", "ID Remoto"),
|
("Remote ID", "ID Remoto"),
|
||||||
@ -602,5 +603,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("ab_web_console_tip", ""),
|
("ab_web_console_tip", ""),
|
||||||
("allow-only-conn-window-open-tip", ""),
|
("allow-only-conn-window-open-tip", ""),
|
||||||
("no_need_privacy_mode_no_physical_displays_tip", ""),
|
("no_need_privacy_mode_no_physical_displays_tip", ""),
|
||||||
|
("Follow remote cursor", ""),
|
||||||
|
("Follow remote window focus", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -239,6 +239,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Empty", "Gol"),
|
("Empty", "Gol"),
|
||||||
("Invalid folder name", "Denumire folder nevalidă"),
|
("Invalid folder name", "Denumire folder nevalidă"),
|
||||||
("Socks5 Proxy", "Proxy Socks5"),
|
("Socks5 Proxy", "Proxy Socks5"),
|
||||||
|
("Socks5/Http(s) Proxy", "Proxy Socks5/Http(s)"),
|
||||||
("Discovered", "Descoperite"),
|
("Discovered", "Descoperite"),
|
||||||
("install_daemon_tip", "Pentru executare la pornirea sistemului, instalează serviciul de sistem."),
|
("install_daemon_tip", "Pentru executare la pornirea sistemului, instalează serviciul de sistem."),
|
||||||
("Remote ID", "ID dispozitiv la distanță"),
|
("Remote ID", "ID dispozitiv la distanță"),
|
||||||
@ -602,5 +603,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("ab_web_console_tip", ""),
|
("ab_web_console_tip", ""),
|
||||||
("allow-only-conn-window-open-tip", ""),
|
("allow-only-conn-window-open-tip", ""),
|
||||||
("no_need_privacy_mode_no_physical_displays_tip", ""),
|
("no_need_privacy_mode_no_physical_displays_tip", ""),
|
||||||
|
("Follow remote cursor", ""),
|
||||||
|
("Follow remote window focus", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -239,6 +239,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Empty", "Пусто"),
|
("Empty", "Пусто"),
|
||||||
("Invalid folder name", "Недопустимое имя папки"),
|
("Invalid folder name", "Недопустимое имя папки"),
|
||||||
("Socks5 Proxy", "SOCKS5-прокси"),
|
("Socks5 Proxy", "SOCKS5-прокси"),
|
||||||
|
("Socks5/Http(s) Proxy", "Socks5/Http(s)-прокси"),
|
||||||
("Discovered", "Найдено"),
|
("Discovered", "Найдено"),
|
||||||
("install_daemon_tip", "Для запуска при загрузке необходимо установить системную службу"),
|
("install_daemon_tip", "Для запуска при загрузке необходимо установить системную службу"),
|
||||||
("Remote ID", "Удалённый ID"),
|
("Remote ID", "Удалённый ID"),
|
||||||
@ -602,5 +603,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("ab_web_console_tip", "Больше в веб-консоли"),
|
("ab_web_console_tip", "Больше в веб-консоли"),
|
||||||
("allow-only-conn-window-open-tip", "Разрешать подключение только при открытом окне RustDesk"),
|
("allow-only-conn-window-open-tip", "Разрешать подключение только при открытом окне RustDesk"),
|
||||||
("no_need_privacy_mode_no_physical_displays_tip", "Физические дисплеи отсутствуют, нет необходимости использовать режим конфиденциальности."),
|
("no_need_privacy_mode_no_physical_displays_tip", "Физические дисплеи отсутствуют, нет необходимости использовать режим конфиденциальности."),
|
||||||
|
("Follow remote cursor", "Следовать за удалённым курсором"),
|
||||||
|
("Follow remote window focus", "Следовать за фокусом удалённого окна"),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -239,6 +239,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Empty", "Prázdne"),
|
("Empty", "Prázdne"),
|
||||||
("Invalid folder name", "Neplatný názov adresára"),
|
("Invalid folder name", "Neplatný názov adresára"),
|
||||||
("Socks5 Proxy", "Socks5 Proxy"),
|
("Socks5 Proxy", "Socks5 Proxy"),
|
||||||
|
("Socks5/Http(s) Proxy", "Socks5/Http(s) Proxy"),
|
||||||
("Discovered", "Objavené"),
|
("Discovered", "Objavené"),
|
||||||
("install_daemon_tip", "Ak chcete, aby sa spúšťal pri štarte systému, musíte nainštalovať systémovú službu."),
|
("install_daemon_tip", "Ak chcete, aby sa spúšťal pri štarte systému, musíte nainštalovať systémovú službu."),
|
||||||
("Remote ID", "Vzdialené ID"),
|
("Remote ID", "Vzdialené ID"),
|
||||||
@ -602,5 +603,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("ab_web_console_tip", "Viac na webovej konzole"),
|
("ab_web_console_tip", "Viac na webovej konzole"),
|
||||||
("allow-only-conn-window-open-tip", "Povoliť pripojenie iba vtedy, ak je otvorené okno aplikácie RustDesk"),
|
("allow-only-conn-window-open-tip", "Povoliť pripojenie iba vtedy, ak je otvorené okno aplikácie RustDesk"),
|
||||||
("no_need_privacy_mode_no_physical_displays_tip", "Žiadne fyzické displeje, nie je potrebné používať režim ochrany osobných údajov."),
|
("no_need_privacy_mode_no_physical_displays_tip", "Žiadne fyzické displeje, nie je potrebné používať režim ochrany osobných údajov."),
|
||||||
|
("Follow remote cursor", "Nasledovať vzdialený kurzor"),
|
||||||
|
("Follow remote window focus", "Nasledovať vzdialené zameranie okna"),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -239,6 +239,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Empty", "Prazno"),
|
("Empty", "Prazno"),
|
||||||
("Invalid folder name", "Napačno ime mape"),
|
("Invalid folder name", "Napačno ime mape"),
|
||||||
("Socks5 Proxy", "Socks5 posredniški strežnik"),
|
("Socks5 Proxy", "Socks5 posredniški strežnik"),
|
||||||
|
("Socks5/Http(s) Proxy", "Socks5/Http(s) posredniški strežnik"),
|
||||||
("Discovered", "Odkriti"),
|
("Discovered", "Odkriti"),
|
||||||
("install_daemon_tip", "Za samodejni zagon ob vklopu računalnika je potrebno dodati sistemsko storitev"),
|
("install_daemon_tip", "Za samodejni zagon ob vklopu računalnika je potrebno dodati sistemsko storitev"),
|
||||||
("Remote ID", "Oddaljeni ID"),
|
("Remote ID", "Oddaljeni ID"),
|
||||||
@ -602,5 +603,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("ab_web_console_tip", ""),
|
("ab_web_console_tip", ""),
|
||||||
("allow-only-conn-window-open-tip", ""),
|
("allow-only-conn-window-open-tip", ""),
|
||||||
("no_need_privacy_mode_no_physical_displays_tip", ""),
|
("no_need_privacy_mode_no_physical_displays_tip", ""),
|
||||||
|
("Follow remote cursor", ""),
|
||||||
|
("Follow remote window focus", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -239,6 +239,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Empty", "Bosh"),
|
("Empty", "Bosh"),
|
||||||
("Invalid folder name", "Emri i dosjes i pavlefshëm"),
|
("Invalid folder name", "Emri i dosjes i pavlefshëm"),
|
||||||
("Socks5 Proxy", "Socks5 Proxy"),
|
("Socks5 Proxy", "Socks5 Proxy"),
|
||||||
|
("Socks5/Http(s) Proxy", "Socks5/Http(s) Proxy"),
|
||||||
("Discovered", "I pambuluar"),
|
("Discovered", "I pambuluar"),
|
||||||
("install_daemon_tip", "Për të nisur në boot, duhet të instaloni shërbimin e sistemit"),
|
("install_daemon_tip", "Për të nisur në boot, duhet të instaloni shërbimin e sistemit"),
|
||||||
("Remote ID", "ID në distancë"),
|
("Remote ID", "ID në distancë"),
|
||||||
@ -602,5 +603,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("ab_web_console_tip", ""),
|
("ab_web_console_tip", ""),
|
||||||
("allow-only-conn-window-open-tip", ""),
|
("allow-only-conn-window-open-tip", ""),
|
||||||
("no_need_privacy_mode_no_physical_displays_tip", ""),
|
("no_need_privacy_mode_no_physical_displays_tip", ""),
|
||||||
|
("Follow remote cursor", ""),
|
||||||
|
("Follow remote window focus", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -239,6 +239,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Empty", "Prazno"),
|
("Empty", "Prazno"),
|
||||||
("Invalid folder name", "Pogrešno ime direktorijuma"),
|
("Invalid folder name", "Pogrešno ime direktorijuma"),
|
||||||
("Socks5 Proxy", "Socks5 proksi"),
|
("Socks5 Proxy", "Socks5 proksi"),
|
||||||
|
("Socks5/Http(s) Proxy", "Socks5/Http(s) proksi"),
|
||||||
("Discovered", "Otkriveno"),
|
("Discovered", "Otkriveno"),
|
||||||
("install_daemon_tip", "Za pokretanje pri startu sistema, treba da instalirate sistemski servis."),
|
("install_daemon_tip", "Za pokretanje pri startu sistema, treba da instalirate sistemski servis."),
|
||||||
("Remote ID", "Udaljeni ID"),
|
("Remote ID", "Udaljeni ID"),
|
||||||
@ -602,5 +603,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("ab_web_console_tip", ""),
|
("ab_web_console_tip", ""),
|
||||||
("allow-only-conn-window-open-tip", ""),
|
("allow-only-conn-window-open-tip", ""),
|
||||||
("no_need_privacy_mode_no_physical_displays_tip", ""),
|
("no_need_privacy_mode_no_physical_displays_tip", ""),
|
||||||
|
("Follow remote cursor", ""),
|
||||||
|
("Follow remote window focus", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -239,6 +239,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Empty", "Tom"),
|
("Empty", "Tom"),
|
||||||
("Invalid folder name", "Ogiltigt mappnamn"),
|
("Invalid folder name", "Ogiltigt mappnamn"),
|
||||||
("Socks5 Proxy", "Socks5 Proxy"),
|
("Socks5 Proxy", "Socks5 Proxy"),
|
||||||
|
("Socks5/Http(s) Proxy", "Socks5/Http(s) Proxy"),
|
||||||
("Discovered", "Upptäckt"),
|
("Discovered", "Upptäckt"),
|
||||||
("install_daemon_tip", "För att starta efter boot måste du installera systemtjänsten."),
|
("install_daemon_tip", "För att starta efter boot måste du installera systemtjänsten."),
|
||||||
("Remote ID", "Fjärr ID"),
|
("Remote ID", "Fjärr ID"),
|
||||||
@ -602,5 +603,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("ab_web_console_tip", ""),
|
("ab_web_console_tip", ""),
|
||||||
("allow-only-conn-window-open-tip", ""),
|
("allow-only-conn-window-open-tip", ""),
|
||||||
("no_need_privacy_mode_no_physical_displays_tip", ""),
|
("no_need_privacy_mode_no_physical_displays_tip", ""),
|
||||||
|
("Follow remote cursor", ""),
|
||||||
|
("Follow remote window focus", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -239,6 +239,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Empty", ""),
|
("Empty", ""),
|
||||||
("Invalid folder name", ""),
|
("Invalid folder name", ""),
|
||||||
("Socks5 Proxy", ""),
|
("Socks5 Proxy", ""),
|
||||||
|
("Socks5/Http(s) Proxy", ""),
|
||||||
("Discovered", ""),
|
("Discovered", ""),
|
||||||
("install_daemon_tip", ""),
|
("install_daemon_tip", ""),
|
||||||
("Remote ID", ""),
|
("Remote ID", ""),
|
||||||
@ -602,5 +603,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("ab_web_console_tip", ""),
|
("ab_web_console_tip", ""),
|
||||||
("allow-only-conn-window-open-tip", ""),
|
("allow-only-conn-window-open-tip", ""),
|
||||||
("no_need_privacy_mode_no_physical_displays_tip", ""),
|
("no_need_privacy_mode_no_physical_displays_tip", ""),
|
||||||
|
("Follow remote cursor", ""),
|
||||||
|
("Follow remote window focus", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -239,6 +239,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Empty", "ว่างเปล่า"),
|
("Empty", "ว่างเปล่า"),
|
||||||
("Invalid folder name", "ชื่อโฟลเดอร์ไม่ถูกต้อง"),
|
("Invalid folder name", "ชื่อโฟลเดอร์ไม่ถูกต้อง"),
|
||||||
("Socks5 Proxy", "พรอกซี Socks5"),
|
("Socks5 Proxy", "พรอกซี Socks5"),
|
||||||
|
("Socks5/Http(s) Proxy", "พรอกซี Socks5/Http(s)"),
|
||||||
("Discovered", "ค้นพบ"),
|
("Discovered", "ค้นพบ"),
|
||||||
("install_daemon_tip", "หากต้องการใช้งานขณะระบบเริ่มต้น คุณจำเป็นจะต้องติดตั้งเซอร์วิส"),
|
("install_daemon_tip", "หากต้องการใช้งานขณะระบบเริ่มต้น คุณจำเป็นจะต้องติดตั้งเซอร์วิส"),
|
||||||
("Remote ID", "ID ปลายทาง"),
|
("Remote ID", "ID ปลายทาง"),
|
||||||
@ -602,5 +603,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("ab_web_console_tip", ""),
|
("ab_web_console_tip", ""),
|
||||||
("allow-only-conn-window-open-tip", ""),
|
("allow-only-conn-window-open-tip", ""),
|
||||||
("no_need_privacy_mode_no_physical_displays_tip", ""),
|
("no_need_privacy_mode_no_physical_displays_tip", ""),
|
||||||
|
("Follow remote cursor", ""),
|
||||||
|
("Follow remote window focus", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -239,6 +239,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Empty", "Boş"),
|
("Empty", "Boş"),
|
||||||
("Invalid folder name", "Geçersiz klasör adı"),
|
("Invalid folder name", "Geçersiz klasör adı"),
|
||||||
("Socks5 Proxy", "Socks5 Proxy"),
|
("Socks5 Proxy", "Socks5 Proxy"),
|
||||||
|
("Socks5/Http(s) Proxy", "Socks5/Http(s) Proxy"),
|
||||||
("Discovered", "Keşfedilenler"),
|
("Discovered", "Keşfedilenler"),
|
||||||
("install_daemon_tip", "Başlangıçta başlamak için sistem hizmetini yüklemeniz gerekir."),
|
("install_daemon_tip", "Başlangıçta başlamak için sistem hizmetini yüklemeniz gerekir."),
|
||||||
("Remote ID", "Uzak ID"),
|
("Remote ID", "Uzak ID"),
|
||||||
@ -602,5 +603,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("ab_web_console_tip", ""),
|
("ab_web_console_tip", ""),
|
||||||
("allow-only-conn-window-open-tip", ""),
|
("allow-only-conn-window-open-tip", ""),
|
||||||
("no_need_privacy_mode_no_physical_displays_tip", ""),
|
("no_need_privacy_mode_no_physical_displays_tip", ""),
|
||||||
|
("Follow remote cursor", ""),
|
||||||
|
("Follow remote window focus", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -239,6 +239,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Empty", "空空如也"),
|
("Empty", "空空如也"),
|
||||||
("Invalid folder name", "資料夾名稱無效"),
|
("Invalid folder name", "資料夾名稱無效"),
|
||||||
("Socks5 Proxy", "Socks5 代理伺服器"),
|
("Socks5 Proxy", "Socks5 代理伺服器"),
|
||||||
|
("Socks5/Http(s) Proxy", "Socks5/Http(s) 代理伺服器"),
|
||||||
("Discovered", "已探索"),
|
("Discovered", "已探索"),
|
||||||
("install_daemon_tip", "若要在開機時啟動,您需要安裝系統服務。"),
|
("install_daemon_tip", "若要在開機時啟動,您需要安裝系統服務。"),
|
||||||
("Remote ID", "遠端 ID"),
|
("Remote ID", "遠端 ID"),
|
||||||
@ -602,5 +603,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("ab_web_console_tip", "打開 Web 控制台以進行更多操作"),
|
("ab_web_console_tip", "打開 Web 控制台以進行更多操作"),
|
||||||
("allow-only-conn-window-open-tip", "只在 RustDesk 視窗開啟時允許連接"),
|
("allow-only-conn-window-open-tip", "只在 RustDesk 視窗開啟時允許連接"),
|
||||||
("no_need_privacy_mode_no_physical_displays_tip", ""),
|
("no_need_privacy_mode_no_physical_displays_tip", ""),
|
||||||
|
("Follow remote cursor", ""),
|
||||||
|
("Follow remote window focus", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -239,6 +239,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Empty", "Пусто"),
|
("Empty", "Пусто"),
|
||||||
("Invalid folder name", "Неприпустима назва теки"),
|
("Invalid folder name", "Неприпустима назва теки"),
|
||||||
("Socks5 Proxy", "Проксі-сервер Socks5"),
|
("Socks5 Proxy", "Проксі-сервер Socks5"),
|
||||||
|
("Socks5/Http(s) Proxy", "Проксі-сервер Socks5/Http(s)"),
|
||||||
("Discovered", "Знайдено"),
|
("Discovered", "Знайдено"),
|
||||||
("install_daemon_tip", "Для запуску під час завантаження, вам необхідно встановити системну службу"),
|
("install_daemon_tip", "Для запуску під час завантаження, вам необхідно встановити системну службу"),
|
||||||
("Remote ID", "Віддалений ідентифікатор"),
|
("Remote ID", "Віддалений ідентифікатор"),
|
||||||
@ -602,5 +603,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("ab_web_console_tip", "Детальніше про веб-консоль"),
|
("ab_web_console_tip", "Детальніше про веб-консоль"),
|
||||||
("allow-only-conn-window-open-tip", ""),
|
("allow-only-conn-window-open-tip", ""),
|
||||||
("no_need_privacy_mode_no_physical_displays_tip", ""),
|
("no_need_privacy_mode_no_physical_displays_tip", ""),
|
||||||
|
("Follow remote cursor", ""),
|
||||||
|
("Follow remote window focus", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -239,6 +239,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Empty", "Trống"),
|
("Empty", "Trống"),
|
||||||
("Invalid folder name", "Tên thư mục không hợp lệ"),
|
("Invalid folder name", "Tên thư mục không hợp lệ"),
|
||||||
("Socks5 Proxy", "Proxy Socks5"),
|
("Socks5 Proxy", "Proxy Socks5"),
|
||||||
|
("Socks5/Http(s) Proxy", "Proxy Socks5/Http(s)"),
|
||||||
("Discovered", "Đuợc phát hiện"),
|
("Discovered", "Đuợc phát hiện"),
|
||||||
("install_daemon_tip", "Để chạy lúc khởi động máy, bạn cần phải cài dịch vụ hệ thống."),
|
("install_daemon_tip", "Để chạy lúc khởi động máy, bạn cần phải cài dịch vụ hệ thống."),
|
||||||
("Remote ID", "ID từ xa"),
|
("Remote ID", "ID từ xa"),
|
||||||
@ -602,5 +603,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("ab_web_console_tip", ""),
|
("ab_web_console_tip", ""),
|
||||||
("allow-only-conn-window-open-tip", ""),
|
("allow-only-conn-window-open-tip", ""),
|
||||||
("no_need_privacy_mode_no_physical_displays_tip", ""),
|
("no_need_privacy_mode_no_physical_displays_tip", ""),
|
||||||
|
("Follow remote cursor", ""),
|
||||||
|
("Follow remote window focus", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,7 +3,9 @@ mod keyboard;
|
|||||||
/// cbindgen:ignore
|
/// cbindgen:ignore
|
||||||
pub mod platform;
|
pub mod platform;
|
||||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||||
pub use platform::{get_cursor, get_cursor_data, get_cursor_pos, start_os_service};
|
pub use platform::{
|
||||||
|
get_cursor, get_cursor_data, get_cursor_pos, get_focused_display, start_os_service,
|
||||||
|
};
|
||||||
#[cfg(not(any(target_os = "ios")))]
|
#[cfg(not(any(target_os = "ios")))]
|
||||||
/// cbindgen:ignore
|
/// cbindgen:ignore
|
||||||
mod server;
|
mod server;
|
||||||
@ -36,15 +38,15 @@ pub mod flutter;
|
|||||||
#[cfg(any(target_os = "android", target_os = "ios", feature = "flutter"))]
|
#[cfg(any(target_os = "android", target_os = "ios", feature = "flutter"))]
|
||||||
pub mod flutter_ffi;
|
pub mod flutter_ffi;
|
||||||
use common::*;
|
use common::*;
|
||||||
|
mod auth_2fa;
|
||||||
#[cfg(feature = "cli")]
|
#[cfg(feature = "cli")]
|
||||||
pub mod cli;
|
pub mod cli;
|
||||||
#[cfg(not(any(target_os = "android", target_os = "ios", feature = "cli")))]
|
#[cfg(not(any(target_os = "android", target_os = "ios", feature = "cli")))]
|
||||||
pub mod core_main;
|
pub mod core_main;
|
||||||
mod lang;
|
|
||||||
mod custom_server;
|
mod custom_server;
|
||||||
|
mod lang;
|
||||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||||
mod port_forward;
|
mod port_forward;
|
||||||
mod auth_2fa;
|
|
||||||
|
|
||||||
#[cfg(all(feature = "flutter", feature = "plugin_framework"))]
|
#[cfg(all(feature = "flutter", feature = "plugin_framework"))]
|
||||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user