mirror of
https://github.com/rustdesk/rustdesk.git
synced 2026-03-18 02:31:00 +03:00
Compare commits
135 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e191d11f74 | ||
|
|
fc396d2166 | ||
|
|
2575e14811 | ||
|
|
0b9a6a280e | ||
|
|
343f12b380 | ||
|
|
ce1e4863cb | ||
|
|
f631c1c28d | ||
|
|
8b9a7a3506 | ||
|
|
055b351164 | ||
|
|
2e89a33210 | ||
|
|
965cc6af26 | ||
|
|
16e191f913 | ||
|
|
1d1e79c802 | ||
|
|
9ffe516f54 | ||
|
|
ac20d2fb56 | ||
|
|
2217152216 | ||
|
|
ee288280b3 | ||
|
|
cccdb2f289 | ||
|
|
1ddab27c0e | ||
|
|
451b6dc651 | ||
|
|
86b327ee41 | ||
|
|
6e305d4865 | ||
|
|
77af6c4ce1 | ||
|
|
fa49c72835 | ||
|
|
c150143d86 | ||
|
|
023d46b48c | ||
|
|
356adbcd8c | ||
|
|
33b47dd6e3 | ||
|
|
a548e9c94d | ||
|
|
cefda0dec1 | ||
|
|
8f545491a2 | ||
|
|
263bbfc66f | ||
|
|
a039741e5a | ||
|
|
2a0e8c109b | ||
|
|
9614bf266a | ||
|
|
aa63ebc7e5 | ||
|
|
fbba8f0b34 | ||
|
|
a27fa43081 | ||
|
|
7a5941de98 | ||
|
|
db3ca6a373 | ||
|
|
ce5f0d513f | ||
|
|
05b0f95b79 | ||
|
|
8b24b195a2 | ||
|
|
5fc8e8c428 | ||
|
|
25f917a7b4 | ||
|
|
55005f8129 | ||
|
|
f08cb0412d | ||
|
|
fc2e27bcf0 | ||
|
|
7aa4592669 | ||
|
|
d656ae2956 | ||
|
|
e4f00361f6 | ||
|
|
1b49d49df2 | ||
|
|
80f759c1ed | ||
|
|
da80f3352a | ||
|
|
ec3ba5be8e | ||
|
|
d04756ad70 | ||
|
|
0eba939cd6 | ||
|
|
1f02bc9d3e | ||
|
|
5fa8c25e65 | ||
|
|
c44803f5b0 | ||
|
|
4b066b1fba | ||
|
|
dd004f1a2d | ||
|
|
222dbf12cd | ||
|
|
b5d54debce | ||
|
|
08cdf7134d | ||
|
|
be5037bd03 | ||
|
|
f9915df926 | ||
|
|
f96c759cf5 | ||
|
|
8f329ebc1a | ||
|
|
4a3c11e711 | ||
|
|
0dbd3094ec | ||
|
|
40999c3211 | ||
|
|
7c2d62237f | ||
|
|
ef90ab2bd4 | ||
|
|
4f3b821883 | ||
|
|
98b00cdb3d | ||
|
|
8e4127b6a0 | ||
|
|
b1f54acf90 | ||
|
|
39a430f96f | ||
|
|
a9f2e14091 | ||
|
|
77baba3122 | ||
|
|
1c62a28ef3 | ||
|
|
9ed2499666 | ||
|
|
06bc554216 | ||
|
|
090f5b65ac | ||
|
|
49dabd3533 | ||
|
|
7289dbc80f | ||
|
|
72f5184ee0 | ||
|
|
e9c5e0d26b | ||
|
|
03999d900e | ||
|
|
b24551da7b | ||
|
|
bc461fe99b | ||
|
|
25e438a663 | ||
|
|
1f5aeda41d | ||
|
|
9114743577 | ||
|
|
7830a9e9f3 | ||
|
|
5fa8485130 | ||
|
|
ed9cb37283 | ||
|
|
e4b270a581 | ||
|
|
9dd9c45afc | ||
|
|
e163b75407 | ||
|
|
10ff3e6937 | ||
|
|
acae6d6558 | ||
|
|
d025ca1d81 | ||
|
|
e5aa31eb4c | ||
|
|
771cc565ab | ||
|
|
db3bdb16a1 | ||
|
|
c06e1d74b4 | ||
|
|
b544a2889b | ||
|
|
9c45636875 | ||
|
|
827b5f6a4c | ||
|
|
b0791ba183 | ||
|
|
b24b381575 | ||
|
|
0751005073 | ||
|
|
fe06cf77da | ||
|
|
d57cf204c8 | ||
|
|
63e22b7685 | ||
|
|
a02d2bb4ac | ||
|
|
0f7d78c263 | ||
|
|
0e321bd845 | ||
|
|
875b738222 | ||
|
|
b39e851262 | ||
|
|
ec466d459f | ||
|
|
d4a712bb32 | ||
|
|
1c17fddf51 | ||
|
|
3c838e7a92 | ||
|
|
8f44787ba3 | ||
|
|
12e15b5a37 | ||
|
|
588103c6dc | ||
|
|
2ce9b108ed | ||
|
|
93e3107881 | ||
|
|
468bdd6cc6 | ||
|
|
d5c5825ffd | ||
|
|
fe4094777f | ||
|
|
f13ef48cec |
11
.github/dependabot.yml
vendored
Normal file
11
.github/dependabot.yml
vendored
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
version: 2
|
||||||
|
updates:
|
||||||
|
- package-ecosystem: "gitsubmodule"
|
||||||
|
directory: "/"
|
||||||
|
target-branch: "master"
|
||||||
|
schedule:
|
||||||
|
interval: "daily"
|
||||||
|
commit-message:
|
||||||
|
prefix: "Git submodule"
|
||||||
|
labels:
|
||||||
|
- "dependencies"
|
||||||
4
.github/workflows/bridge.yml
vendored
4
.github/workflows/bridge.yml
vendored
@@ -6,6 +6,7 @@ on:
|
|||||||
workflow_call:
|
workflow_call:
|
||||||
|
|
||||||
env:
|
env:
|
||||||
|
CARGO_EXPAND_VERSION: "1.0.95"
|
||||||
FLUTTER_VERSION: "3.22.3"
|
FLUTTER_VERSION: "3.22.3"
|
||||||
FLUTTER_RUST_BRIDGE_VERSION: "1.80.1"
|
FLUTTER_RUST_BRIDGE_VERSION: "1.80.1"
|
||||||
RUST_VERSION: "1.75" # https://github.com/rustdesk/rustdesk/discussions/7503
|
RUST_VERSION: "1.75" # https://github.com/rustdesk/rustdesk/discussions/7503
|
||||||
@@ -25,6 +26,8 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Checkout source code
|
- name: Checkout source code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
submodules: recursive
|
||||||
|
|
||||||
- name: Install prerequisites
|
- name: Install prerequisites
|
||||||
run: |
|
run: |
|
||||||
@@ -73,6 +76,7 @@ jobs:
|
|||||||
- name: Install flutter rust bridge deps
|
- name: Install flutter rust bridge deps
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
|
cargo install cargo-expand --version ${{ env.CARGO_EXPAND_VERSION }} --locked
|
||||||
cargo install flutter_rust_bridge_codegen --version ${{ env.FLUTTER_RUST_BRIDGE_VERSION }} --features "uuid" --locked
|
cargo install flutter_rust_bridge_codegen --version ${{ env.FLUTTER_RUST_BRIDGE_VERSION }} --features "uuid" --locked
|
||||||
pushd flutter && sed -i -e 's/extended_text: 14.0.0/extended_text: 13.0.0/g' pubspec.yaml && flutter pub get && popd
|
pushd flutter && sed -i -e 's/extended_text: 14.0.0/extended_text: 13.0.0/g' pubspec.yaml && flutter pub get && popd
|
||||||
|
|
||||||
|
|||||||
8
.github/workflows/ci.yml
vendored
8
.github/workflows/ci.yml
vendored
@@ -4,9 +4,9 @@ env:
|
|||||||
# MIN_SUPPORTED_RUST_VERSION: "1.46.0"
|
# MIN_SUPPORTED_RUST_VERSION: "1.46.0"
|
||||||
# CICD_INTERMEDIATES_DIR: "_cicd-intermediates"
|
# CICD_INTERMEDIATES_DIR: "_cicd-intermediates"
|
||||||
VCPKG_BINARY_SOURCES: "clear;x-gha,readwrite"
|
VCPKG_BINARY_SOURCES: "clear;x-gha,readwrite"
|
||||||
# vcpkg version: 2024.06.15
|
# vcpkg version: 2024.11.16
|
||||||
# for multiarch gcc compatibility
|
# for multiarch gcc compatibility
|
||||||
VCPKG_COMMIT_ID: "f7423ee180c4b7f40d43402c2feb3859161ef625"
|
VCPKG_COMMIT_ID: "b2cb0da531c2f1f740045bfe7c4dac59f0b2b69c"
|
||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
@@ -45,6 +45,8 @@ jobs:
|
|||||||
# steps:
|
# steps:
|
||||||
# - name: Checkout source code
|
# - name: Checkout source code
|
||||||
# uses: actions/checkout@v3
|
# uses: actions/checkout@v3
|
||||||
|
# with:
|
||||||
|
# submodules: recursive
|
||||||
|
|
||||||
# - name: Install rust toolchain (v${{ env.MIN_SUPPORTED_RUST_VERSION }})
|
# - name: Install rust toolchain (v${{ env.MIN_SUPPORTED_RUST_VERSION }})
|
||||||
# uses: actions-rs/toolchain@v1
|
# uses: actions-rs/toolchain@v1
|
||||||
@@ -92,6 +94,8 @@ jobs:
|
|||||||
|
|
||||||
- name: Checkout source code
|
- name: Checkout source code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
submodules: recursive
|
||||||
|
|
||||||
- name: Install prerequisites
|
- name: Install prerequisites
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|||||||
117
.github/workflows/flutter-build.yml
vendored
117
.github/workflows/flutter-build.yml
vendored
@@ -31,17 +31,13 @@ env:
|
|||||||
FLUTTER_ELINUX_VERSION: "3.16.9"
|
FLUTTER_ELINUX_VERSION: "3.16.9"
|
||||||
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: 2024.07.12
|
# vcpkg version: 2025.01.13
|
||||||
VCPKG_COMMIT_ID: "1de2026f28ead93ff1773e6e680387643e914ea1"
|
VCPKG_COMMIT_ID: "6f29f12e82a8293156836ad81cc9bf5af41fe836"
|
||||||
VERSION: "1.3.4"
|
VERSION: "1.3.8"
|
||||||
NDK_VERSION: "r27c"
|
NDK_VERSION: "r27c"
|
||||||
#signing keys env variable checks
|
#signing keys env variable checks
|
||||||
ANDROID_SIGNING_KEY: "${{ secrets.ANDROID_SIGNING_KEY }}"
|
ANDROID_SIGNING_KEY: "${{ secrets.ANDROID_SIGNING_KEY }}"
|
||||||
MACOS_P12_BASE64: "${{ secrets.MACOS_P12_BASE64 }}"
|
MACOS_P12_BASE64: "${{ secrets.MACOS_P12_BASE64 }}"
|
||||||
# To make a custom build with your own servers set the below secret values
|
|
||||||
RS_PUB_KEY: "${{ secrets.RS_PUB_KEY }}"
|
|
||||||
RENDEZVOUS_SERVER: "${{ secrets.RENDEZVOUS_SERVER }}"
|
|
||||||
API_SERVER: "${{ secrets.API_SERVER }}"
|
|
||||||
UPLOAD_ARTIFACT: "${{ inputs.upload-artifact }}"
|
UPLOAD_ARTIFACT: "${{ inputs.upload-artifact }}"
|
||||||
SIGN_BASE_URL: "${{ secrets.SIGN_BASE_URL }}"
|
SIGN_BASE_URL: "${{ secrets.SIGN_BASE_URL }}"
|
||||||
|
|
||||||
@@ -87,6 +83,8 @@ jobs:
|
|||||||
|
|
||||||
- name: Checkout source code
|
- name: Checkout source code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
submodules: recursive
|
||||||
|
|
||||||
- name: Restore bridge files
|
- name: Restore bridge files
|
||||||
uses: actions/download-artifact@master
|
uses: actions/download-artifact@master
|
||||||
@@ -276,6 +274,8 @@ jobs:
|
|||||||
|
|
||||||
- name: Checkout source code
|
- name: Checkout source code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
submodules: recursive
|
||||||
|
|
||||||
- name: Install LLVM and Clang
|
- name: Install LLVM and Clang
|
||||||
uses: rustdesk-org/install-llvm-action-32bit@master
|
uses: rustdesk-org/install-llvm-action-32bit@master
|
||||||
@@ -404,6 +404,8 @@ jobs:
|
|||||||
|
|
||||||
- name: Checkout source code
|
- name: Checkout source code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
submodules: recursive
|
||||||
|
|
||||||
- name: Restore bridge files
|
- name: Restore bridge files
|
||||||
uses: actions/download-artifact@master
|
uses: actions/download-artifact@master
|
||||||
@@ -413,7 +415,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Build rustdesk
|
- name: Build rustdesk
|
||||||
run: |
|
run: |
|
||||||
./build.py --flutter --hwcodec
|
./build.py --flutter --hwcodec --unix-file-copy-paste
|
||||||
|
|
||||||
- name: create unsigned dmg
|
- name: create unsigned dmg
|
||||||
if: env.UPLOAD_ARTIFACT == 'true'
|
if: env.UPLOAD_ARTIFACT == 'true'
|
||||||
@@ -489,6 +491,9 @@ jobs:
|
|||||||
brew install nasm yasm
|
brew install nasm yasm
|
||||||
- name: Checkout source code
|
- name: Checkout source code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
submodules: recursive
|
||||||
|
|
||||||
- name: Install flutter
|
- name: Install flutter
|
||||||
uses: subosito/flutter-action@v2
|
uses: subosito/flutter-action@v2
|
||||||
with:
|
with:
|
||||||
@@ -546,6 +551,12 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
rustup target add ${{ matrix.job.target }}
|
rustup target add ${{ matrix.job.target }}
|
||||||
cargo build --features flutter,hwcodec --release --target aarch64-apple-ios --lib
|
cargo build --features flutter,hwcodec --release --target aarch64-apple-ios --lib
|
||||||
|
|
||||||
|
- name: Upload liblibrustdesk.a Artifacts
|
||||||
|
uses: actions/upload-artifact@master
|
||||||
|
with:
|
||||||
|
name: liblibrustdesk.a
|
||||||
|
path: target/aarch64-apple-ios/release/liblibrustdesk.a
|
||||||
|
|
||||||
- name: Build rustdesk
|
- name: Build rustdesk
|
||||||
shell: bash
|
shell: bash
|
||||||
@@ -588,6 +599,8 @@ jobs:
|
|||||||
|
|
||||||
- name: Checkout source code
|
- name: Checkout source code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
submodules: recursive
|
||||||
|
|
||||||
# $VCPKG_ROOT/vcpkg install --triplet arm64-ios --x-install-root="$VCPKG_ROOT/installed"
|
# $VCPKG_ROOT/vcpkg install --triplet arm64-ios --x-install-root="$VCPKG_ROOT/installed"
|
||||||
|
|
||||||
@@ -660,6 +673,8 @@ jobs:
|
|||||||
|
|
||||||
- name: Checkout source code
|
- name: Checkout source code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
submodules: recursive
|
||||||
|
|
||||||
- name: Import the codesign cert
|
- name: Import the codesign cert
|
||||||
if: env.MACOS_P12_BASE64 != null
|
if: env.MACOS_P12_BASE64 != null
|
||||||
@@ -719,7 +734,7 @@ jobs:
|
|||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
cd "$(dirname "$(which flutter)")"
|
cd "$(dirname "$(which flutter)")"
|
||||||
# https://github.com/flutter/flutter/issues/1.3.43
|
# https://github.com/flutter/flutter/issues/133533
|
||||||
sed -i -e 's/_setFramesEnabledState(false);/\/\/_setFramesEnabledState(false);/g' ../packages/flutter/lib/src/scheduler/binding.dart
|
sed -i -e 's/_setFramesEnabledState(false);/\/\/_setFramesEnabledState(false);/g' ../packages/flutter/lib/src/scheduler/binding.dart
|
||||||
grep -n '_setFramesEnabledState(false);' ../packages/flutter/lib/src/scheduler/binding.dart
|
grep -n '_setFramesEnabledState(false);' ../packages/flutter/lib/src/scheduler/binding.dart
|
||||||
|
|
||||||
@@ -781,7 +796,7 @@ jobs:
|
|||||||
sed -i -e "s/osx_minimum_system_version = \"[0-9]*.[0-9]*\"/osx_minimum_system_version = \"${MIN_MACOS_VERSION}\"/" Cargo.toml
|
sed -i -e "s/osx_minimum_system_version = \"[0-9]*.[0-9]*\"/osx_minimum_system_version = \"${MIN_MACOS_VERSION}\"/" Cargo.toml
|
||||||
sed -i -e "s/MACOSX_DEPLOYMENT_TARGET = [0-9]*.[0-9]*;/MACOSX_DEPLOYMENT_TARGET = ${MIN_MACOS_VERSION};/" flutter/macos/Runner.xcodeproj/project.pbxproj
|
sed -i -e "s/MACOSX_DEPLOYMENT_TARGET = [0-9]*.[0-9]*;/MACOSX_DEPLOYMENT_TARGET = ${MIN_MACOS_VERSION};/" flutter/macos/Runner.xcodeproj/project.pbxproj
|
||||||
fi
|
fi
|
||||||
./build.py --flutter --hwcodec ${{ matrix.job.extra-build-args }}
|
./build.py --flutter --hwcodec --unix-file-copy-paste ${{ matrix.job.extra-build-args }}
|
||||||
|
|
||||||
- name: create unsigned dmg
|
- name: create unsigned dmg
|
||||||
if: env.UPLOAD_ARTIFACT == 'true'
|
if: env.UPLOAD_ARTIFACT == 'true'
|
||||||
@@ -952,6 +967,9 @@ jobs:
|
|||||||
|
|
||||||
- name: Checkout source code
|
- name: Checkout source code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
submodules: recursive
|
||||||
|
|
||||||
- name: Install flutter
|
- name: Install flutter
|
||||||
uses: subosito/flutter-action@v2
|
uses: subosito/flutter-action@v2
|
||||||
with:
|
with:
|
||||||
@@ -1021,16 +1039,6 @@ jobs:
|
|||||||
prefix-key: rustdesk-lib-cache-android # TODO: drop '-android' part after caches are invalidated
|
prefix-key: rustdesk-lib-cache-android # TODO: drop '-android' part after caches are invalidated
|
||||||
key: ${{ matrix.job.target }}
|
key: ${{ matrix.job.target }}
|
||||||
|
|
||||||
- name: fix android for flutter 3.13
|
|
||||||
if: ${{ env.ANDROID_FLUTTER_VERSION == '3.13.9' }}
|
|
||||||
run: |
|
|
||||||
cd flutter
|
|
||||||
sed -i 's/uni_links_desktop/#uni_links_desktop/g' pubspec.yaml
|
|
||||||
sed -i 's/extended_text: .*/extended_text: 11.1.0/' pubspec.yaml
|
|
||||||
flutter pub get
|
|
||||||
cd lib
|
|
||||||
find . | grep dart | xargs sed -i 's/textScaler: TextScaler.linear(\(.*\)),/textScaleFactor: \1,/g'
|
|
||||||
|
|
||||||
- name: Build rustdesk lib
|
- name: Build rustdesk lib
|
||||||
env:
|
env:
|
||||||
ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }}
|
ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }}
|
||||||
@@ -1228,6 +1236,9 @@ jobs:
|
|||||||
|
|
||||||
- name: Checkout source code
|
- name: Checkout source code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
submodules: recursive
|
||||||
|
|
||||||
- name: Install flutter
|
- name: Install flutter
|
||||||
uses: subosito/flutter-action@v2
|
uses: subosito/flutter-action@v2
|
||||||
with:
|
with:
|
||||||
@@ -1270,16 +1281,6 @@ jobs:
|
|||||||
name: librustdesk.so.i686-linux-android
|
name: librustdesk.so.i686-linux-android
|
||||||
path: ./flutter/android/app/src/main/jniLibs/x86
|
path: ./flutter/android/app/src/main/jniLibs/x86
|
||||||
|
|
||||||
- name: fix android for flutter 3.13
|
|
||||||
if: ${{ env.ANDROID_FLUTTER_VERSION == '3.13.9' }}
|
|
||||||
run: |
|
|
||||||
cd flutter
|
|
||||||
sed -i 's/uni_links_desktop/#uni_links_desktop/g' pubspec.yaml
|
|
||||||
sed -i 's/extended_text: .*/extended_text: 11.1.0/' pubspec.yaml
|
|
||||||
flutter pub get
|
|
||||||
cd lib
|
|
||||||
find . | grep dart | xargs sed -i 's/textScaler: TextScaler.linear(\(.*\)),/textScaleFactor: \1,/g'
|
|
||||||
|
|
||||||
- name: Build rustdesk
|
- name: Build rustdesk
|
||||||
shell: bash
|
shell: bash
|
||||||
env:
|
env:
|
||||||
@@ -1373,7 +1374,7 @@ jobs:
|
|||||||
arch: aarch64,
|
arch: aarch64,
|
||||||
target: aarch64-unknown-linux-gnu,
|
target: aarch64-unknown-linux-gnu,
|
||||||
distro: ubuntu18.04,
|
distro: ubuntu18.04,
|
||||||
on: [self-hosted, Linux, ARM64],
|
on: ubuntu-22.04-arm,
|
||||||
deb_arch: arm64,
|
deb_arch: arm64,
|
||||||
vcpkg-triplet: arm64-linux,
|
vcpkg-triplet: arm64-linux,
|
||||||
}
|
}
|
||||||
@@ -1386,16 +1387,20 @@ jobs:
|
|||||||
core.exportVariable('ACTIONS_RUNTIME_TOKEN', process.env.ACTIONS_RUNTIME_TOKEN || '');
|
core.exportVariable('ACTIONS_RUNTIME_TOKEN', process.env.ACTIONS_RUNTIME_TOKEN || '');
|
||||||
|
|
||||||
- name: Maximize build space
|
- name: Maximize build space
|
||||||
if: ${{ matrix.job.arch == 'x86_64' }}
|
|
||||||
run: |
|
run: |
|
||||||
sudo rm -rf /opt/ghc
|
sudo rm -rf /opt/ghc
|
||||||
sudo rm -rf /usr/local/lib/android
|
sudo rm -rf /usr/local/lib/android
|
||||||
sudo rm -rf /usr/share/dotnet
|
sudo rm -rf /usr/share/dotnet
|
||||||
sudo apt-get update -y
|
sudo apt-get update -y
|
||||||
sudo apt-get install -y nasm qemu-user-static
|
sudo apt-get install -y nasm
|
||||||
|
if [[ "${{ matrix.job.arch }}" == "x86_64" ]]; then
|
||||||
|
sudo apt-get install -y qemu-user-static
|
||||||
|
fi
|
||||||
|
|
||||||
- name: Checkout source code
|
- name: Checkout source code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
submodules: recursive
|
||||||
|
|
||||||
- name: Set Swap Space
|
- name: Set Swap Space
|
||||||
if: ${{ matrix.job.arch == 'x86_64' }}
|
if: ${{ matrix.job.arch == 'x86_64' }}
|
||||||
@@ -1549,7 +1554,7 @@ jobs:
|
|||||||
export JOBS=""
|
export JOBS=""
|
||||||
fi
|
fi
|
||||||
echo $JOBS
|
echo $JOBS
|
||||||
cargo build --lib $JOBS --features hwcodec,flutter --release
|
cargo build --lib $JOBS --features hwcodec,flutter,unix-file-copy-paste --release
|
||||||
rm -rf target/release/deps target/release/build
|
rm -rf target/release/deps target/release/build
|
||||||
rm -rf ~/.cargo
|
rm -rf ~/.cargo
|
||||||
|
|
||||||
@@ -1686,7 +1691,6 @@ jobs:
|
|||||||
|
|
||||||
build-rustdesk-linux-sciter:
|
build-rustdesk-linux-sciter:
|
||||||
if: ${{ inputs.upload-artifact }}
|
if: ${{ inputs.upload-artifact }}
|
||||||
needs: build-rustdesk-linux # not for dep, just make it run later for parallelism
|
|
||||||
runs-on: ${{ matrix.job.on }}
|
runs-on: ${{ matrix.job.on }}
|
||||||
name: build-rustdesk-linux-sciter ${{ matrix.job.target }}
|
name: build-rustdesk-linux-sciter ${{ matrix.job.target }}
|
||||||
strategy:
|
strategy:
|
||||||
@@ -1702,17 +1706,17 @@ jobs:
|
|||||||
deb_arch: amd64,
|
deb_arch: amd64,
|
||||||
sciter_arch: x64,
|
sciter_arch: x64,
|
||||||
vcpkg-triplet: x64-linux,
|
vcpkg-triplet: x64-linux,
|
||||||
extra_features: ",hwcodec",
|
extra_features: ",hwcodec,unix-file-copy-paste",
|
||||||
}
|
}
|
||||||
- {
|
- {
|
||||||
arch: armv7,
|
arch: armv7,
|
||||||
target: armv7-unknown-linux-gnueabihf,
|
target: armv7-unknown-linux-gnueabihf,
|
||||||
on: [self-hosted, Linux, ARM64],
|
on: ubuntu-22.04-arm,
|
||||||
distro: ubuntu18.04-rustdesk,
|
distro: ubuntu18.04-rustdesk,
|
||||||
deb_arch: armhf,
|
deb_arch: armhf,
|
||||||
sciter_arch: arm32,
|
sciter_arch: arm32,
|
||||||
vcpkg-triplet: arm-linux,
|
vcpkg-triplet: arm-linux,
|
||||||
extra_features: "",
|
extra_features: ",unix-file-copy-paste",
|
||||||
}
|
}
|
||||||
steps:
|
steps:
|
||||||
- name: Export GitHub Actions cache environment variables
|
- name: Export GitHub Actions cache environment variables
|
||||||
@@ -1724,6 +1728,8 @@ jobs:
|
|||||||
|
|
||||||
- name: Checkout source code
|
- name: Checkout source code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
submodules: recursive
|
||||||
|
|
||||||
- name: Free Space
|
- name: Free Space
|
||||||
run: |
|
run: |
|
||||||
@@ -1852,6 +1858,8 @@ jobs:
|
|||||||
cat ~/.cargo/config
|
cat ~/.cargo/config
|
||||||
# install dependencies from vcpkg
|
# install dependencies from vcpkg
|
||||||
export VCPKG_ROOT=/opt/artifacts/vcpkg
|
export VCPKG_ROOT=/opt/artifacts/vcpkg
|
||||||
|
# remove this when support higher version
|
||||||
|
export USE_AOM_391=1
|
||||||
if ! $VCPKG_ROOT/vcpkg install --triplet ${{ matrix.job.vcpkg-triplet }} --x-install-root="$VCPKG_ROOT/installed"; then
|
if ! $VCPKG_ROOT/vcpkg install --triplet ${{ matrix.job.vcpkg-triplet }} --x-install-root="$VCPKG_ROOT/installed"; then
|
||||||
find "${VCPKG_ROOT}/" -name "*.log" | while read -r _1; do
|
find "${VCPKG_ROOT}/" -name "*.log" | while read -r _1; do
|
||||||
echo "$_1:"
|
echo "$_1:"
|
||||||
@@ -1912,6 +1920,8 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Checkout source code
|
- name: Checkout source code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
submodules: recursive
|
||||||
|
|
||||||
- name: Download Binary
|
- name: Download Binary
|
||||||
uses: actions/download-artifact@master
|
uses: actions/download-artifact@master
|
||||||
@@ -1977,13 +1987,15 @@ jobs:
|
|||||||
target: aarch64-unknown-linux-gnu,
|
target: aarch64-unknown-linux-gnu,
|
||||||
# try out newer flatpak since error of "error: Nothing matches org.freedesktop.Platform in remote flathub"
|
# try out newer flatpak since error of "error: Nothing matches org.freedesktop.Platform in remote flathub"
|
||||||
distro: ubuntu22.04,
|
distro: ubuntu22.04,
|
||||||
on: [self-hosted, Linux, ARM64],
|
on: ubuntu-22.04-arm,
|
||||||
arch: aarch64,
|
arch: aarch64,
|
||||||
suffix: "",
|
suffix: "",
|
||||||
}
|
}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout source code
|
- name: Checkout source code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
submodules: recursive
|
||||||
|
|
||||||
- name: Download Binary
|
- name: Download Binary
|
||||||
uses: actions/download-artifact@master
|
uses: actions/download-artifact@master
|
||||||
@@ -2009,36 +2021,17 @@ jobs:
|
|||||||
shell: /bin/bash
|
shell: /bin/bash
|
||||||
install: |
|
install: |
|
||||||
apt-get update -y
|
apt-get update -y
|
||||||
apt-get install -y \
|
apt-get install -y git flatpak flatpak-builder
|
||||||
curl \
|
|
||||||
git \
|
|
||||||
rpm \
|
|
||||||
wget
|
|
||||||
run: |
|
run: |
|
||||||
# disable git safe.directory
|
# disable git safe.directory
|
||||||
git config --global --add safe.directory "*"
|
git config --global --add safe.directory "*"
|
||||||
pushd /workspace
|
pushd /workspace
|
||||||
# install
|
|
||||||
apt-get update -y
|
|
||||||
apt-get install -y \
|
|
||||||
cmake \
|
|
||||||
curl \
|
|
||||||
flatpak \
|
|
||||||
flatpak-builder \
|
|
||||||
gcc \
|
|
||||||
git \
|
|
||||||
g++ \
|
|
||||||
libgtk-3-dev \
|
|
||||||
nasm \
|
|
||||||
wget
|
|
||||||
# flatpak deps
|
# flatpak deps
|
||||||
flatpak --user remote-add --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo
|
flatpak --user remote-add --if-not-exists flathub https://dl.flathub.org/repo/flathub.flatpakrepo
|
||||||
flatpak --user install -y flathub org.freedesktop.Platform/${{ matrix.job.arch }}/23.08
|
|
||||||
flatpak --user install -y flathub org.freedesktop.Sdk/${{ matrix.job.arch }}/23.08
|
|
||||||
# package
|
# package
|
||||||
pushd flatpak
|
pushd flatpak
|
||||||
git clone https://github.com/flathub/shared-modules.git --depth=1
|
git clone https://github.com/flathub/shared-modules.git --depth=1
|
||||||
flatpak-builder --user --force-clean --repo=repo ./build ./rustdesk.json
|
flatpak-builder --user --install-deps-from=flathub -y --force-clean --repo=repo ./build ./rustdesk.json
|
||||||
flatpak build-bundle ./repo rustdesk-${{ env.VERSION }}-${{ matrix.job.arch }}${{ matrix.job.suffix }}.flatpak com.rustdesk.RustDesk
|
flatpak build-bundle ./repo rustdesk-${{ env.VERSION }}-${{ matrix.job.arch }}${{ matrix.job.suffix }}.flatpak com.rustdesk.RustDesk
|
||||||
|
|
||||||
- name: Publish flatpak package
|
- name: Publish flatpak package
|
||||||
@@ -2060,6 +2053,8 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Checkout source code
|
- name: Checkout source code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
submodules: recursive
|
||||||
|
|
||||||
- name: Prepare env
|
- name: Prepare env
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
10
.github/workflows/playground.yml
vendored
10
.github/workflows/playground.yml
vendored
@@ -16,9 +16,9 @@ env:
|
|||||||
FLUTTER_ELINUX_VERSION: "3.16.9"
|
FLUTTER_ELINUX_VERSION: "3.16.9"
|
||||||
TAG_NAME: "nightly"
|
TAG_NAME: "nightly"
|
||||||
VCPKG_BINARY_SOURCES: "clear;x-gha,readwrite"
|
VCPKG_BINARY_SOURCES: "clear;x-gha,readwrite"
|
||||||
# vcpkg version: 2024.06.15
|
# vcpkg version: 2024.11.16
|
||||||
VCPKG_COMMIT_ID: "f7423ee180c4b7f40d43402c2feb3859161ef625"
|
VCPKG_COMMIT_ID: "b2cb0da531c2f1f740045bfe7c4dac59f0b2b69c"
|
||||||
VERSION: "1.3.4"
|
VERSION: "1.3.8"
|
||||||
NDK_VERSION: "r26d"
|
NDK_VERSION: "r26d"
|
||||||
#signing keys env variable checks
|
#signing keys env variable checks
|
||||||
ANDROID_SIGNING_KEY: "${{ secrets.ANDROID_SIGNING_KEY }}"
|
ANDROID_SIGNING_KEY: "${{ secrets.ANDROID_SIGNING_KEY }}"
|
||||||
@@ -90,7 +90,8 @@ jobs:
|
|||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
ref: ${{ matrix.job.ref }}
|
ref: ${{ matrix.job.ref }}
|
||||||
|
submodules: recursive
|
||||||
|
|
||||||
- name: Import the codesign cert
|
- name: Import the codesign cert
|
||||||
if: env.MACOS_P12_BASE64 != null
|
if: env.MACOS_P12_BASE64 != null
|
||||||
uses: apple-actions/import-codesign-certs@v1
|
uses: apple-actions/import-codesign-certs@v1
|
||||||
@@ -250,6 +251,7 @@ jobs:
|
|||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
ref: ${{ matrix.job.ref }}
|
ref: ${{ matrix.job.ref }}
|
||||||
|
submodules: recursive
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
[submodule "libs/hbb_common"]
|
||||||
|
path = libs/hbb_common
|
||||||
|
url = https://github.com/rustdesk/hbb_common
|
||||||
118
Cargo.lock
generated
118
Cargo.lock
generated
@@ -224,7 +224,7 @@ checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da"
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "arboard"
|
name = "arboard"
|
||||||
version = "3.4.0"
|
version = "3.4.0"
|
||||||
source = "git+https://github.com/rustdesk-org/arboard#747ab2d9b40a5c9c5102051cf3b0bb38b4845e60"
|
source = "git+https://github.com/rustdesk-org/arboard#4e16bad260ea05dd7dcdb68cc7549dad3920b940"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"clipboard-win",
|
"clipboard-win",
|
||||||
"core-graphics 0.23.2",
|
"core-graphics 0.23.2",
|
||||||
@@ -234,6 +234,7 @@ dependencies = [
|
|||||||
"objc2-app-kit",
|
"objc2-app-kit",
|
||||||
"objc2-foundation",
|
"objc2-foundation",
|
||||||
"parking_lot",
|
"parking_lot",
|
||||||
|
"percent-encoding",
|
||||||
"serde 1.0.203",
|
"serde 1.0.203",
|
||||||
"serde_derive",
|
"serde_derive",
|
||||||
"windows-sys 0.48.0",
|
"windows-sys 0.48.0",
|
||||||
@@ -723,9 +724,9 @@ checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bytemuck"
|
name = "bytemuck"
|
||||||
version = "1.16.1"
|
version = "1.21.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b236fc92302c97ed75b38da1f4917b5cdda4984745740f153a5d3059e48d725e"
|
checksum = "ef657dfab802224e671f5818e9a4935f9b1957ed18e58292690cc39e7a4092a3"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "byteorder"
|
name = "byteorder"
|
||||||
@@ -735,9 +736,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bytes"
|
name = "bytes"
|
||||||
version = "1.6.0"
|
version = "1.9.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9"
|
checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde 1.0.203",
|
"serde 1.0.203",
|
||||||
]
|
]
|
||||||
@@ -1290,7 +1291,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "cpal"
|
name = "cpal"
|
||||||
version = "0.15.3"
|
version = "0.15.3"
|
||||||
source = "git+https://github.com/rustdesk-org/cpal?branch=osx-screencapturekit#4d318ff778063ce14669fd4bd67a1673653fc6e5"
|
source = "git+https://github.com/rustdesk-org/cpal?branch=osx-screencapturekit#6b374bcaed076750ca8fce6da518ab39b882e14a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"alsa",
|
"alsa",
|
||||||
"cidre",
|
"cidre",
|
||||||
@@ -1581,6 +1582,16 @@ dependencies = [
|
|||||||
"windows 0.32.0",
|
"windows 0.32.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "default_net"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "git+https://github.com/rustdesk-org/default_net#78f8f70cd85151a3a2c4a3230d80d5272703c02e"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"regex",
|
||||||
|
"winapi 0.3.9",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "deranged"
|
name = "deranged"
|
||||||
version = "0.3.11"
|
version = "0.3.11"
|
||||||
@@ -1697,7 +1708,7 @@ version = "0.5.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412"
|
checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libloading 0.7.4",
|
"libloading 0.8.4",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2221,17 +2232,17 @@ checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fuser"
|
name = "fuser"
|
||||||
version = "0.13.0"
|
version = "0.15.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "21370f84640642c8ea36dfb2a6bfc4c55941f476fcf431f6fef25a5ddcf0169b"
|
checksum = "53274f494609e77794b627b1a3cddfe45d675a6b2e9ba9c0fdc8d8eee2184369"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"log",
|
"log",
|
||||||
"memchr",
|
"memchr",
|
||||||
|
"nix 0.29.0",
|
||||||
"page_size",
|
"page_size",
|
||||||
"pkg-config",
|
|
||||||
"smallvec",
|
"smallvec",
|
||||||
"zerocopy 0.6.6",
|
"zerocopy 0.8.14",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2251,9 +2262,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-channel"
|
name = "futures-channel"
|
||||||
version = "0.3.30"
|
version = "0.3.31"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78"
|
checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"futures-core",
|
"futures-core",
|
||||||
"futures-sink",
|
"futures-sink",
|
||||||
@@ -2261,9 +2272,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-core"
|
name = "futures-core"
|
||||||
version = "0.3.30"
|
version = "0.3.31"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d"
|
checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-executor"
|
name = "futures-executor"
|
||||||
@@ -2278,9 +2289,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-io"
|
name = "futures-io"
|
||||||
version = "0.3.30"
|
version = "0.3.31"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1"
|
checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-lite"
|
name = "futures-lite"
|
||||||
@@ -2312,9 +2323,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-macro"
|
name = "futures-macro"
|
||||||
version = "0.3.30"
|
version = "0.3.31"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac"
|
checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2 1.0.86",
|
"proc-macro2 1.0.86",
|
||||||
"quote 1.0.36",
|
"quote 1.0.36",
|
||||||
@@ -2323,21 +2334,21 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-sink"
|
name = "futures-sink"
|
||||||
version = "0.3.30"
|
version = "0.3.31"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5"
|
checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-task"
|
name = "futures-task"
|
||||||
version = "0.3.30"
|
version = "0.3.31"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004"
|
checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-util"
|
name = "futures-util"
|
||||||
version = "0.3.30"
|
version = "0.3.31"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48"
|
checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"futures-channel",
|
"futures-channel",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
@@ -2901,6 +2912,7 @@ dependencies = [
|
|||||||
"bytes",
|
"bytes",
|
||||||
"chrono",
|
"chrono",
|
||||||
"confy",
|
"confy",
|
||||||
|
"default_net",
|
||||||
"directories-next",
|
"directories-next",
|
||||||
"dirs-next",
|
"dirs-next",
|
||||||
"dlopen",
|
"dlopen",
|
||||||
@@ -2925,6 +2937,7 @@ dependencies = [
|
|||||||
"serde 1.0.203",
|
"serde 1.0.203",
|
||||||
"serde_derive",
|
"serde_derive",
|
||||||
"serde_json 1.0.118",
|
"serde_json 1.0.118",
|
||||||
|
"sha2",
|
||||||
"socket2 0.3.19",
|
"socket2 0.3.19",
|
||||||
"sodiumoxide",
|
"sodiumoxide",
|
||||||
"sysinfo",
|
"sysinfo",
|
||||||
@@ -3065,7 +3078,7 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "hwcodec"
|
name = "hwcodec"
|
||||||
version = "0.7.1"
|
version = "0.7.1"
|
||||||
source = "git+https://github.com/rustdesk-org/hwcodec#835e599ed229e4e01b6fa3566e02ea45c73e2e9c"
|
source = "git+https://github.com/rustdesk-org/hwcodec#0ea7e709d3c48bb6446e33a9cc8fd0e0da5709b9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bindgen 0.59.2",
|
"bindgen 0.59.2",
|
||||||
"cc",
|
"cc",
|
||||||
@@ -4163,7 +4176,7 @@ version = "0.7.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "681030a937600a36906c185595136d26abfebb4aa9c65701cefcaf8578bb982b"
|
checksum = "681030a937600a36906c185595136d26abfebb4aa9c65701cefcaf8578bb982b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro-crate 1.3.1",
|
"proc-macro-crate 2.0.2",
|
||||||
"proc-macro2 1.0.86",
|
"proc-macro2 1.0.86",
|
||||||
"quote 1.0.36",
|
"quote 1.0.36",
|
||||||
"syn 2.0.68",
|
"syn 2.0.68",
|
||||||
@@ -4382,9 +4395,9 @@ checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "openssl"
|
name = "openssl"
|
||||||
version = "0.10.64"
|
version = "0.10.68"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "95a0481286a310808298130d22dd1fef0fa571e05a8f44ec801801e84b216b1f"
|
checksum = "6174bc48f102d208783c2c84bf931bb75927a617866870de8a4ea85597f871f5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.6.0",
|
"bitflags 2.6.0",
|
||||||
"cfg-if 1.0.0",
|
"cfg-if 1.0.0",
|
||||||
@@ -4414,9 +4427,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "openssl-sys"
|
name = "openssl-sys"
|
||||||
version = "0.9.102"
|
version = "0.9.104"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c597637d56fbc83893a35eb0dd04b2b8e7a50c91e64e9493e398b5df4fb45fa2"
|
checksum = "45abf306cbf99debc8195b66b7346498d7b10c210de50418b5ccd7ceba08c741"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cc",
|
"cc",
|
||||||
"libc",
|
"libc",
|
||||||
@@ -4496,9 +4509,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "page_size"
|
name = "page_size"
|
||||||
version = "0.5.0"
|
version = "0.6.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1b7663cbd190cfd818d08efa8497f6cd383076688c49a391ef7c0d03cd12b561"
|
checksum = "30d5b2194ed13191c1999ae0704b7839fb18384fa22e49b57eeaa97d79ce40da"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"winapi 0.3.9",
|
"winapi 0.3.9",
|
||||||
@@ -5219,7 +5232,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "rdev"
|
name = "rdev"
|
||||||
version = "0.5.0-2"
|
version = "0.5.0-2"
|
||||||
source = "git+https://github.com/rustdesk-org/rdev#01ac3ec8009f04f7615842b9152338844b806184"
|
source = "git+https://github.com/rustdesk-org/rdev#f9b60b1dd0f3300a1b797d7a74c116683cd232c8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cocoa 0.24.1",
|
"cocoa 0.24.1",
|
||||||
"core-foundation 0.9.4",
|
"core-foundation 0.9.4",
|
||||||
@@ -5466,7 +5479,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "rust-pulsectl"
|
name = "rust-pulsectl"
|
||||||
version = "0.2.12"
|
version = "0.2.12"
|
||||||
source = "git+https://github.com/open-trade/pulsectl#5e68f4c2b7c644fa321984688602d71e8ad0bba3"
|
source = "git+https://github.com/rustdesk-org/pulsectl#aa34dde499aa912a3abc5289cc0b547bd07dd6e2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libpulse-binding",
|
"libpulse-binding",
|
||||||
]
|
]
|
||||||
@@ -5494,7 +5507,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustdesk"
|
name = "rustdesk"
|
||||||
version = "1.3.4"
|
version = "1.3.8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"android-wakelock",
|
"android-wakelock",
|
||||||
"android_logger",
|
"android_logger",
|
||||||
@@ -5594,7 +5607,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustdesk-portable-packer"
|
name = "rustdesk-portable-packer"
|
||||||
version = "1.3.4"
|
version = "1.3.8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"brotli",
|
"brotli",
|
||||||
"dirs 5.0.1",
|
"dirs 5.0.1",
|
||||||
@@ -5813,7 +5826,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "sciter-rs"
|
name = "sciter-rs"
|
||||||
version = "0.5.57"
|
version = "0.5.57"
|
||||||
source = "git+https://github.com/open-trade/rust-sciter?branch=dyn#5322f3a755a0e6bf999fbc60d1efc35246c0f821"
|
source = "git+https://github.com/rustdesk-org/rust-sciter?branch=dyn#5322f3a755a0e6bf999fbc60d1efc35246c0f821"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"libc",
|
"libc",
|
||||||
@@ -8062,16 +8075,6 @@ dependencies = [
|
|||||||
"zvariant",
|
"zvariant",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "zerocopy"
|
|
||||||
version = "0.6.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "854e949ac82d619ee9a14c66a1b674ac730422372ccb759ce0c39cabcf2bf8e6"
|
|
||||||
dependencies = [
|
|
||||||
"byteorder",
|
|
||||||
"zerocopy-derive 0.6.6",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zerocopy"
|
name = "zerocopy"
|
||||||
version = "0.7.34"
|
version = "0.7.34"
|
||||||
@@ -8082,10 +8085,19 @@ dependencies = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zerocopy-derive"
|
name = "zerocopy"
|
||||||
version = "0.6.6"
|
version = "0.8.14"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "125139de3f6b9d625c39e2efdd73d41bdac468ccd556556440e322be0e1bbd91"
|
checksum = "a367f292d93d4eab890745e75a778da40909cab4d6ff8173693812f79c4a2468"
|
||||||
|
dependencies = [
|
||||||
|
"zerocopy-derive 0.8.14",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zerocopy-derive"
|
||||||
|
version = "0.7.34"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "15e934569e47891f7d9411f1a451d947a60e000ab3bd24fbb970f000387d1b3b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2 1.0.86",
|
"proc-macro2 1.0.86",
|
||||||
"quote 1.0.36",
|
"quote 1.0.36",
|
||||||
@@ -8094,9 +8106,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zerocopy-derive"
|
name = "zerocopy-derive"
|
||||||
version = "0.7.34"
|
version = "0.8.14"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "15e934569e47891f7d9411f1a451d947a60e000ab3bd24fbb970f000387d1b3b"
|
checksum = "d3931cb58c62c13adec22e38686b559c86a30565e16ad6e8510a337cedc611e1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2 1.0.86",
|
"proc-macro2 1.0.86",
|
||||||
"quote 1.0.36",
|
"quote 1.0.36",
|
||||||
|
|||||||
15
Cargo.toml
15
Cargo.toml
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "rustdesk"
|
name = "rustdesk"
|
||||||
version = "1.3.4"
|
version = "1.3.8"
|
||||||
authors = ["rustdesk <info@rustdesk.com>"]
|
authors = ["rustdesk <info@rustdesk.com>"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
build= "build.rs"
|
build= "build.rs"
|
||||||
@@ -16,6 +16,10 @@ crate-type = ["cdylib", "staticlib", "rlib"]
|
|||||||
name = "naming"
|
name = "naming"
|
||||||
path = "src/naming.rs"
|
path = "src/naming.rs"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "service"
|
||||||
|
path = "src/service.rs"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
inline = []
|
inline = []
|
||||||
cli = []
|
cli = []
|
||||||
@@ -78,12 +82,15 @@ fon = "0.6"
|
|||||||
zip = "0.6"
|
zip = "0.6"
|
||||||
shutdown_hooks = "0.1"
|
shutdown_hooks = "0.1"
|
||||||
totp-rs = { version = "5.4", default-features = false, features = ["gen_secret", "otpauth"] }
|
totp-rs = { version = "5.4", default-features = false, features = ["gen_secret", "otpauth"] }
|
||||||
|
|
||||||
|
[target.'cfg(not(target_os = "linux"))'.dependencies]
|
||||||
|
# https://github.com/rustdesk/rustdesk/discussions/10197, not use cpal on linux
|
||||||
cpal = { git = "https://github.com/rustdesk-org/cpal", branch = "osx-screencapturekit" }
|
cpal = { git = "https://github.com/rustdesk-org/cpal", branch = "osx-screencapturekit" }
|
||||||
ringbuf = "0.3"
|
ringbuf = "0.3"
|
||||||
|
|
||||||
[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"
|
||||||
sciter-rs = { git = "https://github.com/open-trade/rust-sciter", branch = "dyn" }
|
sciter-rs = { git = "https://github.com/rustdesk-org/rust-sciter", branch = "dyn" }
|
||||||
sys-locale = "0.3"
|
sys-locale = "0.3"
|
||||||
enigo = { path = "libs/enigo", features = [ "with_serde" ] }
|
enigo = { path = "libs/enigo", features = [ "with_serde" ] }
|
||||||
clipboard = { path = "libs/clipboard" }
|
clipboard = { path = "libs/clipboard" }
|
||||||
@@ -149,7 +156,7 @@ reqwest = { git = "https://github.com/rustdesk-org/reqwest", features = ["blocki
|
|||||||
[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" }
|
||||||
pulse = { package = "libpulse-binding", version = "2.27" }
|
pulse = { package = "libpulse-binding", version = "2.27" }
|
||||||
rust-pulsectl = { git = "https://github.com/open-trade/pulsectl" }
|
rust-pulsectl = { git = "https://github.com/rustdesk-org/pulsectl" }
|
||||||
async-process = "1.7"
|
async-process = "1.7"
|
||||||
evdev = { git="https://github.com/rustdesk-org/evdev" }
|
evdev = { git="https://github.com/rustdesk-org/evdev" }
|
||||||
dbus = "0.9"
|
dbus = "0.9"
|
||||||
@@ -174,7 +181,7 @@ members = ["libs/scrap", "libs/hbb_common", "libs/enigo", "libs/clipboard", "lib
|
|||||||
exclude = ["vdi/host", "examples/custom_plugin"]
|
exclude = ["vdi/host", "examples/custom_plugin"]
|
||||||
|
|
||||||
[package.metadata.winres]
|
[package.metadata.winres]
|
||||||
LegalCopyright = "Copyright © 2024 Purslane Ltd. All rights reserved."
|
LegalCopyright = "Copyright © 2025 Purslane Ltd. All rights reserved."
|
||||||
ProductName = "RustDesk"
|
ProductName = "RustDesk"
|
||||||
FileDescription = "RustDesk Remote Desktop"
|
FileDescription = "RustDesk Remote Desktop"
|
||||||
OriginalFilename = "rustdesk.exe"
|
OriginalFilename = "rustdesk.exe"
|
||||||
|
|||||||
11
Dockerfile
11
Dockerfile
@@ -2,6 +2,7 @@ FROM debian:bullseye-slim
|
|||||||
|
|
||||||
WORKDIR /
|
WORKDIR /
|
||||||
ARG DEBIAN_FRONTEND=noninteractive
|
ARG DEBIAN_FRONTEND=noninteractive
|
||||||
|
ENV VCPKG_FORCE_SYSTEM_BINARIES=1
|
||||||
RUN apt update -y && \
|
RUN apt update -y && \
|
||||||
apt install --yes --no-install-recommends \
|
apt install --yes --no-install-recommends \
|
||||||
g++ \
|
g++ \
|
||||||
@@ -21,7 +22,8 @@ RUN apt update -y && \
|
|||||||
libpam0g-dev \
|
libpam0g-dev \
|
||||||
libpulse-dev \
|
libpulse-dev \
|
||||||
make \
|
make \
|
||||||
cmake \
|
wget \
|
||||||
|
libssl-dev \
|
||||||
unzip \
|
unzip \
|
||||||
zip \
|
zip \
|
||||||
sudo \
|
sudo \
|
||||||
@@ -31,6 +33,13 @@ RUN apt update -y && \
|
|||||||
ninja-build && \
|
ninja-build && \
|
||||||
rm -rf /var/lib/apt/lists/*
|
rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
RUN wget https://github.com/Kitware/CMake/releases/download/v3.30.6/cmake-3.30.6.tar.gz --no-check-certificate && \
|
||||||
|
tar xzf cmake-3.30.6.tar.gz && \
|
||||||
|
cd cmake-3.30.6 && \
|
||||||
|
./configure --prefix=/usr/local && \
|
||||||
|
make && \
|
||||||
|
make install
|
||||||
|
|
||||||
RUN git clone --branch 2023.04.15 --depth=1 https://github.com/microsoft/vcpkg && \
|
RUN git clone --branch 2023.04.15 --depth=1 https://github.com/microsoft/vcpkg && \
|
||||||
/vcpkg/bootstrap-vcpkg.sh -disableMetrics && \
|
/vcpkg/bootstrap-vcpkg.sh -disableMetrics && \
|
||||||
/vcpkg/vcpkg --disable-metrics install libvpx libyuv opus aom
|
/vcpkg/vcpkg --disable-metrics install libvpx libyuv opus aom
|
||||||
|
|||||||
16
README.md
16
README.md
@@ -1,11 +1,10 @@
|
|||||||
<p align="center">
|
<p align="center">
|
||||||
<img src="res/logo-header.svg" alt="RustDesk - Your remote desktop"><br>
|
<img src="res/logo-header.svg" alt="RustDesk - Your remote desktop"><br>
|
||||||
<a href="#public-servers">Servers</a> •
|
|
||||||
<a href="#raw-steps-to-build">Build</a> •
|
<a href="#raw-steps-to-build">Build</a> •
|
||||||
<a href="#how-to-build-with-docker">Docker</a> •
|
<a href="#how-to-build-with-docker">Docker</a> •
|
||||||
<a href="#file-structure">Structure</a> •
|
<a href="#file-structure">Structure</a> •
|
||||||
<a href="#snapshot">Snapshot</a><br>
|
<a href="#snapshot">Snapshot</a><br>
|
||||||
[<a href="docs/README-UA.md">Українська</a>] | [<a href="docs/README-CS.md">česky</a>] | [<a href="docs/README-ZH.md">中文</a>] | [<a href="docs/README-HU.md">Magyar</a>] | [<a href="docs/README-ES.md">Español</a>] | [<a href="docs/README-FA.md">فارسی</a>] | [<a href="docs/README-FR.md">Français</a>] | [<a href="docs/README-DE.md">Deutsch</a>] | [<a href="docs/README-PL.md">Polski</a>] | [<a href="docs/README-ID.md">Indonesian</a>] | [<a href="docs/README-FI.md">Suomi</a>] | [<a href="docs/README-ML.md">മലയാളം</a>] | [<a href="docs/README-JP.md">日本語</a>] | [<a href="docs/README-NL.md">Nederlands</a>] | [<a href="docs/README-IT.md">Italiano</a>] | [<a href="docs/README-RU.md">Русский</a>] | [<a href="docs/README-PTBR.md">Português (Brasil)</a>] | [<a href="docs/README-EO.md">Esperanto</a>] | [<a href="docs/README-KR.md">한국어</a>] | [<a href="docs/README-AR.md">العربي</a>] | [<a href="docs/README-VN.md">Tiếng Việt</a>] | [<a href="docs/README-DA.md">Dansk</a>] | [<a href="docs/README-GR.md">Ελληνικά</a>] | [<a href="docs/README-TR.md">Türkçe</a>]<br>
|
[<a href="docs/README-UA.md">Українська</a>] | [<a href="docs/README-CS.md">česky</a>] | [<a href="docs/README-ZH.md">中文</a>] | [<a href="docs/README-HU.md">Magyar</a>] | [<a href="docs/README-ES.md">Español</a>] | [<a href="docs/README-FA.md">فارسی</a>] | [<a href="docs/README-FR.md">Français</a>] | [<a href="docs/README-DE.md">Deutsch</a>] | [<a href="docs/README-PL.md">Polski</a>] | [<a href="docs/README-ID.md">Indonesian</a>] | [<a href="docs/README-FI.md">Suomi</a>] | [<a href="docs/README-ML.md">മലയാളം</a>] | [<a href="docs/README-JP.md">日本語</a>] | [<a href="docs/README-NL.md">Nederlands</a>] | [<a href="docs/README-IT.md">Italiano</a>] | [<a href="docs/README-RU.md">Русский</a>] | [<a href="docs/README-PTBR.md">Português (Brasil)</a>] | [<a href="docs/README-EO.md">Esperanto</a>] | [<a href="docs/README-KR.md">한국어</a>] | [<a href="docs/README-AR.md">العربي</a>] | [<a href="docs/README-VN.md">Tiếng Việt</a>] | [<a href="docs/README-DA.md">Dansk</a>] | [<a href="docs/README-GR.md">Ελληνικά</a>] | [<a href="docs/README-TR.md">Türkçe</a>] | [<a href="docs/README-NO.md">Norsk</a>]<br>
|
||||||
<b>We need your help to translate this README, <a href="https://github.com/rustdesk/rustdesk/tree/master/src/lang">RustDesk UI</a> and <a href="https://github.com/rustdesk/doc.rustdesk.com">RustDesk Doc</a> to your native language</b>
|
<b>We need your help to translate this README, <a href="https://github.com/rustdesk/rustdesk/tree/master/src/lang">RustDesk UI</a> and <a href="https://github.com/rustdesk/doc.rustdesk.com">RustDesk Doc</a> to your native language</b>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
@@ -25,9 +24,12 @@ RustDesk welcomes contribution from everyone. See [CONTRIBUTING.md](docs/CONTRIB
|
|||||||
|
|
||||||
[**NIGHTLY BUILD**](https://github.com/rustdesk/rustdesk/releases/tag/nightly)
|
[**NIGHTLY BUILD**](https://github.com/rustdesk/rustdesk/releases/tag/nightly)
|
||||||
|
|
||||||
[<img src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png"
|
[<img src="https://f-droid.org/badge/get-it-on.png"
|
||||||
alt="Get it on F-Droid"
|
alt="Get it on F-Droid"
|
||||||
height="80">](https://f-droid.org/en/packages/com.carriez.flutter_hbb)
|
height="80">](https://f-droid.org/en/packages/com.carriez.flutter_hbb)
|
||||||
|
[<img src="https://flathub.org/api/badge?svg&locale=en"
|
||||||
|
alt="Get it on Flathub"
|
||||||
|
height="80">](https://flathub.org/apps/com.rustdesk.RustDesk)
|
||||||
|
|
||||||
## Dependencies
|
## Dependencies
|
||||||
|
|
||||||
@@ -125,6 +127,7 @@ Begin by cloning the repository and building the Docker container:
|
|||||||
```sh
|
```sh
|
||||||
git clone https://github.com/rustdesk/rustdesk
|
git clone https://github.com/rustdesk/rustdesk
|
||||||
cd rustdesk
|
cd rustdesk
|
||||||
|
git submodule update --init --recursive
|
||||||
docker build -t "rustdesk-builder" .
|
docker build -t "rustdesk-builder" .
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -162,6 +165,10 @@ Please ensure that you are running these commands from the root of the RustDesk
|
|||||||
- **[flutter](https://github.com/rustdesk/rustdesk/tree/master/flutter)**: Flutter code for desktop and mobile
|
- **[flutter](https://github.com/rustdesk/rustdesk/tree/master/flutter)**: Flutter code for desktop and mobile
|
||||||
- **[flutter/web/js](https://github.com/rustdesk/rustdesk/tree/master/flutter/web/js)**: JavaScript for Flutter web client
|
- **[flutter/web/js](https://github.com/rustdesk/rustdesk/tree/master/flutter/web/js)**: JavaScript for Flutter web client
|
||||||
|
|
||||||
|
> [!Caution]
|
||||||
|
> **Misuse Disclaimer:** <br>
|
||||||
|
> The developers of RustDesk do not condone or support any unethical or illegal use of this software. Misuse, such as unauthorized access, control or invasion of privacy, is strictly against our guidelines. The authors are not responsible for any misuse of the application.
|
||||||
|
|
||||||
## Screenshots
|
## Screenshots
|
||||||
|
|
||||||

|

|
||||||
@@ -172,6 +179,3 @@ Please ensure that you are running these commands from the root of the RustDesk
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
## [Public Servers](#public-servers)
|
|
||||||
|
|
||||||
RustDesk is supported by a free EU server, graciously provided by [Codext GmbH](https://codext.link/rustdesk?utm_source=github)
|
|
||||||
|
|||||||
@@ -18,8 +18,8 @@ AppDir:
|
|||||||
id: rustdesk
|
id: rustdesk
|
||||||
name: rustdesk
|
name: rustdesk
|
||||||
icon: rustdesk
|
icon: rustdesk
|
||||||
version: 1.3.4
|
version: 1.3.8
|
||||||
exec: usr/lib/rustdesk/rustdesk
|
exec: usr/share/rustdesk/rustdesk
|
||||||
exec_args: $@
|
exec_args: $@
|
||||||
apt:
|
apt:
|
||||||
arch:
|
arch:
|
||||||
@@ -77,7 +77,7 @@ AppDir:
|
|||||||
env:
|
env:
|
||||||
GIO_MODULE_DIR: /lib64/gio/modules:/usr/lib/aarch64-linux-gnu/gio/modules:$APPDIR/usr/lib/aarch64-linux-gnu/gio/modules
|
GIO_MODULE_DIR: /lib64/gio/modules:/usr/lib/aarch64-linux-gnu/gio/modules:$APPDIR/usr/lib/aarch64-linux-gnu/gio/modules
|
||||||
GDK_BACKEND: x11
|
GDK_BACKEND: x11
|
||||||
APPDIR_LIBRARY_PATH: /lib64:/usr/lib/aarch64-linux-gnu:$APPDIR/lib/aarch64-linux-gnu:$APPDIR/lib/aarch64-linux-gnu/security:$APPDIR/lib/systemd:$APPDIR/usr/lib/aarch64-linux-gnu:$APPDIR/usr/lib/aarch64-linux-gnu/gdk-pixbuf-2.0/2.10.0/loaders:$APPDIR/usr/lib/aarch64-linux-gnu/gstreamer-1.0:$APPDIR/usr/lib/aarch64-linux-gnu/gtk-3.0/3.0.0/immodules:$APPDIR/usr/lib/aarch64-linux-gnu/gtk-3.0/3.0.0/printbackends:$APPDIR/usr/lib/aarch64-linux-gnu/krb5/plugins/preauth:$APPDIR/usr/lib/aarch64-linux-gnu/libcanberra-0.30:$APPDIR/usr/lib/aarch64-linux-gnu/pulseaudio:$APPDIR/usr/lib/aarch64-linux-gnu/sasl2:$APPDIR/usr/lib/aarch64-linux-gnu/vdpau:$APPDIR/usr/lib/rustdesk/lib:$APPDIR/lib/aarch64
|
APPDIR_LIBRARY_PATH: /lib64:/usr/lib/aarch64-linux-gnu:$APPDIR/lib/aarch64-linux-gnu:$APPDIR/lib/aarch64-linux-gnu/security:$APPDIR/lib/systemd:$APPDIR/usr/lib/aarch64-linux-gnu:$APPDIR/usr/lib/aarch64-linux-gnu/gdk-pixbuf-2.0/2.10.0/loaders:$APPDIR/usr/lib/aarch64-linux-gnu/gstreamer-1.0:$APPDIR/usr/lib/aarch64-linux-gnu/gtk-3.0/3.0.0/immodules:$APPDIR/usr/lib/aarch64-linux-gnu/gtk-3.0/3.0.0/printbackends:$APPDIR/usr/lib/aarch64-linux-gnu/krb5/plugins/preauth:$APPDIR/usr/lib/aarch64-linux-gnu/libcanberra-0.30:$APPDIR/usr/lib/aarch64-linux-gnu/pulseaudio:$APPDIR/usr/lib/aarch64-linux-gnu/sasl2:$APPDIR/usr/lib/aarch64-linux-gnu/vdpau:$APPDIR/usr/share/rustdesk/lib:$APPDIR/lib/aarch64
|
||||||
GST_PLUGIN_PATH: /lib64/gstreamer-1.0:/usr/lib/aarch64-linux-gnu/gstreamer-1.0:$APPDIR/usr/lib/aarch64-linux-gnu/gstreamer-1.0
|
GST_PLUGIN_PATH: /lib64/gstreamer-1.0:/usr/lib/aarch64-linux-gnu/gstreamer-1.0:$APPDIR/usr/lib/aarch64-linux-gnu/gstreamer-1.0
|
||||||
GST_PLUGIN_SYSTEM_PATH: /lib64/gstreamer-1.0:/usr/lib/aarch64-linux-gnu/gstreamer-1.0:$APPDIR/usr/lib/aarch64-linux-gnu/gstreamer-1.0
|
GST_PLUGIN_SYSTEM_PATH: /lib64/gstreamer-1.0:/usr/lib/aarch64-linux-gnu/gstreamer-1.0:$APPDIR/usr/lib/aarch64-linux-gnu/gstreamer-1.0
|
||||||
test:
|
test:
|
||||||
|
|||||||
@@ -18,8 +18,8 @@ AppDir:
|
|||||||
id: rustdesk
|
id: rustdesk
|
||||||
name: rustdesk
|
name: rustdesk
|
||||||
icon: rustdesk
|
icon: rustdesk
|
||||||
version: 1.3.4
|
version: 1.3.8
|
||||||
exec: usr/lib/rustdesk/rustdesk
|
exec: usr/share/rustdesk/rustdesk
|
||||||
exec_args: $@
|
exec_args: $@
|
||||||
apt:
|
apt:
|
||||||
arch:
|
arch:
|
||||||
@@ -80,7 +80,7 @@ AppDir:
|
|||||||
env:
|
env:
|
||||||
GIO_MODULE_DIR: /lib64/gio/modules:/usr/lib/x86_64-linux-gnu/gio/modules:$APPDIR/usr/lib/x86_64-linux-gnu/gio/modules
|
GIO_MODULE_DIR: /lib64/gio/modules:/usr/lib/x86_64-linux-gnu/gio/modules:$APPDIR/usr/lib/x86_64-linux-gnu/gio/modules
|
||||||
GDK_BACKEND: x11
|
GDK_BACKEND: x11
|
||||||
APPDIR_LIBRARY_PATH: /lib64:/usr/lib/x86_64-linux-gnu:$APPDIR/lib/x86_64-linux-gnu:$APPDIR/lib/x86_64-linux-gnu/security:$APPDIR/lib/systemd:$APPDIR/usr/lib/x86_64-linux-gnu:$APPDIR/usr/lib/x86_64-linux-gnu/gdk-pixbuf-2.0/2.10.0/loaders:$APPDIR/usr/lib/x86_64-linux-gnu/gstreamer-1.0:$APPDIR/usr/lib/x86_64-linux-gnu/gtk-3.0/3.0.0/immodules:$APPDIR/usr/lib/x86_64-linux-gnu/gtk-3.0/3.0.0/printbackends:$APPDIR/usr/lib/x86_64-linux-gnu/krb5/plugins/preauth:$APPDIR/usr/lib/x86_64-linux-gnu/libcanberra-0.30:$APPDIR/usr/lib/x86_64-linux-gnu/pulseaudio:$APPDIR/usr/lib/x86_64-linux-gnu/sasl2:$APPDIR/usr/lib/x86_64-linux-gnu/vdpau:$APPDIR/usr/lib/rustdesk/lib:$APPDIR/lib/x86_64
|
APPDIR_LIBRARY_PATH: /lib64:/usr/lib/x86_64-linux-gnu:$APPDIR/lib/x86_64-linux-gnu:$APPDIR/lib/x86_64-linux-gnu/security:$APPDIR/lib/systemd:$APPDIR/usr/lib/x86_64-linux-gnu:$APPDIR/usr/lib/x86_64-linux-gnu/gdk-pixbuf-2.0/2.10.0/loaders:$APPDIR/usr/lib/x86_64-linux-gnu/gstreamer-1.0:$APPDIR/usr/lib/x86_64-linux-gnu/gtk-3.0/3.0.0/immodules:$APPDIR/usr/lib/x86_64-linux-gnu/gtk-3.0/3.0.0/printbackends:$APPDIR/usr/lib/x86_64-linux-gnu/krb5/plugins/preauth:$APPDIR/usr/lib/x86_64-linux-gnu/libcanberra-0.30:$APPDIR/usr/lib/x86_64-linux-gnu/pulseaudio:$APPDIR/usr/lib/x86_64-linux-gnu/sasl2:$APPDIR/usr/lib/x86_64-linux-gnu/vdpau:$APPDIR/usr/share/rustdesk/lib:$APPDIR/lib/x86_64
|
||||||
GST_PLUGIN_PATH: /lib64/gstreamer-1.0:/usr/lib/x86_64-linux-gnu/gstreamer-1.0:$APPDIR/usr/lib/x86_64-linux-gnu/gstreamer-1.0
|
GST_PLUGIN_PATH: /lib64/gstreamer-1.0:/usr/lib/x86_64-linux-gnu/gstreamer-1.0:$APPDIR/usr/lib/x86_64-linux-gnu/gstreamer-1.0
|
||||||
GST_PLUGIN_SYSTEM_PATH: /lib64/gstreamer-1.0:/usr/lib/x86_64-linux-gnu/gstreamer-1.0:$APPDIR/usr/lib/x86_64-linux-gnu/gstreamer-1.0
|
GST_PLUGIN_SYSTEM_PATH: /lib64/gstreamer-1.0:/usr/lib/x86_64-linux-gnu/gstreamer-1.0:$APPDIR/usr/lib/x86_64-linux-gnu/gstreamer-1.0
|
||||||
test:
|
test:
|
||||||
|
|||||||
37
build.py
37
build.py
@@ -9,6 +9,7 @@ import shutil
|
|||||||
import hashlib
|
import hashlib
|
||||||
import argparse
|
import argparse
|
||||||
import sys
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
windows = platform.platform().startswith('Windows')
|
windows = platform.platform().startswith('Windows')
|
||||||
osx = platform.platform().startswith(
|
osx = platform.platform().startswith(
|
||||||
@@ -321,7 +322,7 @@ def build_flutter_deb(version, features):
|
|||||||
os.chdir('flutter')
|
os.chdir('flutter')
|
||||||
system2('flutter build linux --release')
|
system2('flutter build linux --release')
|
||||||
system2('mkdir -p tmpdeb/usr/bin/')
|
system2('mkdir -p tmpdeb/usr/bin/')
|
||||||
system2('mkdir -p tmpdeb/usr/lib/rustdesk')
|
system2('mkdir -p tmpdeb/usr/share/rustdesk')
|
||||||
system2('mkdir -p tmpdeb/etc/rustdesk/')
|
system2('mkdir -p tmpdeb/etc/rustdesk/')
|
||||||
system2('mkdir -p tmpdeb/etc/pam.d/')
|
system2('mkdir -p tmpdeb/etc/pam.d/')
|
||||||
system2('mkdir -p tmpdeb/usr/share/rustdesk/files/systemd/')
|
system2('mkdir -p tmpdeb/usr/share/rustdesk/files/systemd/')
|
||||||
@@ -331,7 +332,7 @@ def build_flutter_deb(version, features):
|
|||||||
system2('mkdir -p tmpdeb/usr/share/polkit-1/actions')
|
system2('mkdir -p tmpdeb/usr/share/polkit-1/actions')
|
||||||
system2('rm tmpdeb/usr/bin/rustdesk || true')
|
system2('rm tmpdeb/usr/bin/rustdesk || true')
|
||||||
system2(
|
system2(
|
||||||
f'cp -r {flutter_build_dir}/* tmpdeb/usr/lib/rustdesk/')
|
f'cp -r {flutter_build_dir}/* tmpdeb/usr/share/rustdesk/')
|
||||||
system2(
|
system2(
|
||||||
'cp ../res/rustdesk.service tmpdeb/usr/share/rustdesk/files/systemd/')
|
'cp ../res/rustdesk.service tmpdeb/usr/share/rustdesk/files/systemd/')
|
||||||
system2(
|
system2(
|
||||||
@@ -354,7 +355,7 @@ def build_flutter_deb(version, features):
|
|||||||
system2('mkdir -p tmpdeb/DEBIAN')
|
system2('mkdir -p tmpdeb/DEBIAN')
|
||||||
generate_control_file(version)
|
generate_control_file(version)
|
||||||
system2('cp -a ../res/DEBIAN/* tmpdeb/DEBIAN/')
|
system2('cp -a ../res/DEBIAN/* tmpdeb/DEBIAN/')
|
||||||
md5_file('usr/share/rustdesk/files/systemd/rustdesk.service')
|
md5_file_folder("tmpdeb/")
|
||||||
system2('dpkg-deb -b tmpdeb rustdesk.deb;')
|
system2('dpkg-deb -b tmpdeb rustdesk.deb;')
|
||||||
|
|
||||||
system2('/bin/rm -rf tmpdeb/')
|
system2('/bin/rm -rf tmpdeb/')
|
||||||
@@ -366,7 +367,7 @@ def build_flutter_deb(version, features):
|
|||||||
def build_deb_from_folder(version, binary_folder):
|
def build_deb_from_folder(version, binary_folder):
|
||||||
os.chdir('flutter')
|
os.chdir('flutter')
|
||||||
system2('mkdir -p tmpdeb/usr/bin/')
|
system2('mkdir -p tmpdeb/usr/bin/')
|
||||||
system2('mkdir -p tmpdeb/usr/lib/rustdesk')
|
system2('mkdir -p tmpdeb/usr/share/rustdesk')
|
||||||
system2('mkdir -p tmpdeb/usr/share/rustdesk/files/systemd/')
|
system2('mkdir -p tmpdeb/usr/share/rustdesk/files/systemd/')
|
||||||
system2('mkdir -p tmpdeb/usr/share/icons/hicolor/256x256/apps/')
|
system2('mkdir -p tmpdeb/usr/share/icons/hicolor/256x256/apps/')
|
||||||
system2('mkdir -p tmpdeb/usr/share/icons/hicolor/scalable/apps/')
|
system2('mkdir -p tmpdeb/usr/share/icons/hicolor/scalable/apps/')
|
||||||
@@ -374,7 +375,7 @@ def build_deb_from_folder(version, binary_folder):
|
|||||||
system2('mkdir -p tmpdeb/usr/share/polkit-1/actions')
|
system2('mkdir -p tmpdeb/usr/share/polkit-1/actions')
|
||||||
system2('rm tmpdeb/usr/bin/rustdesk || true')
|
system2('rm tmpdeb/usr/bin/rustdesk || true')
|
||||||
system2(
|
system2(
|
||||||
f'cp -r ../{binary_folder}/* tmpdeb/usr/lib/rustdesk/')
|
f'cp -r ../{binary_folder}/* tmpdeb/usr/share/rustdesk/')
|
||||||
system2(
|
system2(
|
||||||
'cp ../res/rustdesk.service tmpdeb/usr/share/rustdesk/files/systemd/')
|
'cp ../res/rustdesk.service tmpdeb/usr/share/rustdesk/files/systemd/')
|
||||||
system2(
|
system2(
|
||||||
@@ -391,7 +392,7 @@ def build_deb_from_folder(version, binary_folder):
|
|||||||
system2('mkdir -p tmpdeb/DEBIAN')
|
system2('mkdir -p tmpdeb/DEBIAN')
|
||||||
generate_control_file(version)
|
generate_control_file(version)
|
||||||
system2('cp -a ../res/DEBIAN/* tmpdeb/DEBIAN/')
|
system2('cp -a ../res/DEBIAN/* tmpdeb/DEBIAN/')
|
||||||
md5_file('usr/share/rustdesk/files/systemd/rustdesk.service')
|
md5_file_folder("tmpdeb/")
|
||||||
system2('dpkg-deb -b tmpdeb rustdesk.deb;')
|
system2('dpkg-deb -b tmpdeb rustdesk.deb;')
|
||||||
|
|
||||||
system2('/bin/rm -rf tmpdeb/')
|
system2('/bin/rm -rf tmpdeb/')
|
||||||
@@ -404,12 +405,13 @@ def build_flutter_dmg(version, features):
|
|||||||
if not skip_cargo:
|
if not skip_cargo:
|
||||||
# set minimum osx build target, now is 10.14, which is the same as the flutter xcode project
|
# set minimum osx build target, now is 10.14, which is the same as the flutter xcode project
|
||||||
system2(
|
system2(
|
||||||
f'MACOSX_DEPLOYMENT_TARGET=10.14 cargo build --features {features} --lib --release')
|
f'MACOSX_DEPLOYMENT_TARGET=10.14 cargo build --features {features} --release')
|
||||||
# copy dylib
|
# copy dylib
|
||||||
system2(
|
system2(
|
||||||
"cp target/release/liblibrustdesk.dylib target/release/librustdesk.dylib")
|
"cp target/release/liblibrustdesk.dylib target/release/librustdesk.dylib")
|
||||||
os.chdir('flutter')
|
os.chdir('flutter')
|
||||||
system2('flutter build macos --release')
|
system2('flutter build macos --release')
|
||||||
|
system2('cp -rf ../target/release/service ./build/macos/Build/Products/Release/RustDesk.app/Contents/MacOS/')
|
||||||
'''
|
'''
|
||||||
system2(
|
system2(
|
||||||
"create-dmg --volname \"RustDesk Installer\" --window-pos 200 120 --window-size 800 400 --icon-size 100 --app-drop-link 600 185 --icon RustDesk.app 200 190 --hide-extension RustDesk.app rustdesk.dmg ./build/macos/Build/Products/Release/RustDesk.app")
|
"create-dmg --volname \"RustDesk Installer\" --window-pos 200 120 --window-size 800 400 --icon-size 100 --app-drop-link 600 185 --icon RustDesk.app 200 190 --hide-extension RustDesk.app rustdesk.dmg ./build/macos/Build/Products/Release/RustDesk.app")
|
||||||
@@ -621,21 +623,24 @@ def main():
|
|||||||
os.system('mkdir -p tmpdeb/etc/pam.d/')
|
os.system('mkdir -p tmpdeb/etc/pam.d/')
|
||||||
os.system('cp pam.d/rustdesk.debian tmpdeb/etc/pam.d/rustdesk')
|
os.system('cp pam.d/rustdesk.debian tmpdeb/etc/pam.d/rustdesk')
|
||||||
system2('strip tmpdeb/usr/bin/rustdesk')
|
system2('strip tmpdeb/usr/bin/rustdesk')
|
||||||
system2('mkdir -p tmpdeb/usr/lib/rustdesk')
|
system2('mkdir -p tmpdeb/usr/share/rustdesk')
|
||||||
system2('mv tmpdeb/usr/bin/rustdesk tmpdeb/usr/lib/rustdesk/')
|
system2('mv tmpdeb/usr/bin/rustdesk tmpdeb/usr/share/rustdesk/')
|
||||||
system2('cp libsciter-gtk.so tmpdeb/usr/lib/rustdesk/')
|
system2('cp libsciter-gtk.so tmpdeb/usr/share/rustdesk/')
|
||||||
md5_file('usr/share/rustdesk/files/systemd/rustdesk.service')
|
md5_file_folder("tmpdeb/")
|
||||||
md5_file('etc/rustdesk/startwm.sh')
|
|
||||||
md5_file('etc/X11/rustdesk/xorg.conf')
|
|
||||||
md5_file('etc/pam.d/rustdesk')
|
|
||||||
md5_file('usr/lib/rustdesk/libsciter-gtk.so')
|
|
||||||
system2('dpkg-deb -b tmpdeb rustdesk.deb; /bin/rm -rf tmpdeb/')
|
system2('dpkg-deb -b tmpdeb rustdesk.deb; /bin/rm -rf tmpdeb/')
|
||||||
os.rename('rustdesk.deb', 'rustdesk-%s.deb' % version)
|
os.rename('rustdesk.deb', 'rustdesk-%s.deb' % version)
|
||||||
|
|
||||||
|
|
||||||
def md5_file(fn):
|
def md5_file(fn):
|
||||||
md5 = hashlib.md5(open('tmpdeb/' + fn, 'rb').read()).hexdigest()
|
md5 = hashlib.md5(open('tmpdeb/' + fn, 'rb').read()).hexdigest()
|
||||||
system2('echo "%s %s" >> tmpdeb/DEBIAN/md5sums' % (md5, fn))
|
system2('echo "%s /%s" >> tmpdeb/DEBIAN/md5sums' % (md5, fn))
|
||||||
|
|
||||||
|
def md5_file_folder(base_dir):
|
||||||
|
base_path = Path(base_dir)
|
||||||
|
for file in base_path.rglob('*'):
|
||||||
|
if file.is_file() and 'DEBIAN' not in file.parts:
|
||||||
|
relative_path = file.relative_to(base_path)
|
||||||
|
md5_file(str(relative_path))
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
125
docs/CODE_OF_CONDUCT-NO.md
Normal file
125
docs/CODE_OF_CONDUCT-NO.md
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
|
||||||
|
# Atferdskodeks for bidragsyterpaktern
|
||||||
|
|
||||||
|
## Hva Vi Står For
|
||||||
|
|
||||||
|
Vi som medlemer, bidragere, og ledere står for å skape ett hat-fritt felleskap,
|
||||||
|
uansett alder, kroppstørrelse, synlig eller usynlige funksjonsnedsettninger,
|
||||||
|
etnesitet, kjønns karaktertrekk, kjønnsidentitet, kunnskapsnivå, utdanning,
|
||||||
|
sosial-økonomisk status, nasjonalitet, utsende, rase, religion, eller seksual
|
||||||
|
identitet og orientasjon.
|
||||||
|
|
||||||
|
Vi står for åpen, velkommende, mangfold, inklusiv og sunn oppførsel i vårt felleskap.
|
||||||
|
|
||||||
|
## Våre Standarer
|
||||||
|
|
||||||
|
Eksempler på oppførsel som hjelper ett positivt felleskap inkluderer:
|
||||||
|
|
||||||
|
* Vise empati og vennlighet mot andre mennesker
|
||||||
|
* Være respektfull ovenfor ulike meninger, synspunkter og erfaringer
|
||||||
|
* Gi og ta konstruktiv kritikk i beste mening
|
||||||
|
* Akseptere ansvar og unskylde seg for de som er utsatt av våre feil,
|
||||||
|
og lære av disse
|
||||||
|
* Fokusere på det som er best ikke bare for individer, men for felleskapet
|
||||||
|
|
||||||
|
Eksempler på uakseptabel oppførsel inkluderer:
|
||||||
|
|
||||||
|
* Bruk av seksualisert språk eller bilder, og seksual oppmerksomhet.
|
||||||
|
* Troll-ene, fornermende og nedsettende kommentarer, og personlig eller politiske angrep
|
||||||
|
* Offentlig eller privat trakassering
|
||||||
|
* Publisering av andres private informasjon, sånn som bosteds- og epost-addresser,
|
||||||
|
uten deres godskjenning.
|
||||||
|
* Andre rettningslinjer som kan bli sett på som upassende i en profesjonell setting.
|
||||||
|
|
||||||
|
## Håndhevingsansvar
|
||||||
|
|
||||||
|
Felleskapets ledere har ansvar for å klarifisere og håndheve våre standarer av
|
||||||
|
akseptert oppførsel og vill ta rimelige og rettferdige handliger som respons på
|
||||||
|
oppførsel de anser som upassende, truende, fornermende eller skadelig.
|
||||||
|
|
||||||
|
Felleskapets ledere har retten og ansvaret til å fjerne, redigere, eller avslå
|
||||||
|
kommentarer, commits, kode, wiki endringer, issues, og andre birag som ikke
|
||||||
|
samsvarer med disse etiske rettningslinjene, og vill kommunisere grunner for
|
||||||
|
moderatorenes valg når passende.
|
||||||
|
|
||||||
|
## Omfang
|
||||||
|
|
||||||
|
Disse etiske rettningslinjene gjelder innenfor alle platformene til felleskapet, og
|
||||||
|
de gjelder også når ett individ representerer felleskapet på offentlige medier.
|
||||||
|
Eksempler på representasjon av vårt felleskap inkluderer bruke av offisielle e-mail
|
||||||
|
addresser, publisering gjennom en offisiell sosial media bruker, eller oppførsel som en
|
||||||
|
utpekt representant på digitale og fysiske arrangsjemanger.
|
||||||
|
|
||||||
|
## Håndheving
|
||||||
|
|
||||||
|
Hendelser av misbruk, trakasserende eller på noen måte uakseptert oppførsel kann
|
||||||
|
bli raportert til felleskapets ledere med ansvar for håndheving på
|
||||||
|
[info@rustdesk.com](mailto:info@rustdesk.com).
|
||||||
|
All tilbakemelding vill bli sett gjennom og investigert rettferdig så fort som mulig.
|
||||||
|
|
||||||
|
Alle felleskapets ledere er obligert til å respektere privatlivet og sikkerhetet ovenfor
|
||||||
|
den som raporterer en hendelse.
|
||||||
|
|
||||||
|
## Håndhevings Guide
|
||||||
|
|
||||||
|
Felleskapets ledere vill følge disse Rettningslinjene for sammfunspåvirkning med
|
||||||
|
tanke på konsekvenser for en handling de anser i brudd med disse etiske rettningslinjene:
|
||||||
|
|
||||||
|
### 1. Korreksjon
|
||||||
|
|
||||||
|
**Sammfunspåvirkning**: Bruk av upassende språk eller annen oppførsel ansett som
|
||||||
|
uprofesjonelt eller uvelkommen i dette felleskapet.
|
||||||
|
|
||||||
|
**Konsekvens**: En privat, skrevet advarsel fra en leder av felleskapet, som
|
||||||
|
klarifiserer grunnlaget til hvorfor denne oppførselen var upassende. En offentlig
|
||||||
|
unskyldning kan bli forespurt.
|
||||||
|
|
||||||
|
### 2. Advarsel
|
||||||
|
|
||||||
|
**Sammfunspåvirkning**: Ett brudd på en singulær hendelse eller en serie handlinger.
|
||||||
|
|
||||||
|
**Konsekvens**: En advarsel med konsekvenser for kontinuerende oppførsel. Ingen
|
||||||
|
interaksjon med individene involvert, inkluderer uoppfordret interaksjoner med
|
||||||
|
de som håndhever disse etiske rettningslinjene, er tillat for en spesifisert tidsperiode.
|
||||||
|
Dette inkluderer å unngå interaksjoner i felleskapets platformer, samt eksterne
|
||||||
|
kanaler, som f.eks sosial media. Brudd av disse vilkårene kan føre til midlertidig
|
||||||
|
eller permanent bannlysning.
|
||||||
|
|
||||||
|
### 3. Midlertidig Bannlysning
|
||||||
|
|
||||||
|
**Sammfunspåvirkning**: Ett særiøst brudd på felleskapets standarer, inkludert
|
||||||
|
vedvarende upassende oppførsel.
|
||||||
|
|
||||||
|
**Konsekvens**: En midlertidig bannlysning fra noen som helst interaksjon eller
|
||||||
|
offentlig kommunikasjon med felleskapet for en spesifisert tidsperiode. Ingen
|
||||||
|
interaksjon med individene involvert, inkluderer uoppfordret interaksjoner med
|
||||||
|
de som håndhever disse etiske rettningslinjene, er tillat for denne perioden.
|
||||||
|
Brudd på disse vilkårene kan føre til permanent bannlysning.
|
||||||
|
|
||||||
|
### 4. Permanent Bannlysning
|
||||||
|
|
||||||
|
**Sammfunspåvirkning**: Demonstasjon av mønster i brudd på felleskapets standarer,
|
||||||
|
inklusivt vedvarende upassende oppførsel, trakassering av ett individ, eller
|
||||||
|
aggresjon mot eller nedsettelse av grupper individer.
|
||||||
|
|
||||||
|
**Konsekvens**: En permanent bannlysning fra alle offentlige interaksjoner i
|
||||||
|
felleskapet
|
||||||
|
|
||||||
|
## Attribusjon
|
||||||
|
|
||||||
|
Disse etiske rettningslinjene er adaptert fra [Contributor Covenant][homepage],
|
||||||
|
versjon 2.0, tilgjengelig ved
|
||||||
|
[https://www.contributor-covenant.org/version/2/0/code_of_conduct.html][v2.0].
|
||||||
|
|
||||||
|
Sammfunspåvirknings guid inspirert av
|
||||||
|
[Mozilla's code of conduct enforcement ladder][Mozilla CoC].
|
||||||
|
|
||||||
|
For svar til vanlige spørsmål angående disse etiske rettningslinjene, se FAQ på
|
||||||
|
[https://www.contributor-covenant.org/faq][FAQ]. Oversettelse tilgjengelig
|
||||||
|
ved [https://www.contributor-covenant.org/translations][translations].
|
||||||
|
|
||||||
|
[homepage]: https://www.contributor-covenant.org
|
||||||
|
[v2.0]: https://www.contributor-covenant.org/version/2/0/code_of_conduct.html
|
||||||
|
[Mozilla CoC]: https://github.com/mozilla/diversity
|
||||||
|
[FAQ]: https://www.contributor-covenant.org/faq
|
||||||
|
[translations]: https://www.contributor-covenant.org/translations
|
||||||
46
docs/CONTRIBUTING-NO.md
Normal file
46
docs/CONTRIBUTING-NO.md
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
# Bidrag til RustDesk
|
||||||
|
|
||||||
|
RustDesk er åpene for bidrag fra alle. Her er reglene for de som har lyst til å
|
||||||
|
hjelpe oss:
|
||||||
|
|
||||||
|
## Bidrag
|
||||||
|
|
||||||
|
Bidrag til RustDesk eller deres avhengigheter burde være i form av GitHub pull requests.
|
||||||
|
Hver pull request vill bli sett igjennom av en kjerne bidrager (noen med autoritet til
|
||||||
|
å godkjenne endringene) og enten bli sendt til main treet eller respondert med
|
||||||
|
tilbakemelding på endringer som er nødvendig. Alle bidrag burde følge dette formate
|
||||||
|
også de fra kjerne bidragere.
|
||||||
|
|
||||||
|
Om du ønsker å jobbe på en issue må du huske å gjøre krav på den først. Dette
|
||||||
|
kann gjøres ved å kommentere på den GitHub issue-en du ønsker å jobbe på.
|
||||||
|
Dette er for å hindre duplikat innsats på samme problem.
|
||||||
|
|
||||||
|
## Pull Request Sjekkliste
|
||||||
|
|
||||||
|
- Lag en gren fra master grenen og, hvis det er nødvendig, rebase den til den nåværende
|
||||||
|
master grenen før du sender inn din pull request. Hvis ikke dette gjøres på rent
|
||||||
|
vis vill du bli spurt om å rebase dine endringer.
|
||||||
|
|
||||||
|
- Commits burde være så små som mulig, samtidig som de må være korrekt uavhenging av hverandre
|
||||||
|
(hver commit burde kompilere og bestå tester).
|
||||||
|
|
||||||
|
- Commits burde være akkopaniert med en Developer Certificate of Origin
|
||||||
|
(http://developercertificate.org), som indikerer att du (og din arbeidsgiver
|
||||||
|
i det tilfellet) godkjenner å bli knyttet til vilkårene av [prosjekt lisensen](../LICENCE).
|
||||||
|
Ved bruk av git er dette `-s` opsjonen til `git commit`.
|
||||||
|
|
||||||
|
- Hvis dine endringer ikke blir sett eller hvis du trenger en spesefik person til
|
||||||
|
å se på dem kan du @-svare en med autoritet til å godkjenne dine endringer.
|
||||||
|
Dette kann gjøres i en pull request, en kommentar eller via epost på [email](mailto:info@rustdesk.com).
|
||||||
|
|
||||||
|
- Legg til tester relevant til en fikset bug eller en ny tilgjengelighet.
|
||||||
|
|
||||||
|
For spesefike git instruksjoner, se [GitHub workflow 101](https://github.com/servo/servo/wiki/GitHub-workflow).
|
||||||
|
|
||||||
|
## Oppførsel
|
||||||
|
|
||||||
|
https://github.com/rustdesk/rustdesk/blob/master/docs/CODE_OF_CONDUCT.md
|
||||||
|
|
||||||
|
## Kommunikasjon
|
||||||
|
|
||||||
|
RustDesk bidragere burker [Discord](https://discord.gg/nDceKgxnkV).
|
||||||
14
docs/DEVCONTAINER-NO.md
Normal file
14
docs/DEVCONTAINER-NO.md
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
|
||||||
|
Etter start av devcontainer i docker konteineren, blir en linux binærfil i debug modus laget.
|
||||||
|
|
||||||
|
Nå tilbyr devcontainer linux og android builds i både debug og release modus.
|
||||||
|
|
||||||
|
Under er tabellen over kommandoer som kan kjøres fra rot-direktive for kreasjon av spesefike builds.
|
||||||
|
|
||||||
|
Kommando|Build Type|Modus
|
||||||
|
-|-|-|
|
||||||
|
`.devcontainer/build.sh --debug linux`|Linux|debug
|
||||||
|
`.devcontainer/build.sh --release linux`|Linux|release
|
||||||
|
`.devcontainer/build.sh --debug android`|android-arm64|debug
|
||||||
|
`.devcontainer/build.sh --release android`|android-arm64|release
|
||||||
|
|
||||||
@@ -147,6 +147,10 @@ target/release/rustdesk
|
|||||||
|
|
||||||
Bitte stellen Sie sicher, dass Sie diese Befehle im Stammverzeichnis des RustDesk-Repositorys nutzen. Ansonsten kann es passieren, dass das Programm die Ressourcen nicht finden kann. Bitte bedenken Sie auch, dass andere Cargo-Unterbefehle wie `install` oder `run` aktuell noch nicht unterstützt werden, da sie das Programm innerhalb des Containers starten oder installieren würden, anstatt auf Ihrem eigentlichen System.
|
Bitte stellen Sie sicher, dass Sie diese Befehle im Stammverzeichnis des RustDesk-Repositorys nutzen. Ansonsten kann es passieren, dass das Programm die Ressourcen nicht finden kann. Bitte bedenken Sie auch, dass andere Cargo-Unterbefehle wie `install` oder `run` aktuell noch nicht unterstützt werden, da sie das Programm innerhalb des Containers starten oder installieren würden, anstatt auf Ihrem eigentlichen System.
|
||||||
|
|
||||||
|
> [!Vorsicht]
|
||||||
|
> **Haftungsausschluss bei Missbrauch::** <br>
|
||||||
|
> Die Entwickler von RustDesk billigen oder unterstützen keine unethische oder illegale Nutzung dieser Software. Missbrauch, wie unbefugter Zugriff, unbefugte Kontrolle oder Verletzung der Privatsphäre, verstößt strikt gegen unsere Richtlinien. Die Autoren sind nicht verantwortlich für jeglichen Missbrauch der Anwendung.
|
||||||
|
|
||||||
## Dateistruktur
|
## Dateistruktur
|
||||||
|
|
||||||
- **[libs/hbb_common](https://github.com/rustdesk/rustdesk/tree/master/libs/hbb_common)**: Video-Codec, Konfiguration, TCP/UDP-Wrapper, Protokoll-Puffer, fs-Funktionen für Dateitransfer und ein paar andere nützliche Funktionen
|
- **[libs/hbb_common](https://github.com/rustdesk/rustdesk/tree/master/libs/hbb_common)**: Video-Codec, Konfiguration, TCP/UDP-Wrapper, Protokoll-Puffer, fs-Funktionen für Dateitransfer und ein paar andere nützliche Funktionen
|
||||||
|
|||||||
@@ -147,6 +147,10 @@ Por favor, asegurate de que estás ejecutando estos comandos desde la raíz del
|
|||||||
- **[flutter](https://github.com/rustdesk/rustdesk/tree/master/flutter)**: Flutter, código para moviles
|
- **[flutter](https://github.com/rustdesk/rustdesk/tree/master/flutter)**: Flutter, código para moviles
|
||||||
- **[flutter/web/js](https://github.com/rustdesk/rustdesk/tree/master/flutter/web/js)**: Javascript para el cliente web Flutter
|
- **[flutter/web/js](https://github.com/rustdesk/rustdesk/tree/master/flutter/web/js)**: Javascript para el cliente web Flutter
|
||||||
|
|
||||||
|
> [!Precaución]
|
||||||
|
> **Descargo de responsabilidad por uso indebido:** <br>
|
||||||
|
> Los desarrolladores de RustDesk no aprueban ni apoyan ningún uso no ético o ilegal de este software. El uso indebido, como el acceso no autorizado, el control o la invasión de la privacidad, está estrictamente en contra de nuestras directrices. Los autores no son responsables de ningún uso indebido de la aplicación.
|
||||||
|
|
||||||
## Capturas de pantalla
|
## Capturas de pantalla
|
||||||
|
|
||||||

|

|
||||||
|
|||||||
@@ -137,6 +137,10 @@ Veuillez vous assurer que vous exécutez ces commandes à partir de la racine du
|
|||||||
- **[src/rendezvous_mediator.rs](https://github.com/rustdesk/rustdesk/tree/master/src/rendezvous_mediator.rs)** : Communiquer avec [rustdesk-server](https://github.com/rustdesk/rustdesk-server), attendre une connexion distante directe (TCP hole punching) ou relayée.
|
- **[src/rendezvous_mediator.rs](https://github.com/rustdesk/rustdesk/tree/master/src/rendezvous_mediator.rs)** : Communiquer avec [rustdesk-server](https://github.com/rustdesk/rustdesk-server), attendre une connexion distante directe (TCP hole punching) ou relayée.
|
||||||
- **[src/platform](https://github.com/rustdesk/rustdesk/tree/master/src/platform)** : code spécifique à la plateforme
|
- **[src/platform](https://github.com/rustdesk/rustdesk/tree/master/src/platform)** : code spécifique à la plateforme
|
||||||
|
|
||||||
|
> [!Attention]
|
||||||
|
> **Avertissement contre l'utilisation abusive:** <br>
|
||||||
|
> Les développeurs de RustDesk ne cautionnent ni ne soutiennent aucune utilisation non éthique ou illégale de ce logiciel. Toute utilisation abusive, telle que l'accès non autorisé, le contrôle ou l'invasion de la vie privée, est strictement contraire à nos directives. Les auteurs ne sont pas responsables de toute utilisation abusive de l'application.
|
||||||
|
|
||||||
## Images
|
## Images
|
||||||
|
|
||||||

|

|
||||||
|
|||||||
@@ -164,6 +164,10 @@ Assicurati di eseguire questi comandi dalla radice del repository RustDesk, altr
|
|||||||
- **[flutter](https://github.com/rustdesk/rustdesk/tree/master/flutter)**: codice Flutter per desktop e mobile
|
- **[flutter](https://github.com/rustdesk/rustdesk/tree/master/flutter)**: codice Flutter per desktop e mobile
|
||||||
- **[flutter/web/js](https://github.com/rustdesk/rustdesk/tree/master/flutter/web/js)**: JavaScript per client web Flutter
|
- **[flutter/web/js](https://github.com/rustdesk/rustdesk/tree/master/flutter/web/js)**: JavaScript per client web Flutter
|
||||||
|
|
||||||
|
> [!Attenzione]
|
||||||
|
> **Dichiarazione di non responsabilità per uso improprio:** <br>
|
||||||
|
> Gli sviluppatori di RustDesk non approvano né supportano alcun uso non etico o illegale di questo software. L'uso improprio, come l'accesso non autorizzato, il controllo o l'invasione della privacy, è strettamente contro le nostre linee guida. Gli autori non sono responsabili per qualsiasi uso improprio dell'applicazione.
|
||||||
|
|
||||||
## Schermate
|
## Schermate
|
||||||
|
|
||||||

|

|
||||||
|
|||||||
@@ -168,6 +168,10 @@ target/release/rustdesk
|
|||||||
- **[flutter](https://github.com/rustdesk/rustdesk/tree/master/flutter)**: デスクトップとモバイル向けのFlutterコード
|
- **[flutter](https://github.com/rustdesk/rustdesk/tree/master/flutter)**: デスクトップとモバイル向けのFlutterコード
|
||||||
- **[flutter/web/js](https://github.com/rustdesk/rustdesk/tree/master/flutter/web/js)**: Flutterウェブクライアント向けのJavaScript
|
- **[flutter/web/js](https://github.com/rustdesk/rustdesk/tree/master/flutter/web/js)**: Flutterウェブクライアント向けのJavaScript
|
||||||
|
|
||||||
|
> [!注意]
|
||||||
|
> **:不正使用に関する免責事項** <br>
|
||||||
|
> RustDeskの開発者は、このソフトウェアの非倫理的または違法な使用を容認または支持しません。不正アクセス、不正な制御、またはプライバシーの侵害などの不正使用は、当社のガイドラインに厳密に違反します。開発者は、アプリケーションの不正使用に対して一切の責任を負いません。
|
||||||
|
|
||||||
## スクリーンショット
|
## スクリーンショット
|
||||||
|
|
||||||

|

|
||||||
|
|||||||
@@ -9,12 +9,12 @@
|
|||||||
<b>README를 모국어로 번역하기 위한 당신의 도움의 필요합니다.</b>
|
<b>README를 모국어로 번역하기 위한 당신의 도움의 필요합니다.</b>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
Chat with us: [Discord](https://discord.gg/nDceKgxnkV) | [Twitter](https://twitter.com/rustdesk) | [Reddit](https://www.reddit.com/r/rustdesk)
|
채팅하기: [Discord](https://discord.gg/nDceKgxnkV) | [Twitter](https://twitter.com/rustdesk) | [Reddit](https://www.reddit.com/r/rustdesk)
|
||||||
|
|
||||||
|
|
||||||
[](https://ko-fi.com/I2I04VU09)
|
[](https://ko-fi.com/I2I04VU09)
|
||||||
|
|
||||||
Rust로 작성되었고, 설정없이 바로 사용할 수 있는 원격 데스트탑 소프트웨어입니다. 자신의 데이터를 완전히 컨트롤할 수 있고, 보안의 염려도 없습니다. 우리의 rendezvous/relay 서버를 사용해도, [스스로 설정](https://rustdesk.com/server)하는 것도, [스스로 rendezvous/relay 서버를 작성할 수도 있습니다](https://github.com/rustdesk/rustdesk-server-demo).
|
Rust로 작성되었고, 설정없이 바로 사용할 수 있는 원격 데스트탑 소프트웨어입니다. 자신의 데이터를 완전히 컨트롤할 수 있고, 보안의 염려도 없습니다. 우리의 rendezvous/relay 서버를 사용해도, [직접 설정](https://rustdesk.com/server)하거나 [직접 rendezvous/relay 서버를 작성할 수도 있습니다](https://github.com/rustdesk/rustdesk-server-demo).
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
@@ -43,9 +43,9 @@ RustDesk는 모든 기여를 환영합니다. 기여하고자 한다면 [`docs/C
|
|||||||
- Windows: vcpkg install libvpx:x64-windows-static libyuv:x64-windows-static opus:x64-windows-static aom:x64-windows-static
|
- Windows: vcpkg install libvpx:x64-windows-static libyuv:x64-windows-static opus:x64-windows-static aom:x64-windows-static
|
||||||
- Linux/MacOS: vcpkg install libvpx libyuv opus aom
|
- Linux/MacOS: vcpkg install libvpx libyuv opus aom
|
||||||
|
|
||||||
- run `cargo run`
|
- 실행 `cargo run`
|
||||||
|
|
||||||
## [Build](https://rustdesk.com/docs/en/dev/build/)
|
## [빌드](https://rustdesk.com/docs/en/dev/build/)
|
||||||
|
|
||||||
## Linux에서 빌드 순서
|
## Linux에서 빌드 순서
|
||||||
|
|
||||||
@@ -67,7 +67,7 @@ sudo yum -y install gcc-c++ git curl wget nasm yasm gcc gtk3-devel clang libxcb-
|
|||||||
sudo pacman -Syu --needed unzip git cmake gcc curl wget yasm nasm zip make pkg-config clang gtk3 xdotool libxcb libxfixes alsa-lib pipewire
|
sudo pacman -Syu --needed unzip git cmake gcc curl wget yasm nasm zip make pkg-config clang gtk3 xdotool libxcb libxfixes alsa-lib pipewire
|
||||||
```
|
```
|
||||||
|
|
||||||
### Install vcpkg
|
### vcpkg 설치
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
git clone https://github.com/microsoft/vcpkg
|
git clone https://github.com/microsoft/vcpkg
|
||||||
@@ -79,7 +79,7 @@ export VCPKG_ROOT=$HOME/vcpkg
|
|||||||
vcpkg/vcpkg install libvpx libyuv opus aom
|
vcpkg/vcpkg install libvpx libyuv opus aom
|
||||||
```
|
```
|
||||||
|
|
||||||
### Fix libvpx (For Fedora)
|
### libvpx 수정 (For Fedora용)
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
cd vcpkg/buildtrees/libvpx/src
|
cd vcpkg/buildtrees/libvpx/src
|
||||||
@@ -92,7 +92,7 @@ cp libvpx.a $HOME/vcpkg/installed/x64-linux/lib/
|
|||||||
cd
|
cd
|
||||||
```
|
```
|
||||||
|
|
||||||
### Build
|
### 빌드
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
|
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
|
||||||
@@ -107,7 +107,7 @@ VCPKG_ROOT=$HOME/vcpkg cargo run
|
|||||||
|
|
||||||
## Docker에 빌드하는 방법
|
## Docker에 빌드하는 방법
|
||||||
|
|
||||||
레포지토리를 클론하고, Docker 컨테이너 구성하는 것으로 시작합니다.
|
리포지토리를 클론하고, Docker 컨테이너 구성하는 것으로 시작합니다.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
git clone https://github.com/rustdesk/rustdesk
|
git clone https://github.com/rustdesk/rustdesk
|
||||||
@@ -115,13 +115,13 @@ cd rustdesk
|
|||||||
docker build -t "rustdesk-builder" .
|
docker build -t "rustdesk-builder" .
|
||||||
```
|
```
|
||||||
|
|
||||||
이후, 애플리케이션을 빌드할 필요가 있을 때마다, 이하의 커맨드를 실행합니다.
|
이후, 애플리케이션을 빌드할 필요가 있을 때마다, 아래의의 명령을 실행합니다.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
docker run --rm -it -v $PWD:/home/user/rustdesk -v rustdesk-git-cache:/home/user/.cargo/git -v rustdesk-registry-cache:/home/user/.cargo/registry -e PUID="$(id -u)" -e PGID="$(id -g)" rustdesk-builder
|
docker run --rm -it -v $PWD:/home/user/rustdesk -v rustdesk-git-cache:/home/user/.cargo/git -v rustdesk-registry-cache:/home/user/.cargo/registry -e PUID="$(id -u)" -e PGID="$(id -g)" rustdesk-builder
|
||||||
```
|
```
|
||||||
|
|
||||||
첫 빌드에서는 의존관계가 캐시될 때까지 시간이 걸릴 수 있습니다만, 이후의 빌드때는 빨라집니다. 더불어 빌드 커맨드에 다른 인수를 지정할 필요가 있다면, 커맨드 끝에 있는 `<OPTIONAL-ARGS>` 에 지정할 수 있습니다. 예를 들어 최적화된 출시 버전을 빌드하고 싶다면 이렇게 상기한 커맨드 뒤에 `--release` 를 붙여 실행합니다. 성공했다면 실행파일은 시스템 타겟 폴더에 담겨지고, 다음 커맨드로 실행할 수 있습니다.
|
첫 빌드에서는 의존관계가 캐시될 때까지 시간이 걸릴 수 있습니다만, 이후의 빌드때는 빨라집니다. 더불어 빌드 명령에 다른 인수를 지정할 필요가 있다면, 명령 끝에 있는 `<OPTIONAL-ARGS>` 에 지정할 수 있습니다. 예를 들어 최적화된 출시 버전을 빌드하고 싶다면 이렇게 상기한 명령 뒤에 `--release` 를 붙여 실행합니다. 성공했다면 실행파일은 시스템 타겟 폴더에 담겨지고, 다음 명령으로 실행할 수 있습니다.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
target/debug/rustdesk
|
target/debug/rustdesk
|
||||||
@@ -133,9 +133,9 @@ target/debug/rustdesk
|
|||||||
target/release/rustdesk
|
target/release/rustdesk
|
||||||
```
|
```
|
||||||
|
|
||||||
커맨드를 RustDesk 리포지토리 루트에서 실행한다는 것을 확인해주세요. 그렇게 하지 않으면 애플리케이션이 필요한 리소스를 발견하지 못 할 가능성이 있습니다. 또한 `install`, `run` 같은 cargo 서브커맨드는 호스트가 아니라 컨테이너 프로그램을 설치, 실행을 위함이므로 현재 이 방법은 지원하지 않다는 점을 유념해주시길 바랍니다.
|
명령을 RustDesk 리포지토리 루트에서 실행한다는 것을 확인해주세요. 그렇게 하지 않으면 애플리케이션이 필요한 리소스를 발견하지 못 할 가능성이 있습니다. 또한 `install`, `run` 같은 cargo 하위 명령은 호스트가 아니라 컨테이너 프로그램을 설치, 실행을 위함이므로 현재 이 방법은 지원하지 않다는 점을 유념해주시길 바랍니다.
|
||||||
|
|
||||||
## File Structure
|
## 파일 구조
|
||||||
|
|
||||||
- **[libs/hbb_common](https://github.com/rustdesk/rustdesk/tree/master/libs/hbb_common)**: 비디오 코덱, 설정, tcp/udp 랩퍼, protobuf, 파일 전송을 위한 fs 함수, 그 외 유틸리티 함수
|
- **[libs/hbb_common](https://github.com/rustdesk/rustdesk/tree/master/libs/hbb_common)**: 비디오 코덱, 설정, tcp/udp 랩퍼, protobuf, 파일 전송을 위한 fs 함수, 그 외 유틸리티 함수
|
||||||
- **[libs/scrap](https://github.com/rustdesk/rustdesk/tree/master/libs/scrap)**: 화면 캡처
|
- **[libs/scrap](https://github.com/rustdesk/rustdesk/tree/master/libs/scrap)**: 화면 캡처
|
||||||
@@ -143,12 +143,16 @@ target/release/rustdesk
|
|||||||
- **[src/ui](https://github.com/rustdesk/rustdesk/tree/master/src/ui)**: GUI
|
- **[src/ui](https://github.com/rustdesk/rustdesk/tree/master/src/ui)**: GUI
|
||||||
- **[src/server](https://github.com/rustdesk/rustdesk/tree/master/src/server)**: 오디오, 클립보드, 입력, 비디오 서비스 그리고 네트워크 연결
|
- **[src/server](https://github.com/rustdesk/rustdesk/tree/master/src/server)**: 오디오, 클립보드, 입력, 비디오 서비스 그리고 네트워크 연결
|
||||||
- **[src/client.rs](https://github.com/rustdesk/rustdesk/tree/master/src/client.rs)**: 피어 접속 시작
|
- **[src/client.rs](https://github.com/rustdesk/rustdesk/tree/master/src/client.rs)**: 피어 접속 시작
|
||||||
- **[src/rendezvous_mediator.rs](https://github.com/rustdesk/rustdesk/tree/master/src/rendezvous_mediator.rs)**: [rustdesk-server](https://github.com/rustdesk/rustdesk-server)와 통신해서 리모트 다이렉트(TCP hole punching) 혹은 relayed 접속
|
- **[src/rendezvous_mediator.rs](https://github.com/rustdesk/rustdesk/tree/master/src/rendezvous_mediator.rs)**: [rustdesk-server](https://github.com/rustdesk/rustdesk-server)와 통신해서 리모트 다이렉트 (TCP hole punching) 혹은 relayed 접속
|
||||||
- **[src/platform](https://github.com/rustdesk/rustdesk/tree/master/src/platform)**: 플랫폼 고유의 코드
|
- **[src/platform](https://github.com/rustdesk/rustdesk/tree/master/src/platform)**: 플랫폼 고유의 코드
|
||||||
- **[flutter](https://github.com/rustdesk/rustdesk/tree/master/flutter)**: Flutter code for mobile
|
- **[flutter](https://github.com/rustdesk/rustdesk/tree/master/flutter)**: 모바일용 Flutter 코드
|
||||||
- **[flutter/web/js](https://github.com/rustdesk/rustdesk/tree/master/flutter/web/js)**: Javascript for Flutter web client
|
- **[flutter/web/js](https://github.com/rustdesk/rustdesk/tree/master/flutter/web/js)**: Flutter 웹 클라이언트용 자바스크립트
|
||||||
|
|
||||||
## Snapshot
|
> [!주의]
|
||||||
|
> **오용에 대한 면책 조항:** <br>
|
||||||
|
> RustDesk의 개발자들은 이 소프트웨어의 비윤리적이거나 불법적인 사용을 용인하거나 지원하지 않습니다. 무단 접근, 제어 또는 개인정보 침해와 같은 오용은 우리의 지침을 엄격히 위반하는 것입니다. 개발자들은 애플리케이션의 오용에 대해 책임을 지지 않습니다.
|
||||||
|
|
||||||
|
## 스냅샷
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
|||||||
177
docs/README-NO.md
Normal file
177
docs/README-NO.md
Normal file
@@ -0,0 +1,177 @@
|
|||||||
|
<p align="center">
|
||||||
|
<img src="res/logo-header.svg" alt="RustDesk - Your remote desktop"><br>
|
||||||
|
<a href="#public-servers">Servere</a> •
|
||||||
|
<a href="#raw-steps-to-build">Build</a> •
|
||||||
|
<a href="#how-to-build-with-docker">Docker</a> •
|
||||||
|
<a href="#file-structure">Struktur</a> •
|
||||||
|
<a href="#snapshot">Snapshot</a><br>
|
||||||
|
[<a href="docs/README-UA.md">Українська</a>] | [<a href="docs/README-CS.md">česky</a>] | [<a href="docs/README-ZH.md">中文</a>] | [<a href="docs/README-HU.md">Magyar</a>] | [<a href="docs/README-ES.md">Español</a>] | [<a href="docs/README-FA.md">فارسی</a>] | [<a href="docs/README-FR.md">Français</a>] | [<a href="docs/README-DE.md">Deutsch</a>] | [<a href="docs/README-PL.md">Polski</a>] | [<a href="docs/README-ID.md">Indonesian</a>] | [<a href="docs/README-FI.md">Suomi</a>] | [<a href="docs/README-ML.md">മലയാളം</a>] | [<a href="docs/README-JP.md">日本語</a>] | [<a href="docs/README-NL.md">Nederlands</a>] | [<a href="docs/README-IT.md">Italiano</a>] | [<a href="docs/README-RU.md">Русский</a>] | [<a href="docs/README-PTBR.md">Português (Brasil)</a>] | [<a href="docs/README-EO.md">Esperanto</a>] | [<a href="docs/README-KR.md">한국어</a>] | [<a href="docs/README-AR.md">العربي</a>] | [<a href="docs/README-VN.md">Tiếng Việt</a>] | [<a href="docs/README-DA.md">Dansk</a>] | [<a href="docs/README-GR.md">Ελληνικά</a>] | [<a href="docs/README-TR.md">Türkçe</a>] | [<a href="docs/README-NO.md">Norsk</a><br>
|
||||||
|
<b>Vi trenger din hjelp til å oversette denne README-en, <a href="https://github.com/rustdesk/rustdesk/tree/master/src/lang">RustDesk UI</a> og <a href="https://github.com/rustdesk/doc.rustdesk.com">RustDesk Doc</a> tid ditt morsmål</b>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
Snakk med oss: [Discord](https://discord.gg/nDceKgxnkV) | [Twitter](https://twitter.com/rustdesk) | [Reddit](https://www.reddit.com/r/rustdesk)
|
||||||
|
|
||||||
|
[](https://ko-fi.com/I2I04VU09)
|
||||||
|
|
||||||
|
Enda en annen fjernstyrt desktop programvare, skrevet i Rust. Virker rett ut av pakken, ingen konfigurasjon nødvendig. Du har full kontroll over din data, uten beskymring for sikkerhet. Du kan bruke vår rendezvous_mediator/relay server, [sett opp din egen](https://rustdesk.com/server), eller [skriv din egen rendezvous_mediator/relay server](https://github.com/rustdesk/rustdesk-server-demo).
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
RustDesk er velkommen for bidrag fra alle. Se [CONTRIBUTING.md](docs/CONTRIBUTING-NO.md) for hjelp med oppstart.
|
||||||
|
|
||||||
|
[**FAQ**](https://github.com/rustdesk/rustdesk/wiki/FAQ)
|
||||||
|
|
||||||
|
[**BINARY NEDLASTING**](https://github.com/rustdesk/rustdesk/releases)
|
||||||
|
|
||||||
|
[**NIGHTLY BUILD**](https://github.com/rustdesk/rustdesk/releases/tag/nightly)
|
||||||
|
|
||||||
|
[<img src="https://f-droid.org/badge/get-it-on.png"
|
||||||
|
alt="Få det på F-Droid"
|
||||||
|
height="80">](https://f-droid.org/en/packages/com.carriez.flutter_hbb)
|
||||||
|
[<img src="https://flathub.org/api/badge?svg&locale=en"
|
||||||
|
alt="Få det på Flathub"
|
||||||
|
height="80">](https://flathub.org/apps/com.rustdesk.RustDesk)
|
||||||
|
|
||||||
|
## Avhengigheter
|
||||||
|
|
||||||
|
Desktop versjoner bruker Flutter eller Sciter (avviklet) for GUI, denne veiledningen er bare for Sciter, grunnet att det er letter og en mer venlig start. Skjekk ut vår [CI](https://github.com/rustdesk/rustdesk/blob/master/.github/workflows/flutter-build.yml) for bygging av Flutter versjonen.
|
||||||
|
|
||||||
|
Venligst last ned Sciters dynamiske bibliotek selv.
|
||||||
|
|
||||||
|
[Windows](https://raw.githubusercontent.com/c-smile/sciter-sdk/master/bin.win/x64/sciter.dll) |
|
||||||
|
[Linux](https://raw.githubusercontent.com/c-smile/sciter-sdk/master/bin.lnx/x64/libsciter-gtk.so) |
|
||||||
|
[macOS](https://raw.githubusercontent.com/c-smile/sciter-sdk/master/bin.osx/libsciter.dylib)
|
||||||
|
|
||||||
|
## Rå steg for bygging
|
||||||
|
|
||||||
|
- Klargjør ditt Rust development env og C++ build env
|
||||||
|
|
||||||
|
- Installer [vcpkg](https://github.com/microsoft/vcpkg), og koriger `VCPKG_ROOT` env vaiabelen
|
||||||
|
|
||||||
|
- Windows: vcpkg install libvpx:x64-windows-static libyuv:x64-windows-static opus:x64-windows-static aom:x64-windows-static
|
||||||
|
- Linux/macOS: vcpkg install libvpx libyuv opus aom
|
||||||
|
|
||||||
|
- Kjør `cargo run`
|
||||||
|
|
||||||
|
## [Bygg](https://rustdesk.com/docs/en/dev/build/)
|
||||||
|
|
||||||
|
## Hvordan Bygge til Linux
|
||||||
|
|
||||||
|
### Ubuntu 18 (Debian 10)
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sudo apt install -y zip g++ gcc git curl wget nasm yasm libgtk-3-dev clang libxcb-randr0-dev libxdo-dev \
|
||||||
|
libxfixes-dev libxcb-shape0-dev libxcb-xfixes0-dev libasound2-dev libpulse-dev cmake make \
|
||||||
|
libclang-dev ninja-build libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev libpam0g-dev
|
||||||
|
```
|
||||||
|
|
||||||
|
### openSUSE Tumbleweed
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sudo zypper install gcc-c++ git curl wget nasm yasm gcc gtk3-devel clang libxcb-devel libXfixes-devel cmake alsa-lib-devel gstreamer-devel gstreamer-plugins-base-devel xdotool-devel pam-devel
|
||||||
|
```
|
||||||
|
|
||||||
|
### Fedora 28 (CentOS 8)
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sudo yum -y install gcc-c++ git curl wget nasm yasm gcc gtk3-devel clang libxcb-devel libxdo-devel libXfixes-devel pulseaudio-libs-devel cmake alsa-lib-devel gstreamer1-devel gstreamer1-plugins-base-devel pam-devel
|
||||||
|
```
|
||||||
|
|
||||||
|
### Arch (Manjaro)
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sudo pacman -Syu --needed unzip git cmake gcc curl wget yasm nasm zip make pkg-config clang gtk3 xdotool libxcb libxfixes alsa-lib pipewire
|
||||||
|
```
|
||||||
|
|
||||||
|
### Installer vcpkg
|
||||||
|
|
||||||
|
```sh
|
||||||
|
git clone https://github.com/microsoft/vcpkg
|
||||||
|
cd vcpkg
|
||||||
|
git checkout 2023.04.15
|
||||||
|
cd ..
|
||||||
|
vcpkg/bootstrap-vcpkg.sh
|
||||||
|
export VCPKG_ROOT=$HOME/vcpkg
|
||||||
|
vcpkg/vcpkg install libvpx libyuv opus aom
|
||||||
|
```
|
||||||
|
|
||||||
|
### Fiks libvpx (For Fedora)
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cd vcpkg/buildtrees/libvpx/src
|
||||||
|
cd *
|
||||||
|
./configure
|
||||||
|
sed -i 's/CFLAGS+=-I/CFLAGS+=-fPIC -I/g' Makefile
|
||||||
|
sed -i 's/CXXFLAGS+=-I/CXXFLAGS+=-fPIC -I/g' Makefile
|
||||||
|
make
|
||||||
|
cp libvpx.a $HOME/vcpkg/installed/x64-linux/lib/
|
||||||
|
cd
|
||||||
|
```
|
||||||
|
|
||||||
|
### Bygg
|
||||||
|
|
||||||
|
```sh
|
||||||
|
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
|
||||||
|
source $HOME/.cargo/env
|
||||||
|
git clone https://github.com/rustdesk/rustdesk
|
||||||
|
cd rustdesk
|
||||||
|
mkdir -p target/debug
|
||||||
|
wget https://raw.githubusercontent.com/c-smile/sciter-sdk/master/bin.lnx/x64/libsciter-gtk.so
|
||||||
|
mv libsciter-gtk.so target/debug
|
||||||
|
VCPKG_ROOT=$HOME/vcpkg cargo run
|
||||||
|
```
|
||||||
|
|
||||||
|
## Hvordan bygge med Docker
|
||||||
|
|
||||||
|
Start med å klone repositoret og bygg Docker konteineren:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
git clone https://github.com/rustdesk/rustdesk
|
||||||
|
cd rustdesk
|
||||||
|
docker build -t "rustdesk-builder" .
|
||||||
|
```
|
||||||
|
|
||||||
|
Deretter, hver gang du trenger å bygge applikasjonen, kjør følgene kommando:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
docker run --rm -it -v $PWD:/home/user/rustdesk -v rustdesk-git-cache:/home/user/.cargo/git -v rustdesk-registry-cache:/home/user/.cargo/registry -e PUID="$(id -u)" -e PGID="$(id -g)" rustdesk-builder
|
||||||
|
```
|
||||||
|
|
||||||
|
Det kan ta lengere tid før avhengighetene blir bufret første gang du bygger, senere bygg er raskere. Hvis du trenger å spesifisere forkjellige argumenter til bygge kommandoen, kan du gjøre det på slutten av kommandoen ved `<OPTIONAL-ARGS>` feltet. For eksempel, hvis du ville bygge en optimalisert release versjon, ville du kjørt kommandoen over fulgt `--release`. Den kjørbare filen vill være tilgjengelig i mål direktive på ditt system, og kan bli kjørt med:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
target/debug/rustdesk
|
||||||
|
```
|
||||||
|
|
||||||
|
Eller, hvis du kjører ett release program:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
target/release/rustdesk
|
||||||
|
```
|
||||||
|
|
||||||
|
Venligst pass på att du kjører disse kommandoene fra roten av RustDesk repositoret, eller kan det hende att applikasjon ikke finner de riktige ressursene. Pass også på att andre cargo subkommandoer som for eksempel `install` eller `run` ikke støttes med denne metoden da de vill installere eller kjøre programmet i konteineren istedet for verten.
|
||||||
|
|
||||||
|
## Fil Struktur
|
||||||
|
|
||||||
|
- **[libs/hbb_common](https://github.com/rustdesk/rustdesk/tree/master/libs/hbb_common)**: video kodek, configurasjon, tcp/udp innpakning, protobuf, fs funksjon for fil overføring, og noen andre verktøy funksjoner
|
||||||
|
- **[libs/scrap](https://github.com/rustdesk/rustdesk/tree/master/libs/scrap)**: skjermfangst
|
||||||
|
- **[libs/enigo](https://github.com/rustdesk/rustdesk/tree/master/libs/enigo)**: platform spesefik keyboard/mus kontroll
|
||||||
|
- **[libs/clipboard](https://github.com/rustdesk/rustdesk/tree/master/libs/clipboard)**: fil kopi og innliming implementasjon for Windows, Linux, macOS.
|
||||||
|
- **[src/ui](https://github.com/rustdesk/rustdesk/tree/master/src/ui)**: foreldret Sciter UI (avviklet)
|
||||||
|
- **[src/server](https://github.com/rustdesk/rustdesk/tree/master/src/server)**: lyd/utklippstavle/input/video tjenester, og internett tilkobling
|
||||||
|
- **[src/client.rs](https://github.com/rustdesk/rustdesk/tree/master/src/client.rs)**: start en peer tilkobling
|
||||||
|
- **[src/rendezvous_mediator.rs](https://github.com/rustdesk/rustdesk/tree/master/src/rendezvous_mediator.rs)**: Kommunikasjon med [rustdesk-server](https://github.com/rustdesk/rustdesk-server), vent på direkte fjernstyring (TCP hulling) eller vidresendt tilkobling
|
||||||
|
- **[src/platform](https://github.com/rustdesk/rustdesk/tree/master/src/platform)**: platform spesefik kode
|
||||||
|
- **[flutter](https://github.com/rustdesk/rustdesk/tree/master/flutter)**: Flutter kode for desktop og mobil
|
||||||
|
- **[flutter/web/js](https://github.com/rustdesk/rustdesk/tree/master/flutter/web/js)**: JavaScript for Flutter nettsted klient
|
||||||
|
|
||||||
|
## Skjermbilder
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
@@ -165,6 +165,3 @@ Upewnij się, że uruchamiasz te polecenia z katalogu głównego repozytorium Ru
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
## [Serwery publiczne](#public-servers)
|
|
||||||
|
|
||||||
RustDesk jest obsługiwany przez bezpłatne serwer w Unii Europejskiej, uprzejmie dostarczony przez [Codext GmbH](https://codext.link/rustdesk?utm_source=github)
|
|
||||||
|
|||||||
@@ -137,6 +137,10 @@ Por favor verifique que está executando estes comandos da raiz do repositório
|
|||||||
- **[src/rendezvous_mediator.rs](https://github.com/rustdesk/rustdesk/tree/master/src/rendezvous_mediator.rs)**: Comunicação com [rustdesk-server](https://github.com/rustdesk/rustdesk-server), aguardar pela conexão remota direta (TCP hole punching) ou conexão indireta (relayed)
|
- **[src/rendezvous_mediator.rs](https://github.com/rustdesk/rustdesk/tree/master/src/rendezvous_mediator.rs)**: Comunicação com [rustdesk-server](https://github.com/rustdesk/rustdesk-server), aguardar pela conexão remota direta (TCP hole punching) ou conexão indireta (relayed)
|
||||||
- **[src/platform](https://github.com/rustdesk/rustdesk/tree/master/src/platform)**: código específico a cada plataforma
|
- **[src/platform](https://github.com/rustdesk/rustdesk/tree/master/src/platform)**: código específico a cada plataforma
|
||||||
|
|
||||||
|
> [!Cuidadob]
|
||||||
|
> **Aviso de uso indevido:** <br>
|
||||||
|
> Os desenvolvedores do RustDesk não aprovam nem apoiam qualquer uso antiético ou ilegal deste software. O uso indevido, como acesso não autorizado, controle ou invasão de privacidade, é estritamente contra nossas diretrizes. Os autores não são responsáveis por qualquer uso indevido da aplicação.
|
||||||
|
|
||||||
## Screenshots
|
## Screenshots
|
||||||
|
|
||||||

|

|
||||||
|
|||||||
@@ -148,6 +148,10 @@ target/release/rustdesk
|
|||||||
- **[src/rendezvous_mediator.rs](https://github.com/rustdesk/rustdesk/tree/master/src/rendezvous_mediator.rs)**: свяжитесь с [rustdesk-server](https://github.com/rustdesk/rustdesk-server), дождитесь удаленного прямого (обход TCP NAT) или ретранслируемого соединения
|
- **[src/rendezvous_mediator.rs](https://github.com/rustdesk/rustdesk/tree/master/src/rendezvous_mediator.rs)**: свяжитесь с [rustdesk-server](https://github.com/rustdesk/rustdesk-server), дождитесь удаленного прямого (обход TCP NAT) или ретранслируемого соединения
|
||||||
- **[src/platform](https://github.com/rustdesk/rustdesk/tree/master/src/platform)**: специфичный для платформы код
|
- **[src/platform](https://github.com/rustdesk/rustdesk/tree/master/src/platform)**: специфичный для платформы код
|
||||||
|
|
||||||
|
> [!Осторожно]
|
||||||
|
> **Отказ от ответственности за неправомерное использование:** <br>
|
||||||
|
> Разработчики RustDesk не одобряют и не поддерживают какое-либо неэтичное или незаконное использование данного программного обеспечения. Неправомерное использование, такое как несанкционированный доступ, контроль или вторжение в частную жизнь, строго противоречит нашим правилам. Авторы не несут ответственности за любое неправомерное использование приложения.
|
||||||
|
|
||||||
## Скриншоты
|
## Скриншоты
|
||||||
|
|
||||||

|

|
||||||
|
|||||||
@@ -166,6 +166,10 @@ Lütfen bu komutları RustDesk deposunun kökünden çalıştırdığınızdan e
|
|||||||
- **[flutter](https://github.com/rustdesk/rustdesk/tree/master/flutter)**: mobil için Flutter kodu
|
- **[flutter](https://github.com/rustdesk/rustdesk/tree/master/flutter)**: mobil için Flutter kodu
|
||||||
- **[flutter/web/js](https://github.com/rustdesk/rustdesk/tree/master/flutter/web/js)**: Flutter web istemcisi için JavaScript
|
- **[flutter/web/js](https://github.com/rustdesk/rustdesk/tree/master/flutter/web/js)**: Flutter web istemcisi için JavaScript
|
||||||
|
|
||||||
|
> [!Dikkat]
|
||||||
|
> **Yanlış Kullanım Uyarısı:** <br>
|
||||||
|
> RustDesk geliştiricileri, bu yazılımın etik olmayan veya yasa dışı kullanımını onaylamaz veya desteklemez. Yetkisiz erişim, kontrol veya gizlilik ihlali gibi kötüye kullanımlar kesinlikle yönergelerimize aykırıdır. Yazarlar, uygulamanın herhangi bir yanlış kullanımından sorumlu değildir.
|
||||||
|
|
||||||
## Ekran Görüntüleri
|
## Ekran Görüntüleri
|
||||||
|
|
||||||

|

|
||||||
|
|||||||
@@ -172,6 +172,3 @@ target/release/rustdesk
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
## [Публічні сервери](#публічні-сервери)
|
|
||||||
|
|
||||||
RustDesk підтримується безкоштовним європейським сервером, любʼязно наданим [Codext GmbH](https://codext.link/rustdesk?utm_source=github)
|
|
||||||
|
|||||||
@@ -218,6 +218,10 @@ target/release/rustdesk
|
|||||||
- **[flutter](https://github.com/rustdesk/rustdesk/tree/master/flutter)**: 适用于桌面和移动设备的 Flutter 代码
|
- **[flutter](https://github.com/rustdesk/rustdesk/tree/master/flutter)**: 适用于桌面和移动设备的 Flutter 代码
|
||||||
- **[flutter/web/js](https://github.com/rustdesk/rustdesk/tree/master/flutter/web/js)**: Flutter Web版本中的Javascript代码
|
- **[flutter/web/js](https://github.com/rustdesk/rustdesk/tree/master/flutter/web/js)**: Flutter Web版本中的Javascript代码
|
||||||
|
|
||||||
|
> [!警告]
|
||||||
|
> **免责声明:** <br>
|
||||||
|
> RustDesk 的开发人员不纵容或支持任何不道德或非法的软件使用行为。滥用行为,例如未经授权的访问、控制或侵犯隐私,严格违反我们的准则。作者对应用程序的任何滥用行为概不负责。
|
||||||
|
|
||||||
## 截图
|
## 截图
|
||||||
|
|
||||||

|

|
||||||
|
|||||||
9
docs/SECURITY-NO.md
Normal file
9
docs/SECURITY-NO.md
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
# Sikkerhets Rettningslinjer
|
||||||
|
|
||||||
|
## Reportering av en Sårbarhet
|
||||||
|
|
||||||
|
Vi verdsetter pris på sikkerhet for prosjektet høyt. Og oppmunterer alle brukere til å rapportere sårbarheter de oppdager til oss.
|
||||||
|
Om du finner en sikkerhets sårbarhet i RustDesk prosjektet, venligst raportere det ansvarsfult ved å sende oss en email til info@rustdesk.com.
|
||||||
|
|
||||||
|
På dette tidspunktet har vi ingen bug dusør program. Vi er ett lite team som prøver å løse ett stort problem. Vi trenger att du raporterer alle sårbarhetene
|
||||||
|
annsvarfult så vi kan fortsettte å bygge ett en sikker applikasjon for hele felleskapet.
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "custom_plugin"
|
|
||||||
version = "0.1.0"
|
|
||||||
edition = "2021"
|
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
|
||||||
|
|
||||||
[lib]
|
|
||||||
name = "custom_plugin"
|
|
||||||
path = "src/lib.rs"
|
|
||||||
crate-type = ["cdylib"]
|
|
||||||
|
|
||||||
|
|
||||||
[features]
|
|
||||||
default = ["flutter"]
|
|
||||||
flutter = []
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
lazy_static = "1.4.0"
|
|
||||||
rustdesk = { path = "../../", version = "1.2.0", features = ["flutter"]}
|
|
||||||
|
|
||||||
[profile.release]
|
|
||||||
lto = true
|
|
||||||
codegen-units = 1
|
|
||||||
panic = 'abort'
|
|
||||||
strip = true
|
|
||||||
#opt-level = 'z' # only have smaller size after strip
|
|
||||||
rpath = true
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
use librustdesk::api::RustDeskApiTable;
|
|
||||||
/// This file demonstrates how to write a custom plugin for RustDesk.
|
|
||||||
use std::ffi::{c_char, c_int, CString};
|
|
||||||
|
|
||||||
lazy_static::lazy_static! {
|
|
||||||
pub static ref PLUGIN_NAME: CString = CString::new("A Template Rust Plugin").unwrap();
|
|
||||||
pub static ref PLUGIN_ID: CString = CString::new("TemplatePlugin").unwrap();
|
|
||||||
// Do your own logic based on the API provided by RustDesk.
|
|
||||||
pub static ref API: RustDeskApiTable = RustDeskApiTable::default();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[no_mangle]
|
|
||||||
fn plugin_name() -> *const c_char {
|
|
||||||
return PLUGIN_NAME.as_ptr();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[no_mangle]
|
|
||||||
fn plugin_id() -> *const c_char {
|
|
||||||
return PLUGIN_ID.as_ptr();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[no_mangle]
|
|
||||||
fn plugin_init() -> c_int {
|
|
||||||
return 0 as _;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[no_mangle]
|
|
||||||
fn plugin_dispose() -> c_int {
|
|
||||||
return 0 as _;
|
|
||||||
}
|
|
||||||
59
flatpak/com.rustdesk.RustDesk.metainfo.xml
Normal file
59
flatpak/com.rustdesk.RustDesk.metainfo.xml
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<component type="desktop-application">
|
||||||
|
<id>com.rustdesk.RustDesk</id>
|
||||||
|
<developer id="com.rustdesk">
|
||||||
|
<name>RustDesk</name>
|
||||||
|
</developer>
|
||||||
|
<launchable type="desktop-id">com.rustdesk.RustDesk.desktop</launchable>
|
||||||
|
<metadata_license>CC0-1.0</metadata_license>
|
||||||
|
<project_license>AGPL-3.0-only</project_license>
|
||||||
|
<name>RustDesk</name>
|
||||||
|
<summary>Secure remote desktop access</summary>
|
||||||
|
<description>
|
||||||
|
<p>
|
||||||
|
RustDesk is a full-featured open source remote control alternative for self-hosting and security with minimal configuration.
|
||||||
|
</p>
|
||||||
|
<ul>
|
||||||
|
<li> Works on Windows, macOS, Linux, iOS, Android, Web. </li>
|
||||||
|
<li> Supports VP8 / VP9 / AV1 software codecs, and H264 / H265 hardware codecs. </li>
|
||||||
|
<li> Own your data, easily set up self-hosting solution on your infrastructure. </li>
|
||||||
|
<li> P2P connection with end-to-end encryption based on NaCl. </li>
|
||||||
|
<li> No administrative privileges or installation needed for Windows, elevate priviledge locally or from remote on demand. </li>
|
||||||
|
<li> We like to keep things simple and will strive to make simpler where possible. </li>
|
||||||
|
</ul>
|
||||||
|
<p>
|
||||||
|
For self-hosting setup instructions please go to our home page.
|
||||||
|
</p>
|
||||||
|
</description>
|
||||||
|
<categories>
|
||||||
|
<category>Utility</category>
|
||||||
|
</categories>
|
||||||
|
<screenshots>
|
||||||
|
<screenshot type="default">
|
||||||
|
<caption>Remote desktop session</caption>
|
||||||
|
<image>https://user-images.githubusercontent.com/71636191/171661982-430285f0-2e12-4b1d-9957-4a58e375304d.png</image>
|
||||||
|
</screenshot>
|
||||||
|
</screenshots>
|
||||||
|
<branding>
|
||||||
|
<color type="primary" scheme_preference="light">#d9eaf8</color>
|
||||||
|
<color type="primary" scheme_preference="dark">#0160ee</color>
|
||||||
|
</branding>
|
||||||
|
<url type="homepage">https://rustdesk.com</url>
|
||||||
|
<url type="bugtracker">https://github.com/rustdesk/rustdesk/issues</url>
|
||||||
|
<url type="faq">https://github.com/rustdesk/rustdesk/wiki/FAQ</url>
|
||||||
|
<url type="help">https://rustdesk.com/docs</url>
|
||||||
|
<url type="donation">https://ko-fi.com/rustdesk</url>
|
||||||
|
<url type="vcs-browser">https://github.com/rustdesk/rustdesk</url>
|
||||||
|
<url type="translate">https://github.com/rustdesk/rustdesk/tree/master/src/lang</url>
|
||||||
|
<url type="contribute">https://github.com/rustdesk/rustdesk/blob/master/docs/CONTRIBUTING.md</url>
|
||||||
|
<url type="contact">https://rustdesk.com/docs/en/technical-support</url>
|
||||||
|
<requires>
|
||||||
|
<display_length compare="ge">600</display_length>
|
||||||
|
<internet>always</internet>
|
||||||
|
</requires>
|
||||||
|
<supports>
|
||||||
|
<control>keyboard</control>
|
||||||
|
<control>pointing</control>
|
||||||
|
</supports>
|
||||||
|
<content_rating type="oars-1.1"/>
|
||||||
|
</component>
|
||||||
@@ -1,19 +1,30 @@
|
|||||||
{
|
{
|
||||||
"id": "com.rustdesk.RustDesk",
|
"id": "com.rustdesk.RustDesk",
|
||||||
"runtime": "org.freedesktop.Platform",
|
"runtime": "org.freedesktop.Platform",
|
||||||
"runtime-version": "23.08",
|
"runtime-version": "24.08",
|
||||||
"sdk": "org.freedesktop.Sdk",
|
"sdk": "org.freedesktop.Sdk",
|
||||||
"command": "rustdesk",
|
"command": "rustdesk",
|
||||||
"icon": "share/icons/hicolor/scalable/apps/rustdesk.svg",
|
"cleanup": ["/include", "/lib/pkgconfig", "/share/gtk-doc"],
|
||||||
|
"rename-desktop-file": "rustdesk.desktop",
|
||||||
|
"rename-icon": "rustdesk",
|
||||||
"modules": [
|
"modules": [
|
||||||
"shared-modules/libappindicator/libappindicator-gtk3-12.10.json",
|
"shared-modules/libappindicator/libappindicator-gtk3-12.10.json",
|
||||||
"xdotool.json",
|
|
||||||
{
|
{
|
||||||
"name": "pam",
|
"name": "xdotool",
|
||||||
"buildsystem": "simple",
|
"no-autogen": true,
|
||||||
"build-commands": [
|
"make-install-args": ["PREFIX=${FLATPAK_DEST}"],
|
||||||
"./configure --disable-selinux --prefix=/app && make -j4 install"
|
"sources": [
|
||||||
],
|
{
|
||||||
|
"type": "archive",
|
||||||
|
"url": "https://github.com/jordansissel/xdotool/releases/download/v3.20211022.1/xdotool-3.20211022.1.tar.gz",
|
||||||
|
"sha256": "96f0facfde6d78eacad35b91b0f46fecd0b35e474c03e00e30da3fdd345f9ada"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "pam",
|
||||||
|
"buildsystem": "autotools",
|
||||||
|
"config-opts": ["--disable-selinux"],
|
||||||
"sources": [
|
"sources": [
|
||||||
{
|
{
|
||||||
"type": "archive",
|
"type": "archive",
|
||||||
@@ -26,32 +37,24 @@
|
|||||||
"name": "rustdesk",
|
"name": "rustdesk",
|
||||||
"buildsystem": "simple",
|
"buildsystem": "simple",
|
||||||
"build-commands": [
|
"build-commands": [
|
||||||
"bsdtar -zxvf rustdesk.deb",
|
"bsdtar -Oxf rustdesk.deb data.tar.xz | bsdtar -xf -",
|
||||||
"tar -xvf ./data.tar.xz",
|
"cp -r usr/* /app/",
|
||||||
"cp -r ./usr/* /app/",
|
"mkdir -p /app/bin && ln -s /app/share/rustdesk/rustdesk /app/bin/rustdesk"
|
||||||
"mkdir -p /app/bin && ln -s /app/lib/rustdesk/rustdesk /app/bin/rustdesk",
|
|
||||||
"mv /app/share/applications/rustdesk.desktop /app/share/applications/com.rustdesk.RustDesk.desktop",
|
|
||||||
"mv /app/share/applications/rustdesk-link.desktop /app/share/applications/com.rustdesk.RustDesk-link.desktop",
|
|
||||||
"sed -i '/^Icon=/ c\\Icon=com.rustdesk.RustDesk' /app/share/applications/*.desktop",
|
|
||||||
"mv /app/share/icons/hicolor/scalable/apps/rustdesk.svg /app/share/icons/hicolor/scalable/apps/com.rustdesk.RustDesk.svg",
|
|
||||||
"for size in 16 24 32 48 64 128 256 512; do\n rsvg-convert -w $size -h $size -f png -o $size.png scalable.svg\n install -Dm644 $size.png /app/share/icons/hicolor/${size}x${size}/apps/com.rustdesk.RustDesk.png\n done"
|
|
||||||
],
|
],
|
||||||
"cleanup": ["/include", "/lib/pkgconfig", "/share/gtk-doc"],
|
|
||||||
"sources": [
|
"sources": [
|
||||||
{
|
{
|
||||||
"type": "file",
|
"type": "file",
|
||||||
"path": "./rustdesk.deb"
|
"path": "rustdesk.deb"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "file",
|
"type": "file",
|
||||||
"path": "../res/scalable.svg"
|
"path": "com.rustdesk.RustDesk.metainfo.xml"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"finish-args": [
|
"finish-args": [
|
||||||
"--share=ipc",
|
"--share=ipc",
|
||||||
"--socket=x11",
|
|
||||||
"--socket=fallback-x11",
|
"--socket=fallback-x11",
|
||||||
"--socket=wayland",
|
"--socket=wayland",
|
||||||
"--share=network",
|
"--share=network",
|
||||||
@@ -60,4 +63,4 @@
|
|||||||
"--socket=pulseaudio",
|
"--socket=pulseaudio",
|
||||||
"--talk-name=org.freedesktop.Flatpak"
|
"--talk-name=org.freedesktop.Flatpak"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "xdotool",
|
|
||||||
"buildsystem": "simple",
|
|
||||||
"build-commands": [
|
|
||||||
"make -j4 && PREFIX=./build make install",
|
|
||||||
"cp -r ./build/* /app/"
|
|
||||||
],
|
|
||||||
"sources": [
|
|
||||||
{
|
|
||||||
"type": "archive",
|
|
||||||
"url": "https://github.com/jordansissel/xdotool/releases/download/v3.20211022.1/xdotool-3.20211022.1.tar.gz",
|
|
||||||
"sha256": "96f0facfde6d78eacad35b91b0f46fecd0b35e474c03e00e30da3fdd345f9ada"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -306,8 +306,8 @@ class FloatingWindowService : Service(), View.OnTouchListener {
|
|||||||
popupMenu.menu.add(0, idShowRustDesk, 0, translate("Show RustDesk"))
|
popupMenu.menu.add(0, idShowRustDesk, 0, translate("Show RustDesk"))
|
||||||
// For host side, clipboard sync
|
// For host side, clipboard sync
|
||||||
val idSyncClipboard = 1
|
val idSyncClipboard = 1
|
||||||
val isClipboardListenerEnabled = MainActivity.rdClipboardManager?.isListening ?: false
|
val isServiceSyncEnabled = (MainActivity.rdClipboardManager?.isCaptureStarted ?: false) && FFI.isServiceClipboardEnabled()
|
||||||
if (isClipboardListenerEnabled) {
|
if (isServiceSyncEnabled) {
|
||||||
popupMenu.menu.add(0, idSyncClipboard, 0, translate("Update client clipboard"))
|
popupMenu.menu.add(0, idSyncClipboard, 0, translate("Update client clipboard"))
|
||||||
}
|
}
|
||||||
val idStopService = 2
|
val idStopService = 2
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import android.view.accessibility.AccessibilityEvent
|
|||||||
import android.view.ViewGroup.LayoutParams
|
import android.view.ViewGroup.LayoutParams
|
||||||
import android.view.accessibility.AccessibilityNodeInfo
|
import android.view.accessibility.AccessibilityNodeInfo
|
||||||
import android.view.KeyEvent as KeyEventAndroid
|
import android.view.KeyEvent as KeyEventAndroid
|
||||||
|
import android.view.ViewConfiguration
|
||||||
import android.graphics.Rect
|
import android.graphics.Rect
|
||||||
import android.media.AudioManager
|
import android.media.AudioManager
|
||||||
import android.accessibilityservice.AccessibilityServiceInfo
|
import android.accessibilityservice.AccessibilityServiceInfo
|
||||||
@@ -34,10 +35,15 @@ import hbb.MessageOuterClass.KeyEvent
|
|||||||
import hbb.MessageOuterClass.KeyboardMode
|
import hbb.MessageOuterClass.KeyboardMode
|
||||||
import hbb.KeyEventConverter
|
import hbb.KeyEventConverter
|
||||||
|
|
||||||
const val LIFT_DOWN = 9
|
// const val BUTTON_UP = 2
|
||||||
const val LIFT_MOVE = 8
|
// const val BUTTON_BACK = 0x08
|
||||||
const val LIFT_UP = 10
|
|
||||||
|
const val LEFT_DOWN = 9
|
||||||
|
const val LEFT_MOVE = 8
|
||||||
|
const val LEFT_UP = 10
|
||||||
const val RIGHT_UP = 18
|
const val RIGHT_UP = 18
|
||||||
|
// (BUTTON_BACK << 3) | BUTTON_UP
|
||||||
|
const val BACK_UP = 66
|
||||||
const val WHEEL_BUTTON_DOWN = 33
|
const val WHEEL_BUTTON_DOWN = 33
|
||||||
const val WHEEL_BUTTON_UP = 34
|
const val WHEEL_BUTTON_UP = 34
|
||||||
const val WHEEL_DOWN = 523331
|
const val WHEEL_DOWN = 523331
|
||||||
@@ -64,12 +70,15 @@ class InputService : AccessibilityService() {
|
|||||||
|
|
||||||
private val logTag = "input service"
|
private val logTag = "input service"
|
||||||
private var leftIsDown = false
|
private var leftIsDown = false
|
||||||
private var touchPath = Path()
|
private val touchPath = Path()
|
||||||
|
private var stroke: GestureDescription.StrokeDescription? = null
|
||||||
private var lastTouchGestureStartTime = 0L
|
private var lastTouchGestureStartTime = 0L
|
||||||
private var mouseX = 0
|
private var mouseX = 0
|
||||||
private var mouseY = 0
|
private var mouseY = 0
|
||||||
private var timer = Timer()
|
private var timer = Timer()
|
||||||
private var recentActionTask: TimerTask? = null
|
private var recentActionTask: TimerTask? = null
|
||||||
|
// 100(tap timeout) + 400(long press timeout)
|
||||||
|
private val longPressDuration = ViewConfiguration.getTapTimeout().toLong() + ViewConfiguration.getLongPressTimeout().toLong()
|
||||||
|
|
||||||
private val wheelActionsQueue = LinkedList<GestureDescription>()
|
private val wheelActionsQueue = LinkedList<GestureDescription>()
|
||||||
private var isWheelActionsPolling = false
|
private var isWheelActionsPolling = false
|
||||||
@@ -77,6 +86,9 @@ class InputService : AccessibilityService() {
|
|||||||
|
|
||||||
private var fakeEditTextForTextStateCalculation: EditText? = null
|
private var fakeEditTextForTextStateCalculation: EditText? = null
|
||||||
|
|
||||||
|
private var lastX = 0
|
||||||
|
private var lastY = 0
|
||||||
|
|
||||||
private val volumeController: VolumeController by lazy { VolumeController(applicationContext.getSystemService(AUDIO_SERVICE) as AudioManager) }
|
private val volumeController: VolumeController by lazy { VolumeController(applicationContext.getSystemService(AUDIO_SERVICE) as AudioManager) }
|
||||||
|
|
||||||
@RequiresApi(Build.VERSION_CODES.N)
|
@RequiresApi(Build.VERSION_CODES.N)
|
||||||
@@ -84,7 +96,7 @@ class InputService : AccessibilityService() {
|
|||||||
val x = max(0, _x)
|
val x = max(0, _x)
|
||||||
val y = max(0, _y)
|
val y = max(0, _y)
|
||||||
|
|
||||||
if (mask == 0 || mask == LIFT_MOVE) {
|
if (mask == 0 || mask == LEFT_MOVE) {
|
||||||
val oldX = mouseX
|
val oldX = mouseX
|
||||||
val oldY = mouseY
|
val oldY = mouseY
|
||||||
mouseX = x * SCREEN_INFO.scale
|
mouseX = x * SCREEN_INFO.scale
|
||||||
@@ -98,31 +110,30 @@ class InputService : AccessibilityService() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// left button down ,was up
|
// left button down, was up
|
||||||
if (mask == LIFT_DOWN) {
|
if (mask == LEFT_DOWN) {
|
||||||
isWaitingLongPress = true
|
isWaitingLongPress = true
|
||||||
timer.schedule(object : TimerTask() {
|
timer.schedule(object : TimerTask() {
|
||||||
override fun run() {
|
override fun run() {
|
||||||
if (isWaitingLongPress) {
|
if (isWaitingLongPress) {
|
||||||
isWaitingLongPress = false
|
isWaitingLongPress = false
|
||||||
leftIsDown = false
|
continueGesture(mouseX, mouseY)
|
||||||
endGesture(mouseX, mouseY)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, LONG_TAP_DELAY * 4)
|
}, longPressDuration)
|
||||||
|
|
||||||
leftIsDown = true
|
leftIsDown = true
|
||||||
startGesture(mouseX, mouseY)
|
startGesture(mouseX, mouseY)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// left down ,was down
|
// left down, was down
|
||||||
if (leftIsDown) {
|
if (leftIsDown) {
|
||||||
continueGesture(mouseX, mouseY)
|
continueGesture(mouseX, mouseY)
|
||||||
}
|
}
|
||||||
|
|
||||||
// left up ,was down
|
// left up, was down
|
||||||
if (mask == LIFT_UP) {
|
if (mask == LEFT_UP) {
|
||||||
if (leftIsDown) {
|
if (leftIsDown) {
|
||||||
leftIsDown = false
|
leftIsDown = false
|
||||||
isWaitingLongPress = false
|
isWaitingLongPress = false
|
||||||
@@ -132,6 +143,11 @@ class InputService : AccessibilityService() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (mask == RIGHT_UP) {
|
if (mask == RIGHT_UP) {
|
||||||
|
longPress(mouseX, mouseY)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mask == BACK_UP) {
|
||||||
performGlobalAction(GLOBAL_ACTION_BACK)
|
performGlobalAction(GLOBAL_ACTION_BACK)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -241,36 +257,78 @@ class InputService : AccessibilityService() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun startGesture(x: Int, y: Int) {
|
@RequiresApi(Build.VERSION_CODES.N)
|
||||||
touchPath = Path()
|
private fun performClick(x: Int, y: Int, duration: Long) {
|
||||||
touchPath.moveTo(x.toFloat(), y.toFloat())
|
val path = Path()
|
||||||
lastTouchGestureStartTime = System.currentTimeMillis()
|
path.moveTo(x.toFloat(), y.toFloat())
|
||||||
|
try {
|
||||||
|
val longPressStroke = GestureDescription.StrokeDescription(path, 0, duration)
|
||||||
|
val builder = GestureDescription.Builder()
|
||||||
|
builder.addStroke(longPressStroke)
|
||||||
|
Log.d(logTag, "performClick x:$x y:$y time:$duration")
|
||||||
|
dispatchGesture(builder.build(), null, null)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(logTag, "performClick, error:$e")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun continueGesture(x: Int, y: Int) {
|
@RequiresApi(Build.VERSION_CODES.N)
|
||||||
|
private fun longPress(x: Int, y: Int) {
|
||||||
|
performClick(x, y, longPressDuration)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun startGesture(x: Int, y: Int) {
|
||||||
|
touchPath.reset()
|
||||||
|
touchPath.moveTo(x.toFloat(), y.toFloat())
|
||||||
|
lastTouchGestureStartTime = System.currentTimeMillis()
|
||||||
|
lastX = x
|
||||||
|
lastY = y
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequiresApi(Build.VERSION_CODES.N)
|
||||||
|
private fun doDispatchGesture(x: Int, y: Int, willContinue: Boolean) {
|
||||||
touchPath.lineTo(x.toFloat(), y.toFloat())
|
touchPath.lineTo(x.toFloat(), y.toFloat())
|
||||||
|
var duration = System.currentTimeMillis() - lastTouchGestureStartTime
|
||||||
|
if (duration <= 0) {
|
||||||
|
duration = 1
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
if (stroke == null) {
|
||||||
|
stroke = GestureDescription.StrokeDescription(
|
||||||
|
touchPath,
|
||||||
|
0,
|
||||||
|
duration,
|
||||||
|
willContinue
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
stroke = stroke?.continueStroke(touchPath, 0, duration, willContinue)
|
||||||
|
}
|
||||||
|
stroke?.let {
|
||||||
|
val builder = GestureDescription.Builder()
|
||||||
|
builder.addStroke(it)
|
||||||
|
Log.d(logTag, "doDispatchGesture x:$x y:$y time:$duration")
|
||||||
|
dispatchGesture(builder.build(), null, null)
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(logTag, "doDispatchGesture, willContinue:$willContinue, error:$e")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequiresApi(Build.VERSION_CODES.N)
|
||||||
|
private fun continueGesture(x: Int, y: Int) {
|
||||||
|
doDispatchGesture(x, y, true)
|
||||||
|
touchPath.reset()
|
||||||
|
touchPath.moveTo(x.toFloat(), y.toFloat())
|
||||||
|
lastTouchGestureStartTime = System.currentTimeMillis()
|
||||||
|
lastX = x
|
||||||
|
lastY = y
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequiresApi(Build.VERSION_CODES.N)
|
@RequiresApi(Build.VERSION_CODES.N)
|
||||||
private fun endGesture(x: Int, y: Int) {
|
private fun endGesture(x: Int, y: Int) {
|
||||||
try {
|
doDispatchGesture(x, y, false)
|
||||||
touchPath.lineTo(x.toFloat(), y.toFloat())
|
touchPath.reset()
|
||||||
var duration = System.currentTimeMillis() - lastTouchGestureStartTime
|
stroke = null
|
||||||
if (duration <= 0) {
|
|
||||||
duration = 1
|
|
||||||
}
|
|
||||||
val stroke = GestureDescription.StrokeDescription(
|
|
||||||
touchPath,
|
|
||||||
0,
|
|
||||||
duration
|
|
||||||
)
|
|
||||||
val builder = GestureDescription.Builder()
|
|
||||||
builder.addStroke(stroke)
|
|
||||||
Log.d(logTag, "end gesture x:$x y:$y time:$duration")
|
|
||||||
dispatchGesture(builder.build(), null, null)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.e(logTag, "endGesture error:$e")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequiresApi(Build.VERSION_CODES.N)
|
@RequiresApi(Build.VERSION_CODES.N)
|
||||||
|
|||||||
@@ -103,7 +103,6 @@ class MainActivity : FlutterActivity() {
|
|||||||
mainService?.let {
|
mainService?.let {
|
||||||
unbindService(serviceConnection)
|
unbindService(serviceConnection)
|
||||||
}
|
}
|
||||||
rdClipboardManager?.rustEnableServiceClipboard(false)
|
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -221,6 +220,10 @@ class MainActivity : FlutterActivity() {
|
|||||||
result.success(true)
|
result.success(true)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
"try_sync_clipboard" -> {
|
||||||
|
rdClipboardManager?.syncClipboard(true)
|
||||||
|
result.success(true)
|
||||||
|
}
|
||||||
GET_START_ON_BOOT_OPT -> {
|
GET_START_ON_BOOT_OPT -> {
|
||||||
val prefs = getSharedPreferences(KEY_SHARED_PREFERENCES, MODE_PRIVATE)
|
val prefs = getSharedPreferences(KEY_SHARED_PREFERENCES, MODE_PRIVATE)
|
||||||
result.success(prefs.getBoolean(KEY_START_ON_BOOT_OPT, false))
|
result.success(prefs.getBoolean(KEY_START_ON_BOOT_OPT, false))
|
||||||
@@ -402,13 +405,4 @@ class MainActivity : FlutterActivity() {
|
|||||||
super.onStart()
|
super.onStart()
|
||||||
stopService(Intent(this, FloatingWindowService::class.java))
|
stopService(Intent(this, FloatingWindowService::class.java))
|
||||||
}
|
}
|
||||||
|
|
||||||
// For client side
|
|
||||||
// When swithing from other app to this app, try to sync clipboard.
|
|
||||||
override fun onWindowFocusChanged(hasFocus: Boolean) {
|
|
||||||
super.onWindowFocusChanged(hasFocus)
|
|
||||||
if (hasFocus) {
|
|
||||||
rdClipboardManager?.syncClipboard(true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -65,8 +65,8 @@ class MainService : Service() {
|
|||||||
@Keep
|
@Keep
|
||||||
@RequiresApi(Build.VERSION_CODES.N)
|
@RequiresApi(Build.VERSION_CODES.N)
|
||||||
fun rustPointerInput(kind: Int, mask: Int, x: Int, y: Int) {
|
fun rustPointerInput(kind: Int, mask: Int, x: Int, y: Int) {
|
||||||
// turn on screen with LIFT_DOWN when screen off
|
// turn on screen with LEFT_DOWN when screen off
|
||||||
if (!powerManager.isInteractive && (kind == 0 || mask == LIFT_DOWN)) {
|
if (!powerManager.isInteractive && (kind == 0 || mask == LEFT_DOWN)) {
|
||||||
if (wakeLock.isHeld) {
|
if (wakeLock.isHeld) {
|
||||||
Log.d(logTag, "Turn on Screen, WakeLock release")
|
Log.d(logTag, "Turn on Screen, WakeLock release")
|
||||||
wakeLock.release()
|
wakeLock.release()
|
||||||
@@ -433,6 +433,7 @@ class MainService : Service() {
|
|||||||
checkMediaPermission()
|
checkMediaPermission()
|
||||||
_isStart = true
|
_isStart = true
|
||||||
FFI.setFrameRawEnable("video",true)
|
FFI.setFrameRawEnable("video",true)
|
||||||
|
MainActivity.rdClipboardManager?.setCaptureStarted(_isStart)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -441,6 +442,7 @@ class MainService : Service() {
|
|||||||
Log.d(logTag, "Stop Capture")
|
Log.d(logTag, "Stop Capture")
|
||||||
FFI.setFrameRawEnable("video",false)
|
FFI.setFrameRawEnable("video",false)
|
||||||
_isStart = false
|
_isStart = false
|
||||||
|
MainActivity.rdClipboardManager?.setCaptureStarted(_isStart)
|
||||||
// release video
|
// release video
|
||||||
if (reuseVirtualDisplay) {
|
if (reuseVirtualDisplay) {
|
||||||
// The virtual display video projection can be paused by calling `setSurface(null)`.
|
// The virtual display video projection can be paused by calling `setSurface(null)`.
|
||||||
|
|||||||
@@ -36,19 +36,19 @@ class RdClipboardManager(private val clipboardManager: ClipboardManager) {
|
|||||||
// though the `lastUpdatedClipData` will be set to null once.
|
// though the `lastUpdatedClipData` will be set to null once.
|
||||||
private var lastUpdatedClipData: ClipData? = null
|
private var lastUpdatedClipData: ClipData? = null
|
||||||
private var isClientEnabled = true;
|
private var isClientEnabled = true;
|
||||||
private var _isListening = false;
|
private var _isCaptureStarted = false;
|
||||||
val isListening: Boolean
|
|
||||||
get() = _isListening
|
|
||||||
|
|
||||||
fun checkPrimaryClip(isClient: Boolean, isSync: Boolean) {
|
val isCaptureStarted: Boolean
|
||||||
|
get() = _isCaptureStarted
|
||||||
|
|
||||||
|
fun checkPrimaryClip(isClient: Boolean) {
|
||||||
val clipData = clipboardManager.primaryClip
|
val clipData = clipboardManager.primaryClip
|
||||||
if (clipData != null && clipData.itemCount > 0) {
|
if (clipData != null && clipData.itemCount > 0) {
|
||||||
// Only handle the first item in the clipboard for now.
|
// Only handle the first item in the clipboard for now.
|
||||||
val clip = clipData.getItemAt(0)
|
val clip = clipData.getItemAt(0)
|
||||||
val isHostSync = !isClient && isSync
|
// Ignore the `isClipboardDataEqual()` check if it's a host operation.
|
||||||
// Ignore the `isClipboardDataEqual()` check if it's a host sync operation.
|
// Because it's an action manually triggered by the user.
|
||||||
// Because it's a action manually triggered by the user.
|
if (isClient) {
|
||||||
if (!isHostSync) {
|
|
||||||
if (lastUpdatedClipData != null && isClipboardDataEqual(clipData, lastUpdatedClipData!!)) {
|
if (lastUpdatedClipData != null && isClipboardDataEqual(clipData, lastUpdatedClipData!!)) {
|
||||||
Log.d(logTag, "Clipboard data is the same as last update, ignore")
|
Log.d(logTag, "Clipboard data is the same as last update, ignore")
|
||||||
return
|
return
|
||||||
@@ -95,13 +95,6 @@ class RdClipboardManager(private val clipboardManager: ClipboardManager) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val clipboardListener = object : ClipboardManager.OnPrimaryClipChangedListener {
|
|
||||||
override fun onPrimaryClipChanged() {
|
|
||||||
Log.d(logTag, "onPrimaryClipChanged")
|
|
||||||
checkPrimaryClip(true, false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun isSupportedMimeType(mimeType: String): Boolean {
|
private fun isSupportedMimeType(mimeType: String): Boolean {
|
||||||
return supportedMimeTypes.contains(mimeType)
|
return supportedMimeTypes.contains(mimeType)
|
||||||
}
|
}
|
||||||
@@ -136,43 +129,23 @@ class RdClipboardManager(private val clipboardManager: ClipboardManager) {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@Keep
|
fun setCaptureStarted(started: Boolean) {
|
||||||
fun rustEnableServiceClipboard(enable: Boolean) {
|
_isCaptureStarted = started
|
||||||
Log.d(logTag, "rustEnableServiceClipboard: enable: $enable, _isListening: $_isListening")
|
|
||||||
if (enable) {
|
|
||||||
if (!_isListening) {
|
|
||||||
clipboardManager.addPrimaryClipChangedListener(clipboardListener)
|
|
||||||
_isListening = true
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (_isListening) {
|
|
||||||
clipboardManager.removePrimaryClipChangedListener(clipboardListener)
|
|
||||||
_isListening = false
|
|
||||||
lastUpdatedClipData = null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Keep
|
@Keep
|
||||||
fun rustEnableClientClipboard(enable: Boolean) {
|
fun rustEnableClientClipboard(enable: Boolean) {
|
||||||
Log.d(logTag, "rustEnableClientClipboard: enable: $enable")
|
Log.d(logTag, "rustEnableClientClipboard: enable: $enable")
|
||||||
isClientEnabled = enable
|
isClientEnabled = enable
|
||||||
if (enable) {
|
lastUpdatedClipData = null
|
||||||
lastUpdatedClipData = clipboardManager.primaryClip
|
|
||||||
} else {
|
|
||||||
lastUpdatedClipData = null
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun syncClipboard(isClient: Boolean) {
|
fun syncClipboard(isClient: Boolean) {
|
||||||
Log.d(logTag, "syncClipboard: isClient: $isClient, isClientEnabled: $isClientEnabled, _isListening: $_isListening")
|
Log.d(logTag, "syncClipboard: isClient: $isClient, isClientEnabled: $isClientEnabled")
|
||||||
if (isClient && !isClientEnabled) {
|
if (isClient && !isClientEnabled) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (!isClient && !_isListening) {
|
checkPrimaryClip(isClient)
|
||||||
return
|
|
||||||
}
|
|
||||||
checkPrimaryClip(isClient, true)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Keep
|
@Keep
|
||||||
|
|||||||
@@ -24,4 +24,5 @@ object FFI {
|
|||||||
external fun setCodecInfo(info: String)
|
external fun setCodecInfo(info: String)
|
||||||
external fun getLocalOption(key: String): String
|
external fun getLocalOption(key: String): String
|
||||||
external fun onClipboardUpdate(clips: ByteBuffer)
|
external fun onClipboardUpdate(clips: ByteBuffer)
|
||||||
|
external fun isServiceClipboardEnabled(): Boolean
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
flutter/assets/device_group.ttf
Normal file
BIN
flutter/assets/device_group.ttf
Normal file
Binary file not shown.
@@ -150,6 +150,10 @@ prebuild)
|
|||||||
|
|
||||||
# Flutter used to compile Flutter<->Rust bridge files
|
# Flutter used to compile Flutter<->Rust bridge files
|
||||||
|
|
||||||
|
CARGO_EXPAND_VERSION="$(yq -r \
|
||||||
|
.env.CARGO_EXPAND_VERSION \
|
||||||
|
.github/workflows/bridge.yml)"
|
||||||
|
|
||||||
FLUTTER_BRIDGE_VERSION="$(yq -r \
|
FLUTTER_BRIDGE_VERSION="$(yq -r \
|
||||||
.env.FLUTTER_VERSION \
|
.env.FLUTTER_VERSION \
|
||||||
.github/workflows/bridge.yml)"
|
.github/workflows/bridge.yml)"
|
||||||
@@ -237,7 +241,10 @@ prebuild)
|
|||||||
|
|
||||||
# Install rust bridge generator
|
# Install rust bridge generator
|
||||||
|
|
||||||
cargo install cargo-expand
|
cargo install \
|
||||||
|
cargo-expand \
|
||||||
|
--version "${CARGO_EXPAND_VERSION}" \
|
||||||
|
--locked
|
||||||
cargo install flutter_rust_bridge_codegen \
|
cargo install flutter_rust_bridge_codegen \
|
||||||
--version "${FLUTTER_RUST_BRIDGE_VERSION}" \
|
--version "${FLUTTER_RUST_BRIDGE_VERSION}" \
|
||||||
--features "uuid" \
|
--features "uuid" \
|
||||||
|
|||||||
@@ -2,4 +2,6 @@
|
|||||||
# https://docs.flutter.dev/deployment/ios
|
# https://docs.flutter.dev/deployment/ios
|
||||||
# flutter build ipa --release --obfuscate --split-debug-info=./split-debug-info
|
# flutter build ipa --release --obfuscate --split-debug-info=./split-debug-info
|
||||||
# no obfuscate, because no easy to check errors
|
# no obfuscate, because no easy to check errors
|
||||||
|
cd $(dirname $(dirname $(which flutter)))
|
||||||
|
git apply ~/rustdesk/.github/patches/flutter_3.24.4_dropdown_menu_enableFilter.diff
|
||||||
flutter build ipa --release
|
flutter build ipa --release
|
||||||
|
|||||||
@@ -1,14 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
cd build/web/
|
|
||||||
python3 -c 'x=open("./main.dart.js", "rt").read();import re;y=re.search("https://.*canvaskit-wasm@([\d\.]+)/bin/",x);dirname="canvaskit@"+y.groups()[0];z=x.replace(y.group(),"/"+dirname+"/");f=open("./main.dart.js", "wt");f.write(z);import os;os.system("ln -s canvaskit " + dirname);'
|
|
||||||
mv jds/dist/index.js ./
|
|
||||||
mv jds/dist/vendor.js ./
|
|
||||||
/bin/rm -rf js
|
|
||||||
python3 -c 'import hashlib;x=hashlib.sha1(open("./main.dart.js").read().encode()).hexdigest()[:10];y=open("index.html","rt").read().replace("main.dart.js", "main.dart.js?v="+x);open("index.html","wt").write(y)'
|
|
||||||
python3 -c 'import hashlib;x=hashlib.sha1(open("./index.js").read().encode()).hexdigest()[:10];y=open("index.html","rt").read().replace("js/dist/index.js", "index.js?v="+x);open("index.html","wt").write(y)'
|
|
||||||
python3 -c 'import hashlib;x=hashlib.sha1(open("./vendor.js").read().encode()).hexdigest()[:10];y=open("index.html","rt").read().replace("js/dist/vendor.js", "vendor.js?v="+x);open("index.html","wt").write(y)'
|
|
||||||
tar czf x *
|
|
||||||
scp x sg:/tmp/
|
|
||||||
ssh sg "sudo tar xzf /tmp/x -C /var/www/html/web.rustdesk.com/ && /bin/rm /tmp/x && sudo chown www-data:www-data /var/www/html/web.rustdesk.com/ -R"
|
|
||||||
/bin/rm x
|
|
||||||
cd -
|
|
||||||
@@ -133,7 +133,7 @@ SPEC CHECKSUMS:
|
|||||||
sqflite: 673a0e54cc04b7d6dba8d24fb8095b31c3a99eec
|
sqflite: 673a0e54cc04b7d6dba8d24fb8095b31c3a99eec
|
||||||
SwiftyGif: 93a1cc87bf3a51916001cf8f3d63835fb64c819f
|
SwiftyGif: 93a1cc87bf3a51916001cf8f3d63835fb64c819f
|
||||||
uni_links: d97da20c7701486ba192624d99bffaaffcfc298a
|
uni_links: d97da20c7701486ba192624d99bffaaffcfc298a
|
||||||
url_launcher_ios: bbd758c6e7f9fd7b5b1d4cde34d2b95fcce5e812
|
url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe
|
||||||
video_player_avfoundation: 02011213dab73ae3687df27ce441fbbcc82b5579
|
video_player_avfoundation: 02011213dab73ae3687df27ce441fbbcc82b5579
|
||||||
wakelock_plus: 8b09852c8876491e4b6d179e17dfe2a0b5f60d47
|
wakelock_plus: 8b09852c8876491e4b6d179e17dfe2a0b5f60d47
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import UIKit
|
import UIKit
|
||||||
import Flutter
|
import Flutter
|
||||||
|
|
||||||
@UIApplicationMain
|
@main
|
||||||
@objc class AppDelegate: FlutterAppDelegate {
|
@objc class AppDelegate: FlutterAppDelegate {
|
||||||
override func application(
|
override func application(
|
||||||
_ application: UIApplication,
|
_ application: UIApplication,
|
||||||
|
|||||||
@@ -1,2 +1,4 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
cd $(dirname $(dirname $(which flutter)))
|
||||||
|
git apply ~/rustdesk/.github/patches/flutter_3.24.4_dropdown_menu_enableFilter.diff
|
||||||
cargo build --features flutter,hwcodec --release --target aarch64-apple-ios --lib
|
cargo build --features flutter,hwcodec --release --target aarch64-apple-ios --lib
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
import 'dart:io';
|
|
||||||
|
|
||||||
import 'package:back_button_interceptor/back_button_interceptor.dart';
|
import 'package:back_button_interceptor/back_button_interceptor.dart';
|
||||||
import 'package:desktop_multi_window/desktop_multi_window.dart';
|
import 'package:desktop_multi_window/desktop_multi_window.dart';
|
||||||
@@ -104,6 +103,8 @@ enum DesktopType {
|
|||||||
class IconFont {
|
class IconFont {
|
||||||
static const _family1 = 'Tabbar';
|
static const _family1 = 'Tabbar';
|
||||||
static const _family2 = 'PeerSearchbar';
|
static const _family2 = 'PeerSearchbar';
|
||||||
|
static const _family3 = 'AddressBook';
|
||||||
|
static const _family4 = 'DeviceGroup';
|
||||||
IconFont._();
|
IconFont._();
|
||||||
|
|
||||||
static const IconData max = IconData(0xe606, fontFamily: _family1);
|
static const IconData max = IconData(0xe606, fontFamily: _family1);
|
||||||
@@ -114,8 +115,11 @@ class IconFont {
|
|||||||
static const IconData menu = IconData(0xe628, fontFamily: _family1);
|
static const IconData menu = IconData(0xe628, fontFamily: _family1);
|
||||||
static const IconData search = IconData(0xe6a4, fontFamily: _family2);
|
static const IconData search = IconData(0xe6a4, fontFamily: _family2);
|
||||||
static const IconData roundClose = IconData(0xe6ed, fontFamily: _family2);
|
static const IconData roundClose = IconData(0xe6ed, fontFamily: _family2);
|
||||||
static const IconData addressBook =
|
static const IconData addressBook = IconData(0xe602, fontFamily: _family3);
|
||||||
IconData(0xe602, fontFamily: "AddressBook");
|
static const IconData deviceGroupOutline =
|
||||||
|
IconData(0xe623, fontFamily: _family4);
|
||||||
|
static const IconData deviceGroupFill =
|
||||||
|
IconData(0xe748, fontFamily: _family4);
|
||||||
}
|
}
|
||||||
|
|
||||||
class ColorThemeExtension extends ThemeExtension<ColorThemeExtension> {
|
class ColorThemeExtension extends ThemeExtension<ColorThemeExtension> {
|
||||||
@@ -818,7 +822,11 @@ class OverlayDialogManager {
|
|||||||
|
|
||||||
close([res]) {
|
close([res]) {
|
||||||
_dialogs.remove(dialogTag);
|
_dialogs.remove(dialogTag);
|
||||||
dialog.complete(res);
|
try {
|
||||||
|
dialog.complete(res);
|
||||||
|
} catch (e) {
|
||||||
|
debugPrint("Dialog complete catch error: $e");
|
||||||
|
}
|
||||||
BackButtonInterceptor.removeByName(dialogTag);
|
BackButtonInterceptor.removeByName(dialogTag);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2567,6 +2575,8 @@ bool get kUseCompatibleUiMode =>
|
|||||||
isWindows &&
|
isWindows &&
|
||||||
const [WindowsTarget.w7].contains(windowsBuildNumber.windowsVersion);
|
const [WindowsTarget.w7].contains(windowsBuildNumber.windowsVersion);
|
||||||
|
|
||||||
|
bool get isWin10 => windowsBuildNumber.windowsVersion == WindowsTarget.w10;
|
||||||
|
|
||||||
class ServerConfig {
|
class ServerConfig {
|
||||||
late String idServer;
|
late String idServer;
|
||||||
late String relayServer;
|
late String relayServer;
|
||||||
@@ -2810,7 +2820,7 @@ Widget buildRemoteBlock(
|
|||||||
onExit: (event) => block.value = false,
|
onExit: (event) => block.value = false,
|
||||||
child: Stack(children: [
|
child: Stack(children: [
|
||||||
// scope block tab
|
// scope block tab
|
||||||
FocusScope(child: child, canRequestFocus: !block.value),
|
preventMouseKeyBuilder(child: child, block: block.value),
|
||||||
// mask block click, cm not block click and still use check_click_time to avoid block local click
|
// mask block click, cm not block click and still use check_click_time to avoid block local click
|
||||||
if (mask)
|
if (mask)
|
||||||
Offstage(
|
Offstage(
|
||||||
@@ -2822,6 +2832,11 @@ Widget buildRemoteBlock(
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget preventMouseKeyBuilder({required Widget child, required bool block}) {
|
||||||
|
return ExcludeFocus(
|
||||||
|
excluding: block, child: AbsorbPointer(child: child, absorbing: block));
|
||||||
|
}
|
||||||
|
|
||||||
Widget unreadMessageCountBuilder(RxInt? count,
|
Widget unreadMessageCountBuilder(RxInt? count,
|
||||||
{double? size, double? fontSize}) {
|
{double? size, double? fontSize}) {
|
||||||
return Obx(() => Offstage(
|
return Obx(() => Offstage(
|
||||||
@@ -3460,35 +3475,6 @@ Widget buildPresetPasswordWarning() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool get isLinuxMateDesktop =>
|
|
||||||
isLinux &&
|
|
||||||
(Platform.environment['XDG_CURRENT_DESKTOP']?.toLowerCase() == 'mate' ||
|
|
||||||
Platform.environment['XDG_SESSION_DESKTOP']?.toLowerCase() == 'mate' ||
|
|
||||||
Platform.environment['DESKTOP_SESSION']?.toLowerCase() == 'mate');
|
|
||||||
|
|
||||||
Map<String, dynamic>? _linuxOsDistro;
|
|
||||||
|
|
||||||
String getLinuxOsDistroId() {
|
|
||||||
if (_linuxOsDistro == null) {
|
|
||||||
String osInfo = bind.getOsDistroInfo();
|
|
||||||
if (osInfo.isEmpty) {
|
|
||||||
_linuxOsDistro = {};
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
_linuxOsDistro = jsonDecode(osInfo);
|
|
||||||
} catch (e) {
|
|
||||||
debugPrint('Failed to parse os info: $e');
|
|
||||||
// Don't call `bind.getOsDistroInfo()` again if failed to parse osInfo.
|
|
||||||
_linuxOsDistro = {};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return (_linuxOsDistro?['id'] ?? '') as String;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool get isLinuxMint =>
|
|
||||||
getLinuxOsDistroId().toLowerCase().contains('linuxmint');
|
|
||||||
|
|
||||||
// https://github.com/leanflutter/window_manager/blob/87dd7a50b4cb47a375b9fc697f05e56eea0a2ab3/lib/src/widgets/virtual_window_frame.dart#L44
|
// https://github.com/leanflutter/window_manager/blob/87dd7a50b4cb47a375b9fc697f05e56eea0a2ab3/lib/src/widgets/virtual_window_frame.dart#L44
|
||||||
Widget buildVirtualWindowFrame(BuildContext context, Widget child) {
|
Widget buildVirtualWindowFrame(BuildContext context, Widget child) {
|
||||||
boxShadow() => isMainDesktopWindow
|
boxShadow() => isMainDesktopWindow
|
||||||
@@ -3635,7 +3621,7 @@ void earlyAssert() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void checkUpdate() {
|
void checkUpdate() {
|
||||||
if (isDesktop || isAndroid) {
|
if (!isWeb) {
|
||||||
if (!bind.isCustomClient()) {
|
if (!bind.isCustomClient()) {
|
||||||
platformFFI.registerEventHandler(
|
platformFFI.registerEventHandler(
|
||||||
kCheckSoftwareUpdateFinish, kCheckSoftwareUpdateFinish,
|
kCheckSoftwareUpdateFinish, kCheckSoftwareUpdateFinish,
|
||||||
@@ -3650,3 +3636,87 @@ void checkUpdate() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// https://github.com/flutter/flutter/issues/153560#issuecomment-2497160535
|
||||||
|
// For TextField, TextFormField
|
||||||
|
extension WorkaroundFreezeLinuxMint on Widget {
|
||||||
|
Widget workaroundFreezeLinuxMint() {
|
||||||
|
// No need to check if is Linux Mint, because this workaround is harmless on other platforms.
|
||||||
|
if (isLinux) {
|
||||||
|
return ExcludeSemantics(child: this);
|
||||||
|
} else {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't use `extension` here, the border looks weird if using `extension` in my test.
|
||||||
|
Widget workaroundWindowBorder(BuildContext context, Widget child) {
|
||||||
|
if (!isWin10) {
|
||||||
|
return child;
|
||||||
|
}
|
||||||
|
|
||||||
|
final isLight = Theme.of(context).brightness == Brightness.light;
|
||||||
|
final borderColor = isLight ? Colors.black87 : Colors.grey;
|
||||||
|
final width = isLight ? 0.5 : 0.1;
|
||||||
|
|
||||||
|
getBorderWidget(Widget child) {
|
||||||
|
return Obx(() =>
|
||||||
|
(stateGlobal.isMaximized.isTrue || stateGlobal.fullscreen.isTrue)
|
||||||
|
? Offstage()
|
||||||
|
: child);
|
||||||
|
}
|
||||||
|
|
||||||
|
final List<Widget> borders = [
|
||||||
|
getBorderWidget(Container(
|
||||||
|
color: borderColor,
|
||||||
|
height: width + 0.1,
|
||||||
|
))
|
||||||
|
];
|
||||||
|
if (kWindowType == WindowType.Main && !isLight) {
|
||||||
|
borders.addAll([
|
||||||
|
getBorderWidget(Align(
|
||||||
|
alignment: Alignment.topLeft,
|
||||||
|
child: Container(
|
||||||
|
color: borderColor,
|
||||||
|
width: width,
|
||||||
|
),
|
||||||
|
)),
|
||||||
|
getBorderWidget(Align(
|
||||||
|
alignment: Alignment.topRight,
|
||||||
|
child: Container(
|
||||||
|
color: borderColor,
|
||||||
|
width: width,
|
||||||
|
),
|
||||||
|
)),
|
||||||
|
getBorderWidget(Align(
|
||||||
|
alignment: Alignment.bottomCenter,
|
||||||
|
child: Container(
|
||||||
|
color: borderColor,
|
||||||
|
height: width,
|
||||||
|
),
|
||||||
|
)),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
return Stack(
|
||||||
|
children: [
|
||||||
|
child,
|
||||||
|
...borders,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void updateTextAndPreserveSelection(
|
||||||
|
TextEditingController controller, String text) {
|
||||||
|
// Only care about select all for now.
|
||||||
|
final isSelected = controller.selection.isValid &&
|
||||||
|
controller.selection.end > controller.selection.start;
|
||||||
|
|
||||||
|
// Set text will make the selection invalid.
|
||||||
|
controller.text = text;
|
||||||
|
|
||||||
|
if (isSelected) {
|
||||||
|
controller.selection = TextSelection(
|
||||||
|
baseOffset: 0, extentOffset: controller.value.text.length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -67,6 +67,7 @@ class PeerPayload {
|
|||||||
int? status;
|
int? status;
|
||||||
String user = '';
|
String user = '';
|
||||||
String user_name = '';
|
String user_name = '';
|
||||||
|
String? device_group_name;
|
||||||
String note = '';
|
String note = '';
|
||||||
|
|
||||||
PeerPayload.fromJson(Map<String, dynamic> json)
|
PeerPayload.fromJson(Map<String, dynamic> json)
|
||||||
@@ -75,6 +76,7 @@ class PeerPayload {
|
|||||||
status = json['status'],
|
status = json['status'],
|
||||||
user = json['user'] ?? '',
|
user = json['user'] ?? '',
|
||||||
user_name = json['user_name'] ?? '',
|
user_name = json['user_name'] ?? '',
|
||||||
|
device_group_name = json['device_group_name'] ?? '',
|
||||||
note = json['note'] ?? '';
|
note = json['note'] ?? '';
|
||||||
|
|
||||||
static Peer toPeer(PeerPayload p) {
|
static Peer toPeer(PeerPayload p) {
|
||||||
@@ -84,6 +86,7 @@ class PeerPayload {
|
|||||||
"username": p.info['username'] ?? '',
|
"username": p.info['username'] ?? '',
|
||||||
"platform": _platform(p.info['os']),
|
"platform": _platform(p.info['os']),
|
||||||
"hostname": p.info['device_name'],
|
"hostname": p.info['device_name'],
|
||||||
|
"device_group_name": p.device_group_name,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -265,3 +268,19 @@ class AbTag {
|
|||||||
: name = json['name'] ?? '',
|
: name = json['name'] ?? '',
|
||||||
color = json['color'] ?? '';
|
color = json['color'] ?? '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class DeviceGroupPayload {
|
||||||
|
String name;
|
||||||
|
|
||||||
|
DeviceGroupPayload(this.name);
|
||||||
|
|
||||||
|
DeviceGroupPayload.fromJson(Map<String, dynamic> json)
|
||||||
|
: name = json['name'] ?? '';
|
||||||
|
|
||||||
|
Map<String, dynamic> toGroupCacheJson() {
|
||||||
|
final Map<String, dynamic> map = {
|
||||||
|
'name': name,
|
||||||
|
};
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -286,7 +286,7 @@ class _AddressBookState extends State<AddressBook> {
|
|||||||
borderRadius: BorderRadius.circular(8),
|
borderRadius: BorderRadius.circular(8),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
).workaroundFreezeLinuxMint(),
|
||||||
),
|
),
|
||||||
searchMatchFn: (item, searchValue) {
|
searchMatchFn: (item, searchValue) {
|
||||||
return item.value
|
return item.value
|
||||||
@@ -556,7 +556,7 @@ class _AddressBookState extends State<AddressBook> {
|
|||||||
: translate('ID'),
|
: translate('ID'),
|
||||||
errorText: errorMsg,
|
errorText: errorMsg,
|
||||||
errorMaxLines: 5),
|
errorMaxLines: 5),
|
||||||
))),
|
).workaroundFreezeLinuxMint())),
|
||||||
row(
|
row(
|
||||||
lable: Text(
|
lable: Text(
|
||||||
translate('Alias'),
|
translate('Alias'),
|
||||||
@@ -569,7 +569,7 @@ class _AddressBookState extends State<AddressBook> {
|
|||||||
? null
|
? null
|
||||||
: translate('Alias'),
|
: translate('Alias'),
|
||||||
),
|
),
|
||||||
)),
|
).workaroundFreezeLinuxMint()),
|
||||||
),
|
),
|
||||||
if (isCurrentAbShared)
|
if (isCurrentAbShared)
|
||||||
row(
|
row(
|
||||||
@@ -598,7 +598,7 @@ class _AddressBookState extends State<AddressBook> {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
).workaroundFreezeLinuxMint(),
|
||||||
)),
|
)),
|
||||||
if (gFFI.abModel.currentAbTags.isNotEmpty)
|
if (gFFI.abModel.currentAbTags.isNotEmpty)
|
||||||
Align(
|
Align(
|
||||||
@@ -704,7 +704,7 @@ class _AddressBookState extends State<AddressBook> {
|
|||||||
),
|
),
|
||||||
controller: controller,
|
controller: controller,
|
||||||
autofocus: true,
|
autofocus: true,
|
||||||
),
|
).workaroundFreezeLinuxMint(),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import 'dart:convert';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_hbb/common/formatter/id_formatter.dart';
|
import 'package:flutter_hbb/common/formatter/id_formatter.dart';
|
||||||
import '../../../models/platform_model.dart';
|
import '../../../models/platform_model.dart';
|
||||||
@@ -6,56 +5,104 @@ import 'package:flutter_hbb/models/peer_model.dart';
|
|||||||
import 'package:flutter_hbb/common.dart';
|
import 'package:flutter_hbb/common.dart';
|
||||||
import 'package:flutter_hbb/common/widgets/peer_card.dart';
|
import 'package:flutter_hbb/common/widgets/peer_card.dart';
|
||||||
|
|
||||||
Future<List<Peer>> getAllPeers() async {
|
class AllPeersLoader {
|
||||||
Map<String, dynamic> recentPeers = jsonDecode(bind.mainLoadRecentPeersSync());
|
List<Peer> peers = [];
|
||||||
Map<String, dynamic> lanPeers = jsonDecode(bind.mainLoadLanPeersSync());
|
|
||||||
Map<String, dynamic> combinedPeers = {};
|
|
||||||
|
|
||||||
void mergePeers(Map<String, dynamic> peers) {
|
bool _isPeersLoading = false;
|
||||||
if (peers.containsKey("peers")) {
|
bool _isPeersLoaded = false;
|
||||||
dynamic peerData = peers["peers"];
|
|
||||||
|
|
||||||
if (peerData is String) {
|
final String _listenerKey = 'AllPeersLoader';
|
||||||
try {
|
|
||||||
peerData = jsonDecode(peerData);
|
|
||||||
} catch (e) {
|
|
||||||
print("Error decoding peers: $e");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (peerData is List) {
|
late void Function(VoidCallback) setState;
|
||||||
for (var peer in peerData) {
|
|
||||||
if (peer is Map && peer.containsKey("id")) {
|
bool get needLoad => !_isPeersLoaded && !_isPeersLoading;
|
||||||
String id = peer["id"];
|
bool get isPeersLoaded => _isPeersLoaded;
|
||||||
if (!combinedPeers.containsKey(id)) {
|
|
||||||
combinedPeers[id] = peer;
|
AllPeersLoader();
|
||||||
}
|
|
||||||
}
|
void init(void Function(VoidCallback) setState) {
|
||||||
}
|
this.setState = setState;
|
||||||
|
gFFI.recentPeersModel.addListener(_mergeAllPeers);
|
||||||
|
gFFI.lanPeersModel.addListener(_mergeAllPeers);
|
||||||
|
gFFI.abModel.addPeerUpdateListener(_listenerKey, _mergeAllPeers);
|
||||||
|
gFFI.groupModel.addPeerUpdateListener(_listenerKey, _mergeAllPeers);
|
||||||
|
}
|
||||||
|
|
||||||
|
void clear() {
|
||||||
|
gFFI.recentPeersModel.removeListener(_mergeAllPeers);
|
||||||
|
gFFI.lanPeersModel.removeListener(_mergeAllPeers);
|
||||||
|
gFFI.abModel.removePeerUpdateListener(_listenerKey);
|
||||||
|
gFFI.groupModel.removePeerUpdateListener(_listenerKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> getAllPeers() async {
|
||||||
|
if (!needLoad) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_isPeersLoading = true;
|
||||||
|
|
||||||
|
if (gFFI.recentPeersModel.peers.isEmpty) {
|
||||||
|
bind.mainLoadRecentPeers();
|
||||||
|
}
|
||||||
|
if (gFFI.lanPeersModel.peers.isEmpty) {
|
||||||
|
bind.mainLoadLanPeers();
|
||||||
|
}
|
||||||
|
// No need to care about peers from abModel, and group model.
|
||||||
|
// Because they will pull data in `refreshCurrentUser()` on startup.
|
||||||
|
|
||||||
|
final startTime = DateTime.now();
|
||||||
|
_mergeAllPeers();
|
||||||
|
final diffTime = DateTime.now().difference(startTime).inMilliseconds;
|
||||||
|
if (diffTime < 100) {
|
||||||
|
await Future.delayed(Duration(milliseconds: diffTime));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _mergeAllPeers() {
|
||||||
|
Map<String, dynamic> combinedPeers = {};
|
||||||
|
for (var p in gFFI.abModel.allPeers()) {
|
||||||
|
if (!combinedPeers.containsKey(p.id)) {
|
||||||
|
combinedPeers[p.id] = p.toJson();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
for (var p in gFFI.groupModel.peers.map((e) => Peer.copy(e)).toList()) {
|
||||||
|
if (!combinedPeers.containsKey(p.id)) {
|
||||||
mergePeers(recentPeers);
|
combinedPeers[p.id] = p.toJson();
|
||||||
mergePeers(lanPeers);
|
}
|
||||||
for (var p in gFFI.abModel.allPeers()) {
|
|
||||||
if (!combinedPeers.containsKey(p.id)) {
|
|
||||||
combinedPeers[p.id] = p.toJson();
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
for (var p in gFFI.groupModel.peers.map((e) => Peer.copy(e)).toList()) {
|
List<Peer> parsedPeers = [];
|
||||||
if (!combinedPeers.containsKey(p.id)) {
|
for (var peer in combinedPeers.values) {
|
||||||
combinedPeers[p.id] = p.toJson();
|
parsedPeers.add(Peer.fromJson(peer));
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
List<Peer> parsedPeers = [];
|
Set<String> peerIds = combinedPeers.keys.toSet();
|
||||||
|
for (final peer in gFFI.lanPeersModel.peers) {
|
||||||
|
if (!peerIds.contains(peer.id)) {
|
||||||
|
parsedPeers.add(peer);
|
||||||
|
peerIds.add(peer.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (var peer in combinedPeers.values) {
|
for (final peer in gFFI.recentPeersModel.peers) {
|
||||||
parsedPeers.add(Peer.fromJson(peer));
|
if (!peerIds.contains(peer.id)) {
|
||||||
|
parsedPeers.add(peer);
|
||||||
|
peerIds.add(peer.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (final id in gFFI.recentPeersModel.restPeerIds) {
|
||||||
|
if (!peerIds.contains(id)) {
|
||||||
|
parsedPeers.add(Peer.fromJson({'id': id}));
|
||||||
|
peerIds.add(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
peers = parsedPeers;
|
||||||
|
setState(() {
|
||||||
|
_isPeersLoading = false;
|
||||||
|
_isPeersLoaded = true;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
return parsedPeers;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class AutocompletePeerTile extends StatefulWidget {
|
class AutocompletePeerTile extends StatefulWidget {
|
||||||
|
|||||||
@@ -167,7 +167,7 @@ class ChatPage extends StatelessWidget implements PageShape {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
).workaroundFreezeLinuxMint();
|
||||||
return SelectionArea(child: chat);
|
return SelectionArea(child: chat);
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ void changeIdDialog() {
|
|||||||
final rules = [
|
final rules = [
|
||||||
RegexValidationRule('starts with a letter', RegExp(r'^[a-zA-Z]')),
|
RegexValidationRule('starts with a letter', RegExp(r'^[a-zA-Z]')),
|
||||||
LengthRangeValidationRule(6, 16),
|
LengthRangeValidationRule(6, 16),
|
||||||
RegexValidationRule('allowed characters', RegExp(r'^\w*$'))
|
RegexValidationRule('allowed characters', RegExp(r'^[\w-]*$'))
|
||||||
];
|
];
|
||||||
|
|
||||||
gFFI.dialogManager.show((setState, close, context) {
|
gFFI.dialogManager.show((setState, close, context) {
|
||||||
@@ -140,7 +140,7 @@ void changeIdDialog() {
|
|||||||
msg = '';
|
msg = '';
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
),
|
).workaroundFreezeLinuxMint(),
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
height: 8.0,
|
height: 8.0,
|
||||||
),
|
),
|
||||||
@@ -201,13 +201,14 @@ void changeWhiteList({Function()? callback}) async {
|
|||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
child: TextField(
|
child: TextField(
|
||||||
maxLines: null,
|
maxLines: null,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
errorText: msg.isEmpty ? null : translate(msg),
|
errorText: msg.isEmpty ? null : translate(msg),
|
||||||
),
|
),
|
||||||
controller: controller,
|
controller: controller,
|
||||||
enabled: !isOptFixed,
|
enabled: !isOptFixed,
|
||||||
autofocus: true),
|
autofocus: true)
|
||||||
|
.workaroundFreezeLinuxMint(),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -287,22 +288,23 @@ Future<String> changeDirectAccessPort(
|
|||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
child: TextField(
|
child: TextField(
|
||||||
maxLines: null,
|
maxLines: null,
|
||||||
keyboardType: TextInputType.number,
|
keyboardType: TextInputType.number,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
hintText: '21118',
|
hintText: '21118',
|
||||||
isCollapsed: true,
|
isCollapsed: true,
|
||||||
prefix: Text('$currentIP : '),
|
prefix: Text('$currentIP : '),
|
||||||
suffix: IconButton(
|
suffix: IconButton(
|
||||||
padding: EdgeInsets.zero,
|
padding: EdgeInsets.zero,
|
||||||
icon: const Icon(Icons.clear, size: 16),
|
icon: const Icon(Icons.clear, size: 16),
|
||||||
onPressed: () => controller.clear())),
|
onPressed: () => controller.clear())),
|
||||||
inputFormatters: [
|
inputFormatters: [
|
||||||
FilteringTextInputFormatter.allow(RegExp(
|
FilteringTextInputFormatter.allow(RegExp(
|
||||||
r'^([0-9]|[1-9]\d|[1-9]\d{2}|[1-9]\d{3}|[1-5]\d{4}|6[0-4]\d{3}|65[0-4]\d{2}|655[0-2]\d|6553[0-5])$')),
|
r'^([0-9]|[1-9]\d|[1-9]\d{2}|[1-9]\d{3}|[1-5]\d{4}|6[0-4]\d{3}|65[0-4]\d{2}|655[0-2]\d|6553[0-5])$')),
|
||||||
],
|
],
|
||||||
controller: controller,
|
controller: controller,
|
||||||
autofocus: true),
|
autofocus: true)
|
||||||
|
.workaroundFreezeLinuxMint(),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -335,21 +337,22 @@ Future<String> changeAutoDisconnectTimeout(String old) async {
|
|||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
child: TextField(
|
child: TextField(
|
||||||
maxLines: null,
|
maxLines: null,
|
||||||
keyboardType: TextInputType.number,
|
keyboardType: TextInputType.number,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
hintText: '10',
|
hintText: '10',
|
||||||
isCollapsed: true,
|
isCollapsed: true,
|
||||||
suffix: IconButton(
|
suffix: IconButton(
|
||||||
padding: EdgeInsets.zero,
|
padding: EdgeInsets.zero,
|
||||||
icon: const Icon(Icons.clear, size: 16),
|
icon: const Icon(Icons.clear, size: 16),
|
||||||
onPressed: () => controller.clear())),
|
onPressed: () => controller.clear())),
|
||||||
inputFormatters: [
|
inputFormatters: [
|
||||||
FilteringTextInputFormatter.allow(RegExp(
|
FilteringTextInputFormatter.allow(RegExp(
|
||||||
r'^([0-9]|[1-9]\d|[1-9]\d{2}|[1-9]\d{3}|[1-5]\d{4}|6[0-4]\d{3}|65[0-4]\d{2}|655[0-2]\d|6553[0-5])$')),
|
r'^([0-9]|[1-9]\d|[1-9]\d{2}|[1-9]\d{3}|[1-5]\d{4}|6[0-4]\d{3}|65[0-4]\d{2}|655[0-2]\d|6553[0-5])$')),
|
||||||
],
|
],
|
||||||
controller: controller,
|
controller: controller,
|
||||||
autofocus: true),
|
autofocus: true)
|
||||||
|
.workaroundFreezeLinuxMint(),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -427,7 +430,7 @@ class DialogTextField extends StatelessWidget {
|
|||||||
keyboardType: keyboardType,
|
keyboardType: keyboardType,
|
||||||
inputFormatters: inputFormatters,
|
inputFormatters: inputFormatters,
|
||||||
maxLength: maxLength,
|
maxLength: maxLength,
|
||||||
),
|
).workaroundFreezeLinuxMint(),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
).paddingSymmetric(vertical: 4.0);
|
).paddingSymmetric(vertical: 4.0);
|
||||||
@@ -1501,7 +1504,7 @@ showAuditDialog(FFI ffi) async {
|
|||||||
maxLength: 256,
|
maxLength: 256,
|
||||||
controller: controller,
|
controller: controller,
|
||||||
focusNode: focusNode,
|
focusNode: focusNode,
|
||||||
)),
|
).workaroundFreezeLinuxMint()),
|
||||||
actions: [
|
actions: [
|
||||||
dialogButton('Cancel', onPressed: close, isOutline: true),
|
dialogButton('Cancel', onPressed: close, isOutline: true),
|
||||||
dialogButton('OK', onPressed: submit)
|
dialogButton('OK', onPressed: submit)
|
||||||
@@ -1748,7 +1751,7 @@ void renameDialog(
|
|||||||
autofocus: true,
|
autofocus: true,
|
||||||
decoration: InputDecoration(labelText: translate('Name')),
|
decoration: InputDecoration(labelText: translate('Name')),
|
||||||
validator: validator,
|
validator: validator,
|
||||||
),
|
).workaroundFreezeLinuxMint(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
// NOT use Offstage to wrap LinearProgressIndicator
|
// NOT use Offstage to wrap LinearProgressIndicator
|
||||||
@@ -1808,7 +1811,7 @@ void changeBot({Function()? callback}) async {
|
|||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
hintText: translate('Token'),
|
hintText: translate('Token'),
|
||||||
),
|
),
|
||||||
);
|
).workaroundFreezeLinuxMint();
|
||||||
|
|
||||||
return CustomAlertDialog(
|
return CustomAlertDialog(
|
||||||
title: Text(translate("Telegram bot")),
|
title: Text(translate("Telegram bot")),
|
||||||
@@ -2178,7 +2181,7 @@ void setSharedAbPasswordDialog(String abName, Peer peer) {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
).workaroundFreezeLinuxMint(),
|
||||||
if (!gFFI.abModel.current.isPersonal())
|
if (!gFFI.abModel.current.isPersonal())
|
||||||
Row(children: [
|
Row(children: [
|
||||||
Icon(Icons.info, color: Colors.amber).marginOnly(right: 4),
|
Icon(Icons.info, color: Colors.amber).marginOnly(right: 4),
|
||||||
|
|||||||
@@ -678,7 +678,7 @@ Future<bool?> verificationCodeDialog(
|
|||||||
labelText: "Email", prefixIcon: Icon(Icons.email)),
|
labelText: "Email", prefixIcon: Icon(Icons.email)),
|
||||||
readOnly: true,
|
readOnly: true,
|
||||||
controller: TextEditingController(text: user?.email),
|
controller: TextEditingController(text: user?.email),
|
||||||
)),
|
).workaroundFreezeLinuxMint()),
|
||||||
isEmailVerification ? const SizedBox(height: 8) : const Offstage(),
|
isEmailVerification ? const SizedBox(height: 8) : const Offstage(),
|
||||||
codeField,
|
codeField,
|
||||||
/*
|
/*
|
||||||
|
|||||||
@@ -20,8 +20,11 @@ class MyGroup extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _MyGroupState extends State<MyGroup> {
|
class _MyGroupState extends State<MyGroup> {
|
||||||
RxString get selectedUser => gFFI.groupModel.selectedUser;
|
RxBool get isSelectedDeviceGroup => gFFI.groupModel.isSelectedDeviceGroup;
|
||||||
RxString get searchUserText => gFFI.groupModel.searchUserText;
|
RxString get selectedAccessibleItemName =>
|
||||||
|
gFFI.groupModel.selectedAccessibleItemName;
|
||||||
|
RxString get searchAccessibleItemNameText =>
|
||||||
|
gFFI.groupModel.searchAccessibleItemNameText;
|
||||||
static TextEditingController searchUserController = TextEditingController();
|
static TextEditingController searchUserController = TextEditingController();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -72,7 +75,7 @@ class _MyGroupState extends State<MyGroup> {
|
|||||||
child: Container(
|
child: Container(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
height: double.infinity,
|
height: double.infinity,
|
||||||
child: _buildUserContacts(),
|
child: _buildLeftList(),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
@@ -105,7 +108,7 @@ class _MyGroupState extends State<MyGroup> {
|
|||||||
_buildLeftHeader(),
|
_buildLeftHeader(),
|
||||||
Container(
|
Container(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
child: _buildUserContacts(),
|
child: _buildLeftList(),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -130,7 +133,8 @@ class _MyGroupState extends State<MyGroup> {
|
|||||||
child: TextField(
|
child: TextField(
|
||||||
controller: searchUserController,
|
controller: searchUserController,
|
||||||
onChanged: (value) {
|
onChanged: (value) {
|
||||||
searchUserText.value = value;
|
searchAccessibleItemNameText.value = value;
|
||||||
|
selectedAccessibleItemName.value = '';
|
||||||
},
|
},
|
||||||
textAlignVertical: TextAlignVertical.center,
|
textAlignVertical: TextAlignVertical.center,
|
||||||
style: TextStyle(fontSize: fontSize),
|
style: TextStyle(fontSize: fontSize),
|
||||||
@@ -145,25 +149,35 @@ class _MyGroupState extends State<MyGroup> {
|
|||||||
border: InputBorder.none,
|
border: InputBorder.none,
|
||||||
isDense: true,
|
isDense: true,
|
||||||
),
|
),
|
||||||
)),
|
).workaroundFreezeLinuxMint()),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildUserContacts() {
|
Widget _buildLeftList() {
|
||||||
return Obx(() {
|
return Obx(() {
|
||||||
final items = gFFI.groupModel.users.where((p0) {
|
final userItems = gFFI.groupModel.users.where((p0) {
|
||||||
if (searchUserText.isNotEmpty) {
|
if (searchAccessibleItemNameText.isNotEmpty) {
|
||||||
return p0.name
|
return p0.name
|
||||||
.toLowerCase()
|
.toLowerCase()
|
||||||
.contains(searchUserText.value.toLowerCase());
|
.contains(searchAccessibleItemNameText.value.toLowerCase());
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}).toList();
|
||||||
|
final deviceGroupItems = gFFI.groupModel.deviceGroups.where((p0) {
|
||||||
|
if (searchAccessibleItemNameText.isNotEmpty) {
|
||||||
|
return p0.name
|
||||||
|
.toLowerCase()
|
||||||
|
.contains(searchAccessibleItemNameText.value.toLowerCase());
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}).toList();
|
}).toList();
|
||||||
listView(bool isPortrait) => ListView.builder(
|
listView(bool isPortrait) => ListView.builder(
|
||||||
shrinkWrap: isPortrait,
|
shrinkWrap: isPortrait,
|
||||||
itemCount: items.length,
|
itemCount: deviceGroupItems.length + userItems.length,
|
||||||
itemBuilder: (context, index) => _buildUserItem(items[index]));
|
itemBuilder: (context, index) => index < deviceGroupItems.length
|
||||||
|
? _buildDeviceGroupItem(deviceGroupItems[index])
|
||||||
|
: _buildUserItem(userItems[index - deviceGroupItems.length]));
|
||||||
var maxHeight = max(MediaQuery.of(context).size.height / 6, 100.0);
|
var maxHeight = max(MediaQuery.of(context).size.height / 6, 100.0);
|
||||||
return Obx(() => stateGlobal.isPortrait.isFalse
|
return Obx(() => stateGlobal.isPortrait.isFalse
|
||||||
? listView(false)
|
? listView(false)
|
||||||
@@ -174,14 +188,16 @@ class _MyGroupState extends State<MyGroup> {
|
|||||||
Widget _buildUserItem(UserPayload user) {
|
Widget _buildUserItem(UserPayload user) {
|
||||||
final username = user.name;
|
final username = user.name;
|
||||||
return InkWell(onTap: () {
|
return InkWell(onTap: () {
|
||||||
if (selectedUser.value != username) {
|
isSelectedDeviceGroup.value = false;
|
||||||
selectedUser.value = username;
|
if (selectedAccessibleItemName.value != username) {
|
||||||
|
selectedAccessibleItemName.value = username;
|
||||||
} else {
|
} else {
|
||||||
selectedUser.value = '';
|
selectedAccessibleItemName.value = '';
|
||||||
}
|
}
|
||||||
}, child: Obx(
|
}, child: Obx(
|
||||||
() {
|
() {
|
||||||
bool selected = selectedUser.value == username;
|
bool selected = !isSelectedDeviceGroup.value &&
|
||||||
|
selectedAccessibleItemName.value == username;
|
||||||
final isMe = username == gFFI.userModel.userName.value;
|
final isMe = username == gFFI.userModel.userName.value;
|
||||||
final colorMe = MyTheme.color(context).me!;
|
final colorMe = MyTheme.color(context).me!;
|
||||||
return Container(
|
return Container(
|
||||||
@@ -238,4 +254,43 @@ class _MyGroupState extends State<MyGroup> {
|
|||||||
},
|
},
|
||||||
)).marginSymmetric(horizontal: 12).marginOnly(bottom: 6);
|
)).marginSymmetric(horizontal: 12).marginOnly(bottom: 6);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget _buildDeviceGroupItem(DeviceGroupPayload deviceGroup) {
|
||||||
|
final name = deviceGroup.name;
|
||||||
|
return InkWell(onTap: () {
|
||||||
|
isSelectedDeviceGroup.value = true;
|
||||||
|
if (selectedAccessibleItemName.value != name) {
|
||||||
|
selectedAccessibleItemName.value = name;
|
||||||
|
} else {
|
||||||
|
selectedAccessibleItemName.value = '';
|
||||||
|
}
|
||||||
|
}, child: Obx(
|
||||||
|
() {
|
||||||
|
bool selected = isSelectedDeviceGroup.value &&
|
||||||
|
selectedAccessibleItemName.value == name;
|
||||||
|
return Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: selected ? MyTheme.color(context).highlight : null,
|
||||||
|
border: Border(
|
||||||
|
bottom: BorderSide(
|
||||||
|
width: 0.7,
|
||||||
|
color: Theme.of(context).dividerColor.withOpacity(0.1))),
|
||||||
|
),
|
||||||
|
child: Container(
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
width: 20,
|
||||||
|
height: 20,
|
||||||
|
child: Icon(IconFont.deviceGroupOutline,
|
||||||
|
color: MyTheme.accent, size: 19),
|
||||||
|
).marginOnly(right: 4),
|
||||||
|
Expanded(child: Text(name)),
|
||||||
|
],
|
||||||
|
).paddingSymmetric(vertical: 4),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
)).marginSymmetric(horizontal: 12).marginOnly(bottom: 6);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -716,18 +716,18 @@ abstract class BasePeerCard extends StatelessWidget {
|
|||||||
switch (tab) {
|
switch (tab) {
|
||||||
case PeerTabIndex.recent:
|
case PeerTabIndex.recent:
|
||||||
await bind.mainRemovePeer(id: id);
|
await bind.mainRemovePeer(id: id);
|
||||||
await bind.mainLoadRecentPeers();
|
bind.mainLoadRecentPeers();
|
||||||
break;
|
break;
|
||||||
case PeerTabIndex.fav:
|
case PeerTabIndex.fav:
|
||||||
final favs = (await bind.mainGetFav()).toList();
|
final favs = (await bind.mainGetFav()).toList();
|
||||||
if (favs.remove(id)) {
|
if (favs.remove(id)) {
|
||||||
await bind.mainStoreFav(favs: favs);
|
await bind.mainStoreFav(favs: favs);
|
||||||
await bind.mainLoadFavPeers();
|
bind.mainLoadFavPeers();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case PeerTabIndex.lan:
|
case PeerTabIndex.lan:
|
||||||
await bind.mainRemoveDiscovered(id: id);
|
await bind.mainRemoveDiscovered(id: id);
|
||||||
await bind.mainLoadLanPeers();
|
bind.mainLoadLanPeers();
|
||||||
break;
|
break;
|
||||||
case PeerTabIndex.ab:
|
case PeerTabIndex.ab:
|
||||||
await gFFI.abModel.deletePeers([id]);
|
await gFFI.abModel.deletePeers([id]);
|
||||||
@@ -1257,7 +1257,7 @@ void _rdpDialog(String id) async {
|
|||||||
hintText: '3389'),
|
hintText: '3389'),
|
||||||
controller: portController,
|
controller: portController,
|
||||||
autofocus: true,
|
autofocus: true,
|
||||||
),
|
).workaroundFreezeLinuxMint(),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
).marginOnly(bottom: isDesktop ? 8 : 0),
|
).marginOnly(bottom: isDesktop ? 8 : 0),
|
||||||
@@ -1277,7 +1277,7 @@ void _rdpDialog(String id) async {
|
|||||||
labelText:
|
labelText:
|
||||||
isDesktop ? null : translate('Username')),
|
isDesktop ? null : translate('Username')),
|
||||||
controller: userController,
|
controller: userController,
|
||||||
),
|
).workaroundFreezeLinuxMint(),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
).marginOnly(bottom: stateGlobal.isPortrait.isFalse ? 8 : 0)),
|
).marginOnly(bottom: stateGlobal.isPortrait.isFalse ? 8 : 0)),
|
||||||
@@ -1305,7 +1305,7 @@ void _rdpDialog(String id) async {
|
|||||||
? Icons.visibility_off
|
? Icons.visibility_off
|
||||||
: Icons.visibility))),
|
: Icons.visibility))),
|
||||||
controller: passwordController,
|
controller: passwordController,
|
||||||
)),
|
).workaroundFreezeLinuxMint()),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
))
|
))
|
||||||
|
|||||||
@@ -33,8 +33,8 @@ class PeerTabPage extends StatefulWidget {
|
|||||||
|
|
||||||
class _TabEntry {
|
class _TabEntry {
|
||||||
final Widget widget;
|
final Widget widget;
|
||||||
final Function({dynamic hint}) load;
|
final Function({dynamic hint})? load;
|
||||||
_TabEntry(this.widget, this.load);
|
_TabEntry(this.widget, [this.load]);
|
||||||
}
|
}
|
||||||
|
|
||||||
EdgeInsets? _menuPadding() {
|
EdgeInsets? _menuPadding() {
|
||||||
@@ -44,21 +44,15 @@ EdgeInsets? _menuPadding() {
|
|||||||
class _PeerTabPageState extends State<PeerTabPage>
|
class _PeerTabPageState extends State<PeerTabPage>
|
||||||
with SingleTickerProviderStateMixin {
|
with SingleTickerProviderStateMixin {
|
||||||
final List<_TabEntry> entries = [
|
final List<_TabEntry> entries = [
|
||||||
_TabEntry(
|
_TabEntry(RecentPeersView(
|
||||||
RecentPeersView(
|
menuPadding: _menuPadding(),
|
||||||
menuPadding: _menuPadding(),
|
)),
|
||||||
),
|
_TabEntry(FavoritePeersView(
|
||||||
bind.mainLoadRecentPeers),
|
menuPadding: _menuPadding(),
|
||||||
_TabEntry(
|
)),
|
||||||
FavoritePeersView(
|
_TabEntry(DiscoveredPeersView(
|
||||||
menuPadding: _menuPadding(),
|
menuPadding: _menuPadding(),
|
||||||
),
|
)),
|
||||||
bind.mainLoadFavPeers),
|
|
||||||
_TabEntry(
|
|
||||||
DiscoveredPeersView(
|
|
||||||
menuPadding: _menuPadding(),
|
|
||||||
),
|
|
||||||
bind.mainDiscover),
|
|
||||||
_TabEntry(
|
_TabEntry(
|
||||||
AddressBook(
|
AddressBook(
|
||||||
menuPadding: _menuPadding(),
|
menuPadding: _menuPadding(),
|
||||||
@@ -100,7 +94,7 @@ class _PeerTabPageState extends State<PeerTabPage>
|
|||||||
gFFI.peerTabModel.setCurrentTabCachedPeers([]);
|
gFFI.peerTabModel.setCurrentTabCachedPeers([]);
|
||||||
}
|
}
|
||||||
gFFI.peerTabModel.setCurrentTab(tabIndex);
|
gFFI.peerTabModel.setCurrentTab(tabIndex);
|
||||||
entries[tabIndex].load(hint: false);
|
entries[tabIndex].load?.call(hint: false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -225,7 +219,7 @@ class _PeerTabPageState extends State<PeerTabPage>
|
|||||||
child: RefreshWidget(
|
child: RefreshWidget(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
if (gFFI.peerTabModel.currentTab < entries.length) {
|
if (gFFI.peerTabModel.currentTab < entries.length) {
|
||||||
entries[gFFI.peerTabModel.currentTab].load();
|
entries[gFFI.peerTabModel.currentTab].load?.call();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
spinning: loading,
|
spinning: loading,
|
||||||
@@ -404,7 +398,7 @@ class _PeerTabPageState extends State<PeerTabPage>
|
|||||||
for (var p in peers) {
|
for (var p in peers) {
|
||||||
await bind.mainRemovePeer(id: p.id);
|
await bind.mainRemovePeer(id: p.id);
|
||||||
}
|
}
|
||||||
await bind.mainLoadRecentPeers();
|
bind.mainLoadRecentPeers();
|
||||||
break;
|
break;
|
||||||
case 1:
|
case 1:
|
||||||
final favs = (await bind.mainGetFav()).toList();
|
final favs = (await bind.mainGetFav()).toList();
|
||||||
@@ -412,13 +406,13 @@ class _PeerTabPageState extends State<PeerTabPage>
|
|||||||
favs.remove(p.id);
|
favs.remove(p.id);
|
||||||
}).toList();
|
}).toList();
|
||||||
await bind.mainStoreFav(favs: favs);
|
await bind.mainStoreFav(favs: favs);
|
||||||
await bind.mainLoadFavPeers();
|
bind.mainLoadFavPeers();
|
||||||
break;
|
break;
|
||||||
case 2:
|
case 2:
|
||||||
for (var p in peers) {
|
for (var p in peers) {
|
||||||
await bind.mainRemoveDiscovered(id: p.id);
|
await bind.mainRemoveDiscovered(id: p.id);
|
||||||
}
|
}
|
||||||
await bind.mainLoadLanPeers();
|
bind.mainLoadLanPeers();
|
||||||
break;
|
break;
|
||||||
case 3:
|
case 3:
|
||||||
await gFFI.abModel.deletePeers(peers.map((p) => p.id).toList());
|
await gFFI.abModel.deletePeers(peers.map((p) => p.id).toList());
|
||||||
@@ -743,7 +737,7 @@ class _PeerSearchBarState extends State<PeerSearchBar> {
|
|||||||
border: InputBorder.none,
|
border: InputBorder.none,
|
||||||
isDense: true,
|
isDense: true,
|
||||||
),
|
),
|
||||||
),
|
).workaroundFreezeLinuxMint(),
|
||||||
),
|
),
|
||||||
// Icon(Icons.close),
|
// Icon(Icons.close),
|
||||||
IconButton(
|
IconButton(
|
||||||
|
|||||||
@@ -25,13 +25,13 @@ class PeerSortType {
|
|||||||
static const String remoteId = 'Remote ID';
|
static const String remoteId = 'Remote ID';
|
||||||
static const String remoteHost = 'Remote Host';
|
static const String remoteHost = 'Remote Host';
|
||||||
static const String username = 'Username';
|
static const String username = 'Username';
|
||||||
// static const String status = 'Status';
|
static const String status = 'Status';
|
||||||
|
|
||||||
static List<String> values = [
|
static List<String> values = [
|
||||||
PeerSortType.remoteId,
|
PeerSortType.remoteId,
|
||||||
PeerSortType.remoteHost,
|
PeerSortType.remoteHost,
|
||||||
PeerSortType.username,
|
PeerSortType.username,
|
||||||
// PeerSortType.status
|
PeerSortType.status
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -384,9 +384,9 @@ class _PeersViewState extends State<_PeersView>
|
|||||||
peers.sort((p1, p2) =>
|
peers.sort((p1, p2) =>
|
||||||
p1.username.toLowerCase().compareTo(p2.username.toLowerCase()));
|
p1.username.toLowerCase().compareTo(p2.username.toLowerCase()));
|
||||||
break;
|
break;
|
||||||
// case PeerSortType.status:
|
case PeerSortType.status:
|
||||||
// peers.sort((p1, p2) => p1.online ? -1 : 1);
|
peers.sort((p1, p2) => p1.online ? -1 : 1);
|
||||||
// break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -562,14 +562,26 @@ class MyGroupPeerView extends BasePeersView {
|
|||||||
);
|
);
|
||||||
|
|
||||||
static bool filter(Peer peer) {
|
static bool filter(Peer peer) {
|
||||||
if (gFFI.groupModel.searchUserText.isNotEmpty) {
|
final model = gFFI.groupModel;
|
||||||
if (!peer.loginName.contains(gFFI.groupModel.searchUserText)) {
|
if (model.searchAccessibleItemNameText.isNotEmpty) {
|
||||||
|
final text = model.searchAccessibleItemNameText.value;
|
||||||
|
final searchPeersOfUser = peer.loginName.contains(text) &&
|
||||||
|
model.users.any((user) => user.name == peer.loginName);
|
||||||
|
final searchPeersOfDeviceGroup = peer.device_group_name.contains(text) &&
|
||||||
|
model.deviceGroups.any((g) => g.name == peer.device_group_name);
|
||||||
|
if (!searchPeersOfUser && !searchPeersOfDeviceGroup) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (gFFI.groupModel.selectedUser.isNotEmpty) {
|
if (model.selectedAccessibleItemName.isNotEmpty) {
|
||||||
if (gFFI.groupModel.selectedUser.value != peer.loginName) {
|
if (model.isSelectedDeviceGroup.value) {
|
||||||
return false;
|
if (model.selectedAccessibleItemName.value != peer.device_group_name) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (model.selectedAccessibleItemName.value != peer.loginName) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -187,6 +187,11 @@ class _RawTouchGestureDetectorRegionState
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_cacheLongPressPositionTs = DateTime.now().millisecondsSinceEpoch;
|
_cacheLongPressPositionTs = DateTime.now().millisecondsSinceEpoch;
|
||||||
|
if (ffiModel.isPeerMobile) {
|
||||||
|
await ffi.cursorModel
|
||||||
|
.move(_cacheLongPressPosition.dx, _cacheLongPressPosition.dy);
|
||||||
|
await inputModel.tapDown(MouseButtons.left);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -204,15 +209,31 @@ class _RawTouchGestureDetectorRegionState
|
|||||||
if (lastDeviceKind != PointerDeviceKind.touch) {
|
if (lastDeviceKind != PointerDeviceKind.touch) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (!ffi.ffiModel.isPeerMobile) {
|
||||||
|
if (handleTouch) {
|
||||||
|
final isMoved = await ffi.cursorModel
|
||||||
|
.move(_cacheLongPressPosition.dx, _cacheLongPressPosition.dy);
|
||||||
|
if (!isMoved) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await inputModel.tap(MouseButtons.right);
|
||||||
|
} else {
|
||||||
|
// It's better to send a message to tell the controlled device that the long press event is triggered.
|
||||||
|
// We're now using a `TimerTask` in `InputService.kt` to decide whether to trigger the long press event.
|
||||||
|
// It's not accurate and it's better to use the same detection logic in the controlling side.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onLongPressMoveUpdate(LongPressMoveUpdateDetails d) async {
|
||||||
|
if (!ffiModel.isPeerMobile || lastDeviceKind != PointerDeviceKind.touch) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (handleTouch) {
|
if (handleTouch) {
|
||||||
final isMoved = await ffi.cursorModel
|
if (!ffi.cursorModel.isInRemoteRect(d.localPosition)) {
|
||||||
.move(_cacheLongPressPosition.dx, _cacheLongPressPosition.dy);
|
|
||||||
if (!isMoved) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
await ffi.cursorModel.move(d.localPosition.dx, d.localPosition.dy);
|
||||||
if (!ffi.ffiModel.isPeerMobile) {
|
|
||||||
await inputModel.tap(MouseButtons.right);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -339,7 +360,9 @@ class _RawTouchGestureDetectorRegionState
|
|||||||
if (isDesktop || isWebDesktop) {
|
if (isDesktop || isWebDesktop) {
|
||||||
ffi.cursorModel.clearRemoteWindowCoords();
|
ffi.cursorModel.clearRemoteWindowCoords();
|
||||||
}
|
}
|
||||||
await inputModel.sendMouse('up', MouseButtons.left);
|
if (handleTouch) {
|
||||||
|
await inputModel.sendMouse('up', MouseButtons.left);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// scale + pan event
|
// scale + pan event
|
||||||
@@ -430,7 +453,8 @@ class _RawTouchGestureDetectorRegionState
|
|||||||
instance
|
instance
|
||||||
..onLongPressDown = onLongPressDown
|
..onLongPressDown = onLongPressDown
|
||||||
..onLongPressUp = onLongPressUp
|
..onLongPressUp = onLongPressUp
|
||||||
..onLongPress = onLongPress;
|
..onLongPress = onLongPress
|
||||||
|
..onLongPressMoveUpdate = onLongPressMoveUpdate;
|
||||||
}),
|
}),
|
||||||
// Customized
|
// Customized
|
||||||
HoldTapMoveGestureRecognizer:
|
HoldTapMoveGestureRecognizer:
|
||||||
|
|||||||
@@ -248,7 +248,7 @@ const kFullScreenEdgeSize = 0.0;
|
|||||||
const kMaximizeEdgeSize = 0.0;
|
const kMaximizeEdgeSize = 0.0;
|
||||||
// Do not use kWindowResizeEdgeSize directly. Use `windowResizeEdgeSize` in `common.dart` instead.
|
// Do not use kWindowResizeEdgeSize directly. Use `windowResizeEdgeSize` in `common.dart` instead.
|
||||||
const kWindowResizeEdgeSize = 5.0;
|
const kWindowResizeEdgeSize = 5.0;
|
||||||
const kWindowBorderWidth = 1.0;
|
final kWindowBorderWidth = isWindows ? 0.0 : 1.0;
|
||||||
const kDesktopMenuPadding = EdgeInsets.only(left: 12.0, right: 3.0);
|
const kDesktopMenuPadding = EdgeInsets.only(left: 12.0, right: 3.0);
|
||||||
const kFrameBorderRadius = 12.0;
|
const kFrameBorderRadius = 12.0;
|
||||||
const kFrameClipRRectBorderRadius = 12.0;
|
const kFrameClipRRectBorderRadius = 12.0;
|
||||||
@@ -575,4 +575,4 @@ extension WindowsTargetExt on int {
|
|||||||
WindowsTarget get windowsVersion => getWindowsTarget(this);
|
WindowsTarget get windowsVersion => getWindowsTarget(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
const kCheckSoftwareUpdateFinish = 'check_software_update_finish';
|
const kCheckSoftwareUpdateFinish = 'check_software_update_finish';
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ class _OnlineStatusWidgetState extends State<OnlineStatusWidget> {
|
|||||||
double? get height => bind.isIncomingOnly() ? null : em * 3;
|
double? get height => bind.isIncomingOnly() ? null : em * 3;
|
||||||
|
|
||||||
void onUsePublicServerGuide() {
|
void onUsePublicServerGuide() {
|
||||||
const url = "https://rustdesk.com/pricing.html";
|
const url = "https://rustdesk.com/pricing";
|
||||||
canLaunchUrlString(url).then((can) {
|
canLaunchUrlString(url).then((can) {
|
||||||
if (can) {
|
if (can) {
|
||||||
launchUrlString(url);
|
launchUrlString(url);
|
||||||
@@ -179,6 +179,9 @@ class _OnlineStatusWidgetState extends State<OnlineStatusWidget> {
|
|||||||
stateGlobal.svcStatus.value = SvcStatus.notReady;
|
stateGlobal.svcStatus.value = SvcStatus.notReady;
|
||||||
}
|
}
|
||||||
_svcIsUsingPublicServer.value = await bind.mainIsUsingPublicServer();
|
_svcIsUsingPublicServer.value = await bind.mainIsUsingPublicServer();
|
||||||
|
try {
|
||||||
|
stateGlobal.videoConnCount.value = status['video_conn_count'] as int;
|
||||||
|
} catch (_) {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -197,18 +200,21 @@ class _ConnectionPageState extends State<ConnectionPage>
|
|||||||
final _idController = IDTextEditingController();
|
final _idController = IDTextEditingController();
|
||||||
|
|
||||||
final RxBool _idInputFocused = false.obs;
|
final RxBool _idInputFocused = false.obs;
|
||||||
|
final FocusNode _idFocusNode = FocusNode();
|
||||||
|
final TextEditingController _idEditingController = TextEditingController();
|
||||||
|
|
||||||
bool isWindowMinimized = false;
|
bool isWindowMinimized = false;
|
||||||
List<Peer> peers = [];
|
|
||||||
|
|
||||||
bool isPeersLoading = false;
|
final AllPeersLoader _allPeersLoader = AllPeersLoader();
|
||||||
bool isPeersLoaded = false;
|
|
||||||
// https://github.com/flutter/flutter/issues/157244
|
// https://github.com/flutter/flutter/issues/157244
|
||||||
Iterable<Peer> _autocompleteOpts = [];
|
Iterable<Peer> _autocompleteOpts = [];
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
_allPeersLoader.init(setState);
|
||||||
|
_idFocusNode.addListener(onFocusChanged);
|
||||||
if (_idController.text.isEmpty) {
|
if (_idController.text.isEmpty) {
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
||||||
final lastRemoteId = await bind.mainGetLastRemoteId();
|
final lastRemoteId = await bind.mainGetLastRemoteId();
|
||||||
@@ -219,6 +225,7 @@ class _ConnectionPageState extends State<ConnectionPage>
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
Get.put<TextEditingController>(_idEditingController);
|
||||||
Get.put<IDTextEditingController>(_idController);
|
Get.put<IDTextEditingController>(_idController);
|
||||||
windowManager.addListener(this);
|
windowManager.addListener(this);
|
||||||
}
|
}
|
||||||
@@ -227,6 +234,10 @@ class _ConnectionPageState extends State<ConnectionPage>
|
|||||||
void dispose() {
|
void dispose() {
|
||||||
_idController.dispose();
|
_idController.dispose();
|
||||||
windowManager.removeListener(this);
|
windowManager.removeListener(this);
|
||||||
|
_allPeersLoader.clear();
|
||||||
|
_idFocusNode.removeListener(onFocusChanged);
|
||||||
|
_idFocusNode.dispose();
|
||||||
|
_idEditingController.dispose();
|
||||||
if (Get.isRegistered<IDTextEditingController>()) {
|
if (Get.isRegistered<IDTextEditingController>()) {
|
||||||
Get.delete<IDTextEditingController>();
|
Get.delete<IDTextEditingController>();
|
||||||
}
|
}
|
||||||
@@ -270,6 +281,20 @@ class _ConnectionPageState extends State<ConnectionPage>
|
|||||||
bind.mainOnMainWindowClose();
|
bind.mainOnMainWindowClose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void onFocusChanged() {
|
||||||
|
_idInputFocused.value = _idFocusNode.hasFocus;
|
||||||
|
if (_idFocusNode.hasFocus) {
|
||||||
|
if (_allPeersLoader.needLoad) {
|
||||||
|
_allPeersLoader.getAllPeers();
|
||||||
|
}
|
||||||
|
|
||||||
|
final textLength = _idEditingController.value.text.length;
|
||||||
|
// Select all to facilitate removing text, just following the behavior of address input of chrome.
|
||||||
|
_idEditingController.selection =
|
||||||
|
TextSelection(baseOffset: 0, extentOffset: textLength);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final isOutgoingOnly = bind.isOutgoingOnly();
|
final isOutgoingOnly = bind.isOutgoingOnly();
|
||||||
@@ -301,18 +326,6 @@ class _ConnectionPageState extends State<ConnectionPage>
|
|||||||
connect(context, id, isFileTransfer: isFileTransfer);
|
connect(context, id, isFileTransfer: isFileTransfer);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _fetchPeers() async {
|
|
||||||
setState(() {
|
|
||||||
isPeersLoading = true;
|
|
||||||
});
|
|
||||||
await Future.delayed(Duration(milliseconds: 100));
|
|
||||||
peers = await getAllPeers();
|
|
||||||
setState(() {
|
|
||||||
isPeersLoading = false;
|
|
||||||
isPeersLoaded = true;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// UI for the remote ID TextField.
|
/// UI for the remote ID TextField.
|
||||||
/// Search for a peer.
|
/// Search for a peer.
|
||||||
Widget _buildRemoteIDTextField(BuildContext context) {
|
Widget _buildRemoteIDTextField(BuildContext context) {
|
||||||
@@ -329,11 +342,12 @@ class _ConnectionPageState extends State<ConnectionPage>
|
|||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Autocomplete<Peer>(
|
child: RawAutocomplete<Peer>(
|
||||||
optionsBuilder: (TextEditingValue textEditingValue) {
|
optionsBuilder: (TextEditingValue textEditingValue) {
|
||||||
if (textEditingValue.text == '') {
|
if (textEditingValue.text == '') {
|
||||||
_autocompleteOpts = const Iterable<Peer>.empty();
|
_autocompleteOpts = const Iterable<Peer>.empty();
|
||||||
} else if (peers.isEmpty && !isPeersLoaded) {
|
} else if (_allPeersLoader.peers.isEmpty &&
|
||||||
|
!_allPeersLoader.isPeersLoaded) {
|
||||||
Peer emptyPeer = Peer(
|
Peer emptyPeer = Peer(
|
||||||
id: '',
|
id: '',
|
||||||
username: '',
|
username: '',
|
||||||
@@ -347,6 +361,7 @@ class _ConnectionPageState extends State<ConnectionPage>
|
|||||||
rdpPort: '',
|
rdpPort: '',
|
||||||
rdpUsername: '',
|
rdpUsername: '',
|
||||||
loginName: '',
|
loginName: '',
|
||||||
|
device_group_name: '',
|
||||||
);
|
);
|
||||||
_autocompleteOpts = [emptyPeer];
|
_autocompleteOpts = [emptyPeer];
|
||||||
} else {
|
} else {
|
||||||
@@ -359,7 +374,7 @@ class _ConnectionPageState extends State<ConnectionPage>
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
String textToFind = textEditingValue.text.toLowerCase();
|
String textToFind = textEditingValue.text.toLowerCase();
|
||||||
_autocompleteOpts = peers
|
_autocompleteOpts = _allPeersLoader.peers
|
||||||
.where((peer) =>
|
.where((peer) =>
|
||||||
peer.id.toLowerCase().contains(textToFind) ||
|
peer.id.toLowerCase().contains(textToFind) ||
|
||||||
peer.username
|
peer.username
|
||||||
@@ -373,25 +388,16 @@ class _ConnectionPageState extends State<ConnectionPage>
|
|||||||
}
|
}
|
||||||
return _autocompleteOpts;
|
return _autocompleteOpts;
|
||||||
},
|
},
|
||||||
|
focusNode: _idFocusNode,
|
||||||
|
textEditingController: _idEditingController,
|
||||||
fieldViewBuilder: (
|
fieldViewBuilder: (
|
||||||
BuildContext context,
|
BuildContext context,
|
||||||
TextEditingController fieldTextEditingController,
|
TextEditingController fieldTextEditingController,
|
||||||
FocusNode fieldFocusNode,
|
FocusNode fieldFocusNode,
|
||||||
VoidCallback onFieldSubmitted,
|
VoidCallback onFieldSubmitted,
|
||||||
) {
|
) {
|
||||||
fieldTextEditingController.text = _idController.text;
|
updateTextAndPreserveSelection(
|
||||||
Get.put<TextEditingController>(fieldTextEditingController);
|
fieldTextEditingController, _idController.text);
|
||||||
fieldFocusNode.addListener(() async {
|
|
||||||
_idInputFocused.value = fieldFocusNode.hasFocus;
|
|
||||||
if (fieldFocusNode.hasFocus && !isPeersLoading) {
|
|
||||||
_fetchPeers();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
final textLength =
|
|
||||||
fieldTextEditingController.value.text.length;
|
|
||||||
// select all to facilitate removing text, just following the behavior of address input of chrome
|
|
||||||
fieldTextEditingController.selection =
|
|
||||||
TextSelection(baseOffset: 0, extentOffset: textLength);
|
|
||||||
return Obx(() => TextField(
|
return Obx(() => TextField(
|
||||||
autocorrect: false,
|
autocorrect: false,
|
||||||
enableSuggestions: false,
|
enableSuggestions: false,
|
||||||
@@ -421,7 +427,7 @@ class _ConnectionPageState extends State<ConnectionPage>
|
|||||||
onSubmitted: (_) {
|
onSubmitted: (_) {
|
||||||
onConnect();
|
onConnect();
|
||||||
},
|
},
|
||||||
));
|
).workaroundFreezeLinuxMint());
|
||||||
},
|
},
|
||||||
onSelected: (option) {
|
onSelected: (option) {
|
||||||
setState(() {
|
setState(() {
|
||||||
@@ -464,7 +470,8 @@ class _ConnectionPageState extends State<ConnectionPage>
|
|||||||
maxHeight: maxHeight,
|
maxHeight: maxHeight,
|
||||||
maxWidth: 319,
|
maxWidth: 319,
|
||||||
),
|
),
|
||||||
child: peers.isEmpty && isPeersLoading
|
child: _allPeersLoader.peers.isEmpty &&
|
||||||
|
!_allPeersLoader.isPeersLoaded
|
||||||
? Container(
|
? Container(
|
||||||
height: 80,
|
height: 80,
|
||||||
child: Center(
|
child: Center(
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ class DesktopHomePage extends StatefulWidget {
|
|||||||
const borderColor = Color(0xFF2F65BA);
|
const borderColor = Color(0xFF2F65BA);
|
||||||
|
|
||||||
class _DesktopHomePageState extends State<DesktopHomePage>
|
class _DesktopHomePageState extends State<DesktopHomePage>
|
||||||
with AutomaticKeepAliveClientMixin {
|
with AutomaticKeepAliveClientMixin, WidgetsBindingObserver {
|
||||||
final _leftPaneScrollController = ScrollController();
|
final _leftPaneScrollController = ScrollController();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -51,6 +51,7 @@ class _DesktopHomePageState extends State<DesktopHomePage>
|
|||||||
bool isCardClosed = false;
|
bool isCardClosed = false;
|
||||||
|
|
||||||
final RxBool _editHover = false.obs;
|
final RxBool _editHover = false.obs;
|
||||||
|
final RxBool _block = false.obs;
|
||||||
|
|
||||||
final GlobalKey _childKey = GlobalKey();
|
final GlobalKey _childKey = GlobalKey();
|
||||||
|
|
||||||
@@ -58,14 +59,20 @@ class _DesktopHomePageState extends State<DesktopHomePage>
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
super.build(context);
|
super.build(context);
|
||||||
final isIncomingOnly = bind.isIncomingOnly();
|
final isIncomingOnly = bind.isIncomingOnly();
|
||||||
return Row(
|
return _buildBlock(
|
||||||
|
child: Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
buildLeftPane(context),
|
buildLeftPane(context),
|
||||||
if (!isIncomingOnly) const VerticalDivider(width: 1),
|
if (!isIncomingOnly) const VerticalDivider(width: 1),
|
||||||
if (!isIncomingOnly) Expanded(child: buildRightPane(context)),
|
if (!isIncomingOnly) Expanded(child: buildRightPane(context)),
|
||||||
],
|
],
|
||||||
);
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildBlock({required Widget child}) {
|
||||||
|
return buildRemoteBlock(
|
||||||
|
block: _block, mask: true, use: canBeBlocked, child: child);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget buildLeftPane(BuildContext context) {
|
Widget buildLeftPane(BuildContext context) {
|
||||||
@@ -230,7 +237,7 @@ class _DesktopHomePageState extends State<DesktopHomePage>
|
|||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 22,
|
fontSize: 22,
|
||||||
),
|
),
|
||||||
),
|
).workaroundFreezeLinuxMint(),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
@@ -326,7 +333,7 @@ class _DesktopHomePageState extends State<DesktopHomePage>
|
|||||||
EdgeInsets.only(top: 14, bottom: 10),
|
EdgeInsets.only(top: 14, bottom: 10),
|
||||||
),
|
),
|
||||||
style: TextStyle(fontSize: 15),
|
style: TextStyle(fontSize: 15),
|
||||||
),
|
).workaroundFreezeLinuxMint(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (showOneTime)
|
if (showOneTime)
|
||||||
@@ -423,7 +430,7 @@ class _DesktopHomePageState extends State<DesktopHomePage>
|
|||||||
bind.mainUriPrefixSync().contains('rustdesk')) {
|
bind.mainUriPrefixSync().contains('rustdesk')) {
|
||||||
return buildInstallCard(
|
return buildInstallCard(
|
||||||
"Status",
|
"Status",
|
||||||
"There is a newer version of ${bind.mainGetAppNameSync()} ${bind.mainGetNewVersion()} available.",
|
"${translate("new-version-of-{${bind.mainGetAppNameSync()}}-tip")} (${bind.mainGetNewVersion()}).",
|
||||||
"Click to download", () async {
|
"Click to download", () async {
|
||||||
final Uri url = Uri.parse('https://rustdesk.com/download');
|
final Uri url = Uri.parse('https://rustdesk.com/download');
|
||||||
await launchUrl(url);
|
await launchUrl(url);
|
||||||
@@ -805,6 +812,7 @@ class _DesktopHomePageState extends State<DesktopHomePage>
|
|||||||
_updateWindowSize();
|
_updateWindowSize();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
WidgetsBinding.instance.addObserver(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
_updateWindowSize() {
|
_updateWindowSize() {
|
||||||
@@ -826,13 +834,18 @@ class _DesktopHomePageState extends State<DesktopHomePage>
|
|||||||
_uniLinksSubscription?.cancel();
|
_uniLinksSubscription?.cancel();
|
||||||
Get.delete<RxBool>(tag: 'stop-service');
|
Get.delete<RxBool>(tag: 'stop-service');
|
||||||
_updateTimer?.cancel();
|
_updateTimer?.cancel();
|
||||||
if (!bind.isCustomClient()) {
|
WidgetsBinding.instance.removeObserver(this);
|
||||||
platformFFI.unregisterEventHandler(
|
|
||||||
kCheckSoftwareUpdateFinish, kCheckSoftwareUpdateFinish);
|
|
||||||
}
|
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didChangeAppLifecycleState(AppLifecycleState state) {
|
||||||
|
super.didChangeAppLifecycleState(state);
|
||||||
|
if (state == AppLifecycleState.resumed) {
|
||||||
|
shouldBeBlocked(_block, canBeBlocked);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Widget buildPluginEntry() {
|
Widget buildPluginEntry() {
|
||||||
final entries = PluginUiManager.instance.entries.entries;
|
final entries = PluginUiManager.instance.entries.entries;
|
||||||
return Offstage(
|
return Offstage(
|
||||||
@@ -923,7 +936,7 @@ void setPasswordDialog({VoidCallback? notEmptyCallback}) async {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
maxLength: maxLength,
|
maxLength: maxLength,
|
||||||
),
|
).workaroundFreezeLinuxMint(),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -950,7 +963,7 @@ void setPasswordDialog({VoidCallback? notEmptyCallback}) async {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
maxLength: maxLength,
|
maxLength: maxLength,
|
||||||
),
|
).workaroundFreezeLinuxMint(),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -107,13 +107,20 @@ class DesktopSettingPage extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _DesktopSettingPageState extends State<DesktopSettingPage>
|
class _DesktopSettingPageState extends State<DesktopSettingPage>
|
||||||
with TickerProviderStateMixin, AutomaticKeepAliveClientMixin {
|
with
|
||||||
|
TickerProviderStateMixin,
|
||||||
|
AutomaticKeepAliveClientMixin,
|
||||||
|
WidgetsBindingObserver {
|
||||||
late PageController controller;
|
late PageController controller;
|
||||||
late Rx<SettingsTabKey> selectedTab;
|
late Rx<SettingsTabKey> selectedTab;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool get wantKeepAlive => true;
|
bool get wantKeepAlive => true;
|
||||||
|
|
||||||
|
final RxBool _block = false.obs;
|
||||||
|
final RxBool _canBeBlocked = false.obs;
|
||||||
|
Timer? _videoConnTimer;
|
||||||
|
|
||||||
_DesktopSettingPageState(SettingsTabKey initialTabkey) {
|
_DesktopSettingPageState(SettingsTabKey initialTabkey) {
|
||||||
var initialIndex = DesktopSettingPage.tabKeys.indexOf(initialTabkey);
|
var initialIndex = DesktopSettingPage.tabKeys.indexOf(initialTabkey);
|
||||||
if (initialIndex == -1) {
|
if (initialIndex == -1) {
|
||||||
@@ -133,11 +140,34 @@ class _DesktopSettingPageState extends State<DesktopSettingPage>
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didChangeAppLifecycleState(AppLifecycleState state) {
|
||||||
|
super.didChangeAppLifecycleState(state);
|
||||||
|
if (state == AppLifecycleState.resumed) {
|
||||||
|
shouldBeBlocked(_block, canBeBlocked);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
WidgetsBinding.instance.addObserver(this);
|
||||||
|
_videoConnTimer =
|
||||||
|
periodic_immediate(Duration(milliseconds: 1000), () async {
|
||||||
|
if (!mounted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_canBeBlocked.value = await canBeBlocked();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
super.dispose();
|
super.dispose();
|
||||||
Get.delete<PageController>(tag: _kSettingPageControllerTag);
|
Get.delete<PageController>(tag: _kSettingPageControllerTag);
|
||||||
Get.delete<RxInt>(tag: _kSettingPageTabKeyTag);
|
Get.delete<RxInt>(tag: _kSettingPageTabKeyTag);
|
||||||
|
WidgetsBinding.instance.removeObserver(this);
|
||||||
|
_videoConnTimer?.cancel();
|
||||||
}
|
}
|
||||||
|
|
||||||
List<_TabInfo> _settingTabs() {
|
List<_TabInfo> _settingTabs() {
|
||||||
@@ -207,12 +237,35 @@ class _DesktopSettingPageState extends State<DesktopSettingPage>
|
|||||||
return children;
|
return children;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget _buildBlock({required List<Widget> children}) {
|
||||||
|
// check both mouseMoveTime and videoConnCount
|
||||||
|
return Obx(() {
|
||||||
|
final videoConnBlock =
|
||||||
|
_canBeBlocked.value && stateGlobal.videoConnCount > 0;
|
||||||
|
return Stack(children: [
|
||||||
|
buildRemoteBlock(
|
||||||
|
block: _block,
|
||||||
|
mask: false,
|
||||||
|
use: canBeBlocked,
|
||||||
|
child: preventMouseKeyBuilder(
|
||||||
|
child: Row(children: children),
|
||||||
|
block: videoConnBlock,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (videoConnBlock)
|
||||||
|
Container(
|
||||||
|
color: Colors.black.withOpacity(0.5),
|
||||||
|
)
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
super.build(context);
|
super.build(context);
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: Theme.of(context).colorScheme.background,
|
backgroundColor: Theme.of(context).colorScheme.background,
|
||||||
body: Row(
|
body: _buildBlock(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
SizedBox(
|
SizedBox(
|
||||||
width: _kTabWidth,
|
width: _kTabWidth,
|
||||||
@@ -554,7 +607,6 @@ class _GeneralState extends State<_General> {
|
|||||||
bool user_dir_exists = await Directory(user_dir).exists();
|
bool user_dir_exists = await Directory(user_dir).exists();
|
||||||
bool root_dir_exists =
|
bool root_dir_exists =
|
||||||
showRootDir ? await Directory(root_dir).exists() : false;
|
showRootDir ? await Directory(root_dir).exists() : false;
|
||||||
// canLaunchUrl blocked on windows portable, user SYSTEM
|
|
||||||
return {
|
return {
|
||||||
'user_dir': user_dir,
|
'user_dir': user_dir,
|
||||||
'root_dir': root_dir,
|
'root_dir': root_dir,
|
||||||
@@ -706,8 +758,8 @@ class _SafetyState extends State<_Safety> with AutomaticKeepAliveClientMixin {
|
|||||||
locked = false;
|
locked = false;
|
||||||
setState(() => {});
|
setState(() => {});
|
||||||
}),
|
}),
|
||||||
AbsorbPointer(
|
preventMouseKeyBuilder(
|
||||||
absorbing: locked,
|
block: locked,
|
||||||
child: Column(children: [
|
child: Column(children: [
|
||||||
permissions(context),
|
permissions(context),
|
||||||
password(context),
|
password(context),
|
||||||
@@ -1136,7 +1188,7 @@ class _SafetyState extends State<_Safety> with AutomaticKeepAliveClientMixin {
|
|||||||
contentPadding:
|
contentPadding:
|
||||||
EdgeInsets.symmetric(vertical: 12, horizontal: 12),
|
EdgeInsets.symmetric(vertical: 12, horizontal: 12),
|
||||||
),
|
),
|
||||||
).marginOnly(right: 15),
|
).workaroundFreezeLinuxMint().marginOnly(right: 15),
|
||||||
),
|
),
|
||||||
Obx(() => ElevatedButton(
|
Obx(() => ElevatedButton(
|
||||||
onPressed: applyEnabled.value &&
|
onPressed: applyEnabled.value &&
|
||||||
@@ -1293,7 +1345,7 @@ class _SafetyState extends State<_Safety> with AutomaticKeepAliveClientMixin {
|
|||||||
contentPadding:
|
contentPadding:
|
||||||
EdgeInsets.symmetric(vertical: 12, horizontal: 12),
|
EdgeInsets.symmetric(vertical: 12, horizontal: 12),
|
||||||
),
|
),
|
||||||
).marginOnly(right: 15),
|
).workaroundFreezeLinuxMint().marginOnly(right: 15),
|
||||||
),
|
),
|
||||||
Obx(() => ElevatedButton(
|
Obx(() => ElevatedButton(
|
||||||
onPressed:
|
onPressed:
|
||||||
@@ -1374,8 +1426,8 @@ class _NetworkState extends State<_Network> with AutomaticKeepAliveClientMixin {
|
|||||||
locked = false;
|
locked = false;
|
||||||
setState(() => {});
|
setState(() => {});
|
||||||
}),
|
}),
|
||||||
AbsorbPointer(
|
preventMouseKeyBuilder(
|
||||||
absorbing: locked,
|
block: locked,
|
||||||
child: Column(children: [
|
child: Column(children: [
|
||||||
network(context),
|
network(context),
|
||||||
]),
|
]),
|
||||||
@@ -2259,7 +2311,7 @@ _LabeledTextField(
|
|||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: disabledTextColor(context, enabled),
|
color: disabledTextColor(context, enabled),
|
||||||
),
|
),
|
||||||
),
|
).workaroundFreezeLinuxMint(),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -2438,7 +2490,7 @@ void changeSocks5Proxy() async {
|
|||||||
controller: proxyController,
|
controller: proxyController,
|
||||||
autofocus: true,
|
autofocus: true,
|
||||||
enabled: !isOptFixed,
|
enabled: !isOptFixed,
|
||||||
),
|
).workaroundFreezeLinuxMint(),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
).marginOnly(bottom: 8),
|
).marginOnly(bottom: 8),
|
||||||
@@ -2458,7 +2510,7 @@ void changeSocks5Proxy() async {
|
|||||||
labelText: isMobile ? translate('Username') : null,
|
labelText: isMobile ? translate('Username') : null,
|
||||||
),
|
),
|
||||||
enabled: !isOptFixed,
|
enabled: !isOptFixed,
|
||||||
),
|
).workaroundFreezeLinuxMint(),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
).marginOnly(bottom: 8),
|
).marginOnly(bottom: 8),
|
||||||
@@ -2484,7 +2536,7 @@ void changeSocks5Proxy() async {
|
|||||||
controller: pwdController,
|
controller: pwdController,
|
||||||
enabled: !isOptFixed,
|
enabled: !isOptFixed,
|
||||||
maxLength: bind.mainMaxEncryptLen(),
|
maxLength: bind.mainMaxEncryptLen(),
|
||||||
)),
|
).workaroundFreezeLinuxMint()),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -37,13 +37,9 @@ class DesktopTabPage extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _DesktopTabPageState extends State<DesktopTabPage>
|
class _DesktopTabPageState extends State<DesktopTabPage> {
|
||||||
with WidgetsBindingObserver {
|
|
||||||
final tabController = DesktopTabController(tabType: DesktopTabType.main);
|
final tabController = DesktopTabController(tabType: DesktopTabType.main);
|
||||||
|
|
||||||
final RxBool _block = false.obs;
|
|
||||||
// bool mouseIn = false;
|
|
||||||
|
|
||||||
_DesktopTabPageState() {
|
_DesktopTabPageState() {
|
||||||
RemoteCountState.init();
|
RemoteCountState.init();
|
||||||
Get.put<DesktopTabController>(tabController);
|
Get.put<DesktopTabController>(tabController);
|
||||||
@@ -69,19 +65,10 @@ class _DesktopTabPageState extends State<DesktopTabPage>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
void didChangeAppLifecycleState(AppLifecycleState state) {
|
|
||||||
super.didChangeAppLifecycleState(state);
|
|
||||||
if (state == AppLifecycleState.resumed) {
|
|
||||||
shouldBeBlocked(_block, canBeBlocked);
|
|
||||||
} else if (state == AppLifecycleState.inactive) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
// HardwareKeyboard.instance.addHandler(_handleKeyEvent);
|
// HardwareKeyboard.instance.addHandler(_handleKeyEvent);
|
||||||
WidgetsBinding.instance.addObserver(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -97,7 +84,6 @@ class _DesktopTabPageState extends State<DesktopTabPage>
|
|||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
// HardwareKeyboard.instance.removeHandler(_handleKeyEvent);
|
// HardwareKeyboard.instance.removeHandler(_handleKeyEvent);
|
||||||
WidgetsBinding.instance.removeObserver(this);
|
|
||||||
Get.delete<DesktopTabController>();
|
Get.delete<DesktopTabController>();
|
||||||
|
|
||||||
super.dispose();
|
super.dispose();
|
||||||
@@ -119,7 +105,6 @@ class _DesktopTabPageState extends State<DesktopTabPage>
|
|||||||
isClose: false,
|
isClose: false,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
blockTab: _block,
|
|
||||||
)));
|
)));
|
||||||
return isMacOS || kUseCompatibleUiMode
|
return isMacOS || kUseCompatibleUiMode
|
||||||
? tabWidget
|
? tabWidget
|
||||||
|
|||||||
@@ -768,7 +768,7 @@ class _FileManagerViewState extends State<FileManagerView> {
|
|||||||
),
|
),
|
||||||
controller: name,
|
controller: name,
|
||||||
autofocus: true,
|
autofocus: true,
|
||||||
),
|
).workaroundFreezeLinuxMint(),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
actions: [
|
actions: [
|
||||||
@@ -1657,7 +1657,7 @@ class _FileManagerViewState extends State<FileManagerView> {
|
|||||||
onChanged: _locationStatus.value == LocationStatus.fileSearchBar
|
onChanged: _locationStatus.value == LocationStatus.fileSearchBar
|
||||||
? (searchText) => onSearchText(searchText, isLocal)
|
? (searchText) => onSearchText(searchText, isLocal)
|
||||||
: null,
|
: null,
|
||||||
),
|
).workaroundFreezeLinuxMint(),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -103,11 +103,13 @@ class _FileManagerTabPageState extends State<FileManagerTabPage> {
|
|||||||
));
|
));
|
||||||
final tabWidget = isLinux
|
final tabWidget = isLinux
|
||||||
? buildVirtualWindowFrame(context, child)
|
? buildVirtualWindowFrame(context, child)
|
||||||
: Container(
|
: workaroundWindowBorder(
|
||||||
decoration: BoxDecoration(
|
context,
|
||||||
border: Border.all(color: MyTheme.color(context).border!)),
|
Container(
|
||||||
child: child,
|
decoration: BoxDecoration(
|
||||||
);
|
border: Border.all(color: MyTheme.color(context).border!)),
|
||||||
|
child: child,
|
||||||
|
));
|
||||||
return isMacOS || kUseCompatibleUiMode
|
return isMacOS || kUseCompatibleUiMode
|
||||||
? tabWidget
|
? tabWidget
|
||||||
: SubWindowDragToResizeArea(
|
: SubWindowDragToResizeArea(
|
||||||
|
|||||||
@@ -147,7 +147,7 @@ class _InstallPageBodyState extends State<_InstallPageBody>
|
|||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
contentPadding: EdgeInsets.all(0.75 * em),
|
contentPadding: EdgeInsets.all(0.75 * em),
|
||||||
),
|
),
|
||||||
).marginOnly(right: 10),
|
).workaroundFreezeLinuxMint().marginOnly(right: 10),
|
||||||
),
|
),
|
||||||
Obx(
|
Obx(
|
||||||
() => OutlinedButton.icon(
|
() => OutlinedButton.icon(
|
||||||
|
|||||||
@@ -238,7 +238,7 @@ class _PortForwardPageState extends State<PortForwardPage>
|
|||||||
inputFormatters: inputFormatters,
|
inputFormatters: inputFormatters,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
hintText: hint,
|
hintText: hint,
|
||||||
))),
|
)).workaroundFreezeLinuxMint()),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -118,11 +118,13 @@ class _PortForwardTabPageState extends State<PortForwardTabPage> {
|
|||||||
backgroundColor: Theme.of(context).colorScheme.background,
|
backgroundColor: Theme.of(context).colorScheme.background,
|
||||||
body: child),
|
body: child),
|
||||||
)
|
)
|
||||||
: Container(
|
: workaroundWindowBorder(
|
||||||
decoration: BoxDecoration(
|
context,
|
||||||
border: Border.all(color: MyTheme.color(context).border!)),
|
Container(
|
||||||
child: child,
|
decoration: BoxDecoration(
|
||||||
);
|
border: Border.all(color: MyTheme.color(context).border!)),
|
||||||
|
child: child,
|
||||||
|
));
|
||||||
return isMacOS || kUseCompatibleUiMode
|
return isMacOS || kUseCompatibleUiMode
|
||||||
? tabWidget
|
? tabWidget
|
||||||
: Obx(
|
: Obx(
|
||||||
|
|||||||
@@ -212,14 +212,16 @@ class _ConnectionTabPageState extends State<ConnectionTabPage> {
|
|||||||
);
|
);
|
||||||
final tabWidget = isLinux
|
final tabWidget = isLinux
|
||||||
? buildVirtualWindowFrame(context, child)
|
? buildVirtualWindowFrame(context, child)
|
||||||
: Obx(() => Container(
|
: workaroundWindowBorder(
|
||||||
decoration: BoxDecoration(
|
context,
|
||||||
border: Border.all(
|
Obx(() => Container(
|
||||||
color: MyTheme.color(context).border!,
|
decoration: BoxDecoration(
|
||||||
width: stateGlobal.windowBorderWidth.value),
|
border: Border.all(
|
||||||
),
|
color: MyTheme.color(context).border!,
|
||||||
child: child,
|
width: stateGlobal.windowBorderWidth.value),
|
||||||
));
|
),
|
||||||
|
child: child,
|
||||||
|
)));
|
||||||
return isMacOS || kUseCompatibleUiMode
|
return isMacOS || kUseCompatibleUiMode
|
||||||
? tabWidget
|
? tabWidget
|
||||||
: Obx(() => SubWindowDragToResizeArea(
|
: Obx(() => SubWindowDragToResizeArea(
|
||||||
|
|||||||
@@ -88,12 +88,14 @@ class _DesktopServerPageState extends State<DesktopServerPage>
|
|||||||
);
|
);
|
||||||
return isLinux
|
return isLinux
|
||||||
? buildVirtualWindowFrame(context, body)
|
? buildVirtualWindowFrame(context, body)
|
||||||
: Container(
|
: workaroundWindowBorder(
|
||||||
decoration: BoxDecoration(
|
context,
|
||||||
border:
|
Container(
|
||||||
Border.all(color: MyTheme.color(context).border!)),
|
decoration: BoxDecoration(
|
||||||
child: body,
|
border:
|
||||||
);
|
Border.all(color: MyTheme.color(context).border!)),
|
||||||
|
child: body,
|
||||||
|
));
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -110,7 +112,8 @@ class ConnectionManager extends StatefulWidget {
|
|||||||
|
|
||||||
class ConnectionManagerState extends State<ConnectionManager>
|
class ConnectionManagerState extends State<ConnectionManager>
|
||||||
with WidgetsBindingObserver {
|
with WidgetsBindingObserver {
|
||||||
final RxBool _block = false.obs;
|
final RxBool _controlPageBlock = false.obs;
|
||||||
|
final RxBool _sidePageBlock = false.obs;
|
||||||
|
|
||||||
ConnectionManagerState() {
|
ConnectionManagerState() {
|
||||||
gFFI.serverModel.tabController.onSelected = (client_id_str) {
|
gFFI.serverModel.tabController.onSelected = (client_id_str) {
|
||||||
@@ -139,7 +142,8 @@ class ConnectionManagerState extends State<ConnectionManager>
|
|||||||
super.didChangeAppLifecycleState(state);
|
super.didChangeAppLifecycleState(state);
|
||||||
if (state == AppLifecycleState.resumed) {
|
if (state == AppLifecycleState.resumed) {
|
||||||
if (!allowRemoteCMModification()) {
|
if (!allowRemoteCMModification()) {
|
||||||
shouldBeBlocked(_block, null);
|
shouldBeBlocked(_controlPageBlock, null);
|
||||||
|
shouldBeBlocked(_sidePageBlock, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -192,7 +196,6 @@ class ConnectionManagerState extends State<ConnectionManager>
|
|||||||
selectedBorderColor: MyTheme.accent,
|
selectedBorderColor: MyTheme.accent,
|
||||||
maxLabelWidth: 100,
|
maxLabelWidth: 100,
|
||||||
tail: null, //buildScrollJumper(),
|
tail: null, //buildScrollJumper(),
|
||||||
blockTab: allowRemoteCMModification() ? null : _block,
|
|
||||||
tabBuilder: (key, icon, label, themeConf) {
|
tabBuilder: (key, icon, label, themeConf) {
|
||||||
final client = serverModel.clients
|
final client = serverModel.clients
|
||||||
.firstWhereOrNull((client) => client.id.toString() == key);
|
.firstWhereOrNull((client) => client.id.toString() == key);
|
||||||
@@ -237,13 +240,20 @@ class ConnectionManagerState extends State<ConnectionManager>
|
|||||||
? buildSidePage()
|
? buildSidePage()
|
||||||
: buildRemoteBlock(
|
: buildRemoteBlock(
|
||||||
child: buildSidePage(),
|
child: buildSidePage(),
|
||||||
block: _block,
|
block: _sidePageBlock,
|
||||||
mask: true),
|
mask: true),
|
||||||
)),
|
)),
|
||||||
SizedBox(
|
SizedBox(
|
||||||
width: realClosedWidth,
|
width: realClosedWidth,
|
||||||
child:
|
child: SizedBox(
|
||||||
SizedBox(width: realClosedWidth, child: pageView)),
|
width: realClosedWidth,
|
||||||
|
child: allowRemoteCMModification()
|
||||||
|
? pageView
|
||||||
|
: buildRemoteBlock(
|
||||||
|
child: _buildKeyEventBlock(pageView),
|
||||||
|
block: _controlPageBlock,
|
||||||
|
mask: false,
|
||||||
|
))),
|
||||||
]);
|
]);
|
||||||
return Container(
|
return Container(
|
||||||
color: Theme.of(context).scaffoldBackgroundColor,
|
color: Theme.of(context).scaffoldBackgroundColor,
|
||||||
@@ -268,6 +278,10 @@ class ConnectionManagerState extends State<ConnectionManager>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget _buildKeyEventBlock(Widget child) {
|
||||||
|
return ExcludeFocus(child: child, excluding: true);
|
||||||
|
}
|
||||||
|
|
||||||
Widget buildTitleBar() {
|
Widget buildTitleBar() {
|
||||||
return SizedBox(
|
return SizedBox(
|
||||||
height: kDesktopRemoteTabBarHeight,
|
height: kDesktopRemoteTabBarHeight,
|
||||||
|
|||||||
@@ -1495,7 +1495,7 @@ class _ResolutionsMenuState extends State<_ResolutionsMenu> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
TextField _resolutionInput(TextEditingController controller) {
|
Widget _resolutionInput(TextEditingController controller) {
|
||||||
return TextField(
|
return TextField(
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
border: InputBorder.none,
|
border: InputBorder.none,
|
||||||
@@ -1509,7 +1509,7 @@ class _ResolutionsMenuState extends State<_ResolutionsMenu> {
|
|||||||
FilteringTextInputFormatter.allow(RegExp(r'[0-9]')),
|
FilteringTextInputFormatter.allow(RegExp(r'[0-9]')),
|
||||||
],
|
],
|
||||||
controller: controller,
|
controller: controller,
|
||||||
);
|
).workaroundFreezeLinuxMint();
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Widget> _supportedResolutionMenuButtons() => resolutions
|
List<Widget> _supportedResolutionMenuButtons() => resolutions
|
||||||
|
|||||||
@@ -246,7 +246,6 @@ class DesktopTab extends StatefulWidget {
|
|||||||
final Color? selectedTabBackgroundColor;
|
final Color? selectedTabBackgroundColor;
|
||||||
final Color? unSelectedTabBackgroundColor;
|
final Color? unSelectedTabBackgroundColor;
|
||||||
final Color? selectedBorderColor;
|
final Color? selectedBorderColor;
|
||||||
final RxBool? blockTab;
|
|
||||||
|
|
||||||
final DesktopTabController controller;
|
final DesktopTabController controller;
|
||||||
|
|
||||||
@@ -272,7 +271,6 @@ class DesktopTab extends StatefulWidget {
|
|||||||
this.selectedTabBackgroundColor,
|
this.selectedTabBackgroundColor,
|
||||||
this.unSelectedTabBackgroundColor,
|
this.unSelectedTabBackgroundColor,
|
||||||
this.selectedBorderColor,
|
this.selectedBorderColor,
|
||||||
this.blockTab,
|
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
static RxString tablabelGetter(String peerId) {
|
static RxString tablabelGetter(String peerId) {
|
||||||
@@ -311,7 +309,6 @@ class _DesktopTabState extends State<DesktopTab>
|
|||||||
Color? get unSelectedTabBackgroundColor =>
|
Color? get unSelectedTabBackgroundColor =>
|
||||||
widget.unSelectedTabBackgroundColor;
|
widget.unSelectedTabBackgroundColor;
|
||||||
Color? get selectedBorderColor => widget.selectedBorderColor;
|
Color? get selectedBorderColor => widget.selectedBorderColor;
|
||||||
RxBool? get blockTab => widget.blockTab;
|
|
||||||
DesktopTabController get controller => widget.controller;
|
DesktopTabController get controller => widget.controller;
|
||||||
RxList<String> get invisibleTabKeys => widget.invisibleTabKeys;
|
RxList<String> get invisibleTabKeys => widget.invisibleTabKeys;
|
||||||
Debouncer get _scrollDebounce => widget._scrollDebounce;
|
Debouncer get _scrollDebounce => widget._scrollDebounce;
|
||||||
@@ -533,21 +530,9 @@ class _DesktopTabState extends State<DesktopTab>
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildBlock({required Widget child}) {
|
|
||||||
if (blockTab != null) {
|
|
||||||
return buildRemoteBlock(
|
|
||||||
child: child,
|
|
||||||
block: blockTab!,
|
|
||||||
use: canBeBlocked,
|
|
||||||
mask: tabType == DesktopTabType.main);
|
|
||||||
} else {
|
|
||||||
return child;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
List<Widget> _tabWidgets = [];
|
List<Widget> _tabWidgets = [];
|
||||||
Widget _buildPageView() {
|
Widget _buildPageView() {
|
||||||
final child = _buildBlock(
|
final child = Container(
|
||||||
child: Obx(() => PageView(
|
child: Obx(() => PageView(
|
||||||
controller: state.value.pageController,
|
controller: state.value.pageController,
|
||||||
physics: NeverScrollableScrollPhysics(),
|
physics: NeverScrollableScrollPhysics(),
|
||||||
|
|||||||
@@ -133,7 +133,8 @@ void runMainApp(bool startService) async {
|
|||||||
runApp(App());
|
runApp(App());
|
||||||
|
|
||||||
// Set window option.
|
// Set window option.
|
||||||
WindowOptions windowOptions = getHiddenTitleBarWindowOptions();
|
WindowOptions windowOptions =
|
||||||
|
getHiddenTitleBarWindowOptions(isMainWindow: true);
|
||||||
windowManager.waitUntilReadyToShow(windowOptions, () async {
|
windowManager.waitUntilReadyToShow(windowOptions, () async {
|
||||||
// Restore the location of the main window before window hide or show.
|
// Restore the location of the main window before window hide or show.
|
||||||
await restoreWindowPosition(WindowType.Main);
|
await restoreWindowPosition(WindowType.Main);
|
||||||
@@ -354,7 +355,10 @@ void runInstallPage() async {
|
|||||||
}
|
}
|
||||||
|
|
||||||
WindowOptions getHiddenTitleBarWindowOptions(
|
WindowOptions getHiddenTitleBarWindowOptions(
|
||||||
{Size? size, bool center = false, bool? alwaysOnTop}) {
|
{bool isMainWindow = false,
|
||||||
|
Size? size,
|
||||||
|
bool center = false,
|
||||||
|
bool? alwaysOnTop}) {
|
||||||
var defaultTitleBarStyle = TitleBarStyle.hidden;
|
var defaultTitleBarStyle = TitleBarStyle.hidden;
|
||||||
// we do not hide titlebar on win7 because of the frame overflow.
|
// we do not hide titlebar on win7 because of the frame overflow.
|
||||||
if (kUseCompatibleUiMode) {
|
if (kUseCompatibleUiMode) {
|
||||||
@@ -363,7 +367,7 @@ WindowOptions getHiddenTitleBarWindowOptions(
|
|||||||
return WindowOptions(
|
return WindowOptions(
|
||||||
size: size,
|
size: size,
|
||||||
center: center,
|
center: center,
|
||||||
backgroundColor: Colors.transparent,
|
backgroundColor: (isMacOS && isMainWindow) ? null : Colors.transparent,
|
||||||
skipTaskbar: false,
|
skipTaskbar: false,
|
||||||
titleBarStyle: defaultTitleBarStyle,
|
titleBarStyle: defaultTitleBarStyle,
|
||||||
alwaysOnTop: alwaysOnTop,
|
alwaysOnTop: alwaysOnTop,
|
||||||
@@ -485,18 +489,10 @@ class _AppState extends State<App> with WidgetsBindingObserver {
|
|||||||
child = keyListenerBuilder(context, child);
|
child = keyListenerBuilder(context, child);
|
||||||
}
|
}
|
||||||
if (isLinux) {
|
if (isLinux) {
|
||||||
// `(!(isLinuxMateDesktop || isLinuxMint))` is not used here for clarity.
|
return buildVirtualWindowFrame(context, child);
|
||||||
// `isLinuxMint` will call ffi function.
|
} else {
|
||||||
if (!isLinuxMateDesktop) {
|
return workaroundWindowBorder(context, child);
|
||||||
if (!isLinuxMint) {
|
|
||||||
debugPrint(
|
|
||||||
'Linux distro is not linuxmint, and desktop is not mate, '
|
|
||||||
'so we build virtual window frame.');
|
|
||||||
child = buildVirtualWindowFrame(context, child);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return child;
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -41,10 +41,11 @@ class _ConnectionPageState extends State<ConnectionPage> {
|
|||||||
final _idController = IDTextEditingController();
|
final _idController = IDTextEditingController();
|
||||||
final RxBool _idEmpty = true.obs;
|
final RxBool _idEmpty = true.obs;
|
||||||
|
|
||||||
List<Peer> peers = [];
|
final FocusNode _idFocusNode = FocusNode();
|
||||||
|
final TextEditingController _idEditingController = TextEditingController();
|
||||||
|
|
||||||
|
final AllPeersLoader _allPeersLoader = AllPeersLoader();
|
||||||
|
|
||||||
bool isPeersLoading = false;
|
|
||||||
bool isPeersLoaded = false;
|
|
||||||
StreamSubscription? _uniLinksSubscription;
|
StreamSubscription? _uniLinksSubscription;
|
||||||
|
|
||||||
// https://github.com/flutter/flutter/issues/157244
|
// https://github.com/flutter/flutter/issues/157244
|
||||||
@@ -61,6 +62,8 @@ class _ConnectionPageState extends State<ConnectionPage> {
|
|||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
_allPeersLoader.init(setState);
|
||||||
|
_idFocusNode.addListener(onFocusChanged);
|
||||||
if (_idController.text.isEmpty) {
|
if (_idController.text.isEmpty) {
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
||||||
final lastRemoteId = await bind.mainGetLastRemoteId();
|
final lastRemoteId = await bind.mainGetLastRemoteId();
|
||||||
@@ -71,6 +74,7 @@ class _ConnectionPageState extends State<ConnectionPage> {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
Get.put<TextEditingController>(_idEditingController);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -80,7 +84,7 @@ class _ConnectionPageState extends State<ConnectionPage> {
|
|||||||
slivers: [
|
slivers: [
|
||||||
SliverList(
|
SliverList(
|
||||||
delegate: SliverChildListDelegate([
|
delegate: SliverChildListDelegate([
|
||||||
if (!bind.isCustomClient())
|
if (!bind.isCustomClient() && !isIOS)
|
||||||
Obx(() => _buildUpdateUI(stateGlobal.updateUrl.value)),
|
Obx(() => _buildUpdateUI(stateGlobal.updateUrl.value)),
|
||||||
_buildRemoteIDTextField(),
|
_buildRemoteIDTextField(),
|
||||||
])),
|
])),
|
||||||
@@ -99,6 +103,20 @@ class _ConnectionPageState extends State<ConnectionPage> {
|
|||||||
connect(context, id);
|
connect(context, id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void onFocusChanged() {
|
||||||
|
_idEmpty.value = _idEditingController.text.isEmpty;
|
||||||
|
if (_idFocusNode.hasFocus) {
|
||||||
|
if (_allPeersLoader.needLoad) {
|
||||||
|
_allPeersLoader.getAllPeers();
|
||||||
|
}
|
||||||
|
|
||||||
|
final textLength = _idEditingController.value.text.length;
|
||||||
|
// Select all to facilitate removing text, just following the behavior of address input of chrome.
|
||||||
|
_idEditingController.selection =
|
||||||
|
TextSelection(baseOffset: 0, extentOffset: textLength);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// UI for software update.
|
/// UI for software update.
|
||||||
/// If _updateUrl] is not empty, shows a button to update the software.
|
/// If _updateUrl] is not empty, shows a button to update the software.
|
||||||
Widget _buildUpdateUI(String updateUrl) {
|
Widget _buildUpdateUI(String updateUrl) {
|
||||||
@@ -107,7 +125,7 @@ class _ConnectionPageState extends State<ConnectionPage> {
|
|||||||
: InkWell(
|
: InkWell(
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
final url = 'https://rustdesk.com/download';
|
final url = 'https://rustdesk.com/download';
|
||||||
// https://pub.dev/packages/url_launcher#configuration
|
// https://pub.dev/packages/url_launcher#configuration
|
||||||
// https://developer.android.com/training/package-visibility/use-cases#open-urls-custom-tabs
|
// https://developer.android.com/training/package-visibility/use-cases#open-urls-custom-tabs
|
||||||
//
|
//
|
||||||
// `await launchUrl(Uri.parse(url))` can also run if skip
|
// `await launchUrl(Uri.parse(url))` can also run if skip
|
||||||
@@ -115,9 +133,7 @@ class _ConnectionPageState extends State<ConnectionPage> {
|
|||||||
// 2. `<action android:name="android.support.customtabs.action.CustomTabsService" />` in AndroidManifest.xml
|
// 2. `<action android:name="android.support.customtabs.action.CustomTabsService" />` in AndroidManifest.xml
|
||||||
//
|
//
|
||||||
// But it is better to add the check.
|
// But it is better to add the check.
|
||||||
if (await canLaunchUrl(Uri.parse(url))) {
|
await launchUrl(Uri.parse(url));
|
||||||
await launchUrl(Uri.parse(url));
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
child: Container(
|
child: Container(
|
||||||
alignment: AlignmentDirectional.center,
|
alignment: AlignmentDirectional.center,
|
||||||
@@ -129,18 +145,6 @@ class _ConnectionPageState extends State<ConnectionPage> {
|
|||||||
color: Colors.white, fontWeight: FontWeight.bold))));
|
color: Colors.white, fontWeight: FontWeight.bold))));
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _fetchPeers() async {
|
|
||||||
setState(() {
|
|
||||||
isPeersLoading = true;
|
|
||||||
});
|
|
||||||
await Future.delayed(Duration(milliseconds: 100));
|
|
||||||
peers = await getAllPeers();
|
|
||||||
setState(() {
|
|
||||||
isPeersLoading = false;
|
|
||||||
isPeersLoaded = true;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// UI for the remote ID TextField.
|
/// UI for the remote ID TextField.
|
||||||
/// Search for a peer and connect to it if the id exists.
|
/// Search for a peer and connect to it if the id exists.
|
||||||
Widget _buildRemoteIDTextField() {
|
Widget _buildRemoteIDTextField() {
|
||||||
@@ -158,11 +162,12 @@ class _ConnectionPageState extends State<ConnectionPage> {
|
|||||||
Expanded(
|
Expanded(
|
||||||
child: Container(
|
child: Container(
|
||||||
padding: const EdgeInsets.only(left: 16, right: 16),
|
padding: const EdgeInsets.only(left: 16, right: 16),
|
||||||
child: Autocomplete<Peer>(
|
child: RawAutocomplete<Peer>(
|
||||||
optionsBuilder: (TextEditingValue textEditingValue) {
|
optionsBuilder: (TextEditingValue textEditingValue) {
|
||||||
if (textEditingValue.text == '') {
|
if (textEditingValue.text == '') {
|
||||||
_autocompleteOpts = const Iterable<Peer>.empty();
|
_autocompleteOpts = const Iterable<Peer>.empty();
|
||||||
} else if (peers.isEmpty && !isPeersLoaded) {
|
} else if (_allPeersLoader.peers.isEmpty &&
|
||||||
|
!_allPeersLoader.isPeersLoaded) {
|
||||||
Peer emptyPeer = Peer(
|
Peer emptyPeer = Peer(
|
||||||
id: '',
|
id: '',
|
||||||
username: '',
|
username: '',
|
||||||
@@ -176,6 +181,7 @@ class _ConnectionPageState extends State<ConnectionPage> {
|
|||||||
rdpPort: '',
|
rdpPort: '',
|
||||||
rdpUsername: '',
|
rdpUsername: '',
|
||||||
loginName: '',
|
loginName: '',
|
||||||
|
device_group_name: '',
|
||||||
);
|
);
|
||||||
_autocompleteOpts = [emptyPeer];
|
_autocompleteOpts = [emptyPeer];
|
||||||
} else {
|
} else {
|
||||||
@@ -189,7 +195,7 @@ class _ConnectionPageState extends State<ConnectionPage> {
|
|||||||
}
|
}
|
||||||
String textToFind = textEditingValue.text.toLowerCase();
|
String textToFind = textEditingValue.text.toLowerCase();
|
||||||
|
|
||||||
_autocompleteOpts = peers
|
_autocompleteOpts = _allPeersLoader.peers
|
||||||
.where((peer) =>
|
.where((peer) =>
|
||||||
peer.id.toLowerCase().contains(textToFind) ||
|
peer.id.toLowerCase().contains(textToFind) ||
|
||||||
peer.username
|
peer.username
|
||||||
@@ -203,25 +209,14 @@ class _ConnectionPageState extends State<ConnectionPage> {
|
|||||||
}
|
}
|
||||||
return _autocompleteOpts;
|
return _autocompleteOpts;
|
||||||
},
|
},
|
||||||
|
focusNode: _idFocusNode,
|
||||||
|
textEditingController: _idEditingController,
|
||||||
fieldViewBuilder: (BuildContext context,
|
fieldViewBuilder: (BuildContext context,
|
||||||
TextEditingController fieldTextEditingController,
|
TextEditingController fieldTextEditingController,
|
||||||
FocusNode fieldFocusNode,
|
FocusNode fieldFocusNode,
|
||||||
VoidCallback onFieldSubmitted) {
|
VoidCallback onFieldSubmitted) {
|
||||||
fieldTextEditingController.text = _idController.text;
|
updateTextAndPreserveSelection(
|
||||||
Get.put<TextEditingController>(
|
fieldTextEditingController, _idController.text);
|
||||||
fieldTextEditingController);
|
|
||||||
fieldFocusNode.addListener(() async {
|
|
||||||
_idEmpty.value =
|
|
||||||
fieldTextEditingController.text.isEmpty;
|
|
||||||
if (fieldFocusNode.hasFocus && !isPeersLoading) {
|
|
||||||
_fetchPeers();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
final textLength =
|
|
||||||
fieldTextEditingController.value.text.length;
|
|
||||||
// select all to facilitate removing text, just following the behavior of address input of chrome
|
|
||||||
fieldTextEditingController.selection = TextSelection(
|
|
||||||
baseOffset: 0, extentOffset: textLength);
|
|
||||||
return AutoSizeTextField(
|
return AutoSizeTextField(
|
||||||
controller: fieldTextEditingController,
|
controller: fieldTextEditingController,
|
||||||
focusNode: fieldFocusNode,
|
focusNode: fieldFocusNode,
|
||||||
@@ -301,7 +296,9 @@ class _ConnectionPageState extends State<ConnectionPage> {
|
|||||||
maxHeight: maxHeight,
|
maxHeight: maxHeight,
|
||||||
maxWidth: 320,
|
maxWidth: 320,
|
||||||
),
|
),
|
||||||
child: peers.isEmpty && isPeersLoading
|
child: _allPeersLoader
|
||||||
|
.peers.isEmpty &&
|
||||||
|
!_allPeersLoader.isPeersLoaded
|
||||||
? Container(
|
? Container(
|
||||||
height: 80,
|
height: 80,
|
||||||
child: Center(
|
child: Center(
|
||||||
@@ -364,16 +361,16 @@ class _ConnectionPageState extends State<ConnectionPage> {
|
|||||||
void dispose() {
|
void dispose() {
|
||||||
_uniLinksSubscription?.cancel();
|
_uniLinksSubscription?.cancel();
|
||||||
_idController.dispose();
|
_idController.dispose();
|
||||||
|
_idFocusNode.removeListener(onFocusChanged);
|
||||||
|
_allPeersLoader.clear();
|
||||||
|
_idFocusNode.dispose();
|
||||||
|
_idEditingController.dispose();
|
||||||
if (Get.isRegistered<IDTextEditingController>()) {
|
if (Get.isRegistered<IDTextEditingController>()) {
|
||||||
Get.delete<IDTextEditingController>();
|
Get.delete<IDTextEditingController>();
|
||||||
}
|
}
|
||||||
if (Get.isRegistered<TextEditingController>()) {
|
if (Get.isRegistered<TextEditingController>()) {
|
||||||
Get.delete<TextEditingController>();
|
Get.delete<TextEditingController>();
|
||||||
}
|
}
|
||||||
if (!bind.isCustomClient()) {
|
|
||||||
platformFFI.unregisterEventHandler(
|
|
||||||
kCheckSoftwareUpdateFinish, kCheckSoftwareUpdateFinish);
|
|
||||||
}
|
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -225,7 +225,7 @@ class _FileManagerPageState extends State<FileManagerPage> {
|
|||||||
errorText: errorText,
|
errorText: errorText,
|
||||||
),
|
),
|
||||||
controller: name,
|
controller: name,
|
||||||
),
|
).workaroundFreezeLinuxMint(),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
actions: [
|
actions: [
|
||||||
|
|||||||
@@ -147,6 +147,19 @@ class _RemotePageState extends State<RemotePage> with WidgetsBindingObserver {
|
|||||||
gFFI.chatModel.onVoiceCallClosed("End connetion");
|
gFFI.chatModel.onVoiceCallClosed("End connetion");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didChangeAppLifecycleState(AppLifecycleState state) {
|
||||||
|
if (state == AppLifecycleState.resumed) {
|
||||||
|
trySyncClipboard();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// For client side
|
||||||
|
// When swithing from other app to this app, try to sync clipboard.
|
||||||
|
void trySyncClipboard() {
|
||||||
|
gFFI.invokeMethod("try_sync_clipboard");
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void didChangeMetrics() {
|
void didChangeMetrics() {
|
||||||
// If the soft keyboard is visible and the canvas has been changed(panned or scaled)
|
// If the soft keyboard is visible and the canvas has been changed(panned or scaled)
|
||||||
@@ -591,7 +604,7 @@ class _RemotePageState extends State<RemotePage> with WidgetsBindingObserver {
|
|||||||
// ko/zh/ja input method: the button will trigger `onKeyEvent`
|
// ko/zh/ja input method: the button will trigger `onKeyEvent`
|
||||||
// and the event will not popup if `KeyEventResult.handled` is returned.
|
// and the event will not popup if `KeyEventResult.handled` is returned.
|
||||||
onChanged: handleSoftKeyboardInput,
|
onChanged: handleSoftKeyboardInput,
|
||||||
),
|
).workaroundFreezeLinuxMint(),
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
if (showCursorPaint) {
|
if (showCursorPaint) {
|
||||||
|
|||||||
@@ -782,9 +782,7 @@ class _SettingsState extends State<SettingsPage> with WidgetsBindingObserver {
|
|||||||
tiles: [
|
tiles: [
|
||||||
SettingsTile(
|
SettingsTile(
|
||||||
onPressed: (context) async {
|
onPressed: (context) async {
|
||||||
if (await canLaunchUrl(Uri.parse(url))) {
|
await launchUrl(Uri.parse(url));
|
||||||
await launchUrl(Uri.parse(url));
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
title: Text(translate("Version: ") + version),
|
title: Text(translate("Version: ") + version),
|
||||||
value: Padding(
|
value: Padding(
|
||||||
@@ -928,9 +926,7 @@ void showAbout(OverlayDialogManager dialogManager) {
|
|||||||
InkWell(
|
InkWell(
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
const url = 'https://rustdesk.com/';
|
const url = 'https://rustdesk.com/';
|
||||||
if (await canLaunchUrl(Uri.parse(url))) {
|
await launchUrl(Uri.parse(url));
|
||||||
await launchUrl(Uri.parse(url));
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: EdgeInsets.symmetric(vertical: 8),
|
padding: EdgeInsets.symmetric(vertical: 8),
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ void setPermanentPasswordDialog(OverlayDialogManager dialogManager) async {
|
|||||||
? null
|
? null
|
||||||
: translate('Too short, at least 6 characters.');
|
: translate('Too short, at least 6 characters.');
|
||||||
},
|
},
|
||||||
),
|
).workaroundFreezeLinuxMint(),
|
||||||
TextFormField(
|
TextFormField(
|
||||||
obscureText: true,
|
obscureText: true,
|
||||||
keyboardType: TextInputType.visiblePassword,
|
keyboardType: TextInputType.visiblePassword,
|
||||||
@@ -85,7 +85,7 @@ void setPermanentPasswordDialog(OverlayDialogManager dialogManager) async {
|
|||||||
? null
|
? null
|
||||||
: translate('The confirmation is not identical.');
|
: translate('The confirmation is not identical.');
|
||||||
},
|
},
|
||||||
),
|
).workaroundFreezeLinuxMint(),
|
||||||
])),
|
])),
|
||||||
onCancel: close,
|
onCancel: close,
|
||||||
onSubmit: (validateLength && validateSame) ? submit : null,
|
onSubmit: (validateLength && validateSame) ? submit : null,
|
||||||
@@ -216,7 +216,7 @@ void showServerSettingsWithValue(
|
|||||||
),
|
),
|
||||||
validator: validator,
|
validator: validator,
|
||||||
autofocus: autofocus,
|
autofocus: autofocus,
|
||||||
),
|
).workaroundFreezeLinuxMint(),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
@@ -229,7 +229,7 @@ void showServerSettingsWithValue(
|
|||||||
errorText: errorMsg.isEmpty ? null : errorMsg,
|
errorText: errorMsg.isEmpty ? null : errorMsg,
|
||||||
),
|
),
|
||||||
validator: validator,
|
validator: validator,
|
||||||
);
|
).workaroundFreezeLinuxMint();
|
||||||
}
|
}
|
||||||
|
|
||||||
return CustomAlertDialog(
|
return CustomAlertDialog(
|
||||||
|
|||||||
@@ -58,6 +58,9 @@ class AbModel {
|
|||||||
String? _personalAbGuid;
|
String? _personalAbGuid;
|
||||||
RxBool legacyMode = false.obs;
|
RxBool legacyMode = false.obs;
|
||||||
|
|
||||||
|
// Only handles peers add/remove
|
||||||
|
final Map<String, VoidCallback> _peerIdUpdateListeners = {};
|
||||||
|
|
||||||
final sortTags = shouldSortTags().obs;
|
final sortTags = shouldSortTags().obs;
|
||||||
final filterByIntersection = filterAbTagByIntersection().obs;
|
final filterByIntersection = filterAbTagByIntersection().obs;
|
||||||
|
|
||||||
@@ -188,6 +191,7 @@ class AbModel {
|
|||||||
debugPrint("pull current Ab error: $e");
|
debugPrint("pull current Ab error: $e");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
_callbackPeerUpdate();
|
||||||
if (listInitialized && current.initialized) {
|
if (listInitialized && current.initialized) {
|
||||||
_saveCache();
|
_saveCache();
|
||||||
}
|
}
|
||||||
@@ -419,6 +423,7 @@ class AbModel {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
_callbackPeerUpdate();
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -620,6 +625,9 @@ class AbModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (abEntries.isNotEmpty) {
|
||||||
|
_callbackPeerUpdate();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -742,6 +750,20 @@ class AbModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _callbackPeerUpdate() {
|
||||||
|
for (var listener in _peerIdUpdateListeners.values) {
|
||||||
|
listener();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void addPeerUpdateListener(String key, VoidCallback listener) {
|
||||||
|
_peerIdUpdateListeners[key] = listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
void removePeerUpdateListener(String key) {
|
||||||
|
_peerIdUpdateListeners.remove(key);
|
||||||
|
}
|
||||||
|
|
||||||
// #endregion
|
// #endregion
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,16 +12,20 @@ import '../utils/http_service.dart' as http;
|
|||||||
class GroupModel {
|
class GroupModel {
|
||||||
final RxBool groupLoading = false.obs;
|
final RxBool groupLoading = false.obs;
|
||||||
final RxString groupLoadError = "".obs;
|
final RxString groupLoadError = "".obs;
|
||||||
|
final RxList<DeviceGroupPayload> deviceGroups = RxList.empty(growable: true);
|
||||||
final RxList<UserPayload> users = RxList.empty(growable: true);
|
final RxList<UserPayload> users = RxList.empty(growable: true);
|
||||||
final RxList<Peer> peers = RxList.empty(growable: true);
|
final RxList<Peer> peers = RxList.empty(growable: true);
|
||||||
final RxString selectedUser = ''.obs;
|
final RxBool isSelectedDeviceGroup = false.obs;
|
||||||
final RxString searchUserText = ''.obs;
|
final RxString selectedAccessibleItemName = ''.obs;
|
||||||
|
final RxString searchAccessibleItemNameText = ''.obs;
|
||||||
WeakReference<FFI> parent;
|
WeakReference<FFI> parent;
|
||||||
var initialized = false;
|
var initialized = false;
|
||||||
var _cacheLoadOnceFlag = false;
|
var _cacheLoadOnceFlag = false;
|
||||||
var _statusCode = 200;
|
var _statusCode = 200;
|
||||||
|
|
||||||
bool get emtpy => users.isEmpty && peers.isEmpty;
|
final Map<String, VoidCallback> _peerIdUpdateListeners = {};
|
||||||
|
|
||||||
|
bool get emtpy => deviceGroups.isEmpty && users.isEmpty && peers.isEmpty;
|
||||||
|
|
||||||
late final Peers peersModel;
|
late final Peers peersModel;
|
||||||
|
|
||||||
@@ -55,6 +59,12 @@ class GroupModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _pull() async {
|
Future<void> _pull() async {
|
||||||
|
List<DeviceGroupPayload> tmpDeviceGroups = List.empty(growable: true);
|
||||||
|
if (!await _getDeviceGroups(tmpDeviceGroups)) {
|
||||||
|
// old hbbs doesn't support this api
|
||||||
|
// return;
|
||||||
|
}
|
||||||
|
tmpDeviceGroups.sort((a, b) => a.name.compareTo(b.name));
|
||||||
List<UserPayload> tmpUsers = List.empty(growable: true);
|
List<UserPayload> tmpUsers = List.empty(growable: true);
|
||||||
if (!await _getUsers(tmpUsers)) {
|
if (!await _getUsers(tmpUsers)) {
|
||||||
return;
|
return;
|
||||||
@@ -63,6 +73,7 @@ class GroupModel {
|
|||||||
if (!await _getPeers(tmpPeers)) {
|
if (!await _getPeers(tmpPeers)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
deviceGroups.value = tmpDeviceGroups;
|
||||||
// me first
|
// me first
|
||||||
var index = tmpUsers
|
var index = tmpUsers
|
||||||
.indexWhere((user) => user.name == gFFI.userModel.userName.value);
|
.indexWhere((user) => user.name == gFFI.userModel.userName.value);
|
||||||
@@ -71,8 +82,9 @@ class GroupModel {
|
|||||||
tmpUsers.insert(0, user);
|
tmpUsers.insert(0, user);
|
||||||
}
|
}
|
||||||
users.value = tmpUsers;
|
users.value = tmpUsers;
|
||||||
if (!users.any((u) => u.name == selectedUser.value)) {
|
if (!users.any((u) => u.name == selectedAccessibleItemName.value) &&
|
||||||
selectedUser.value = '';
|
!deviceGroups.any((d) => d.name == selectedAccessibleItemName.value)) {
|
||||||
|
selectedAccessibleItemName.value = '';
|
||||||
}
|
}
|
||||||
// recover online
|
// recover online
|
||||||
final oldOnlineIDs = peers.where((e) => e.online).map((e) => e.id).toList();
|
final oldOnlineIDs = peers.where((e) => e.online).map((e) => e.id).toList();
|
||||||
@@ -82,6 +94,64 @@ class GroupModel {
|
|||||||
.map((e) => e.online = true)
|
.map((e) => e.online = true)
|
||||||
.toList();
|
.toList();
|
||||||
groupLoadError.value = '';
|
groupLoadError.value = '';
|
||||||
|
_callbackPeerUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<bool> _getDeviceGroups(
|
||||||
|
List<DeviceGroupPayload> tmpDeviceGroups) async {
|
||||||
|
final api = "${await bind.mainGetApiServer()}/api/device-group/accessible";
|
||||||
|
try {
|
||||||
|
var uri0 = Uri.parse(api);
|
||||||
|
final pageSize = 100;
|
||||||
|
var total = 0;
|
||||||
|
int current = 0;
|
||||||
|
do {
|
||||||
|
current += 1;
|
||||||
|
var uri = Uri(
|
||||||
|
scheme: uri0.scheme,
|
||||||
|
host: uri0.host,
|
||||||
|
path: uri0.path,
|
||||||
|
port: uri0.port,
|
||||||
|
queryParameters: {
|
||||||
|
'current': current.toString(),
|
||||||
|
'pageSize': pageSize.toString(),
|
||||||
|
});
|
||||||
|
final resp = await http.get(uri, headers: getHttpHeaders());
|
||||||
|
_statusCode = resp.statusCode;
|
||||||
|
Map<String, dynamic> json =
|
||||||
|
_jsonDecodeResp(utf8.decode(resp.bodyBytes), resp.statusCode);
|
||||||
|
if (json.containsKey('error')) {
|
||||||
|
throw json['error'];
|
||||||
|
}
|
||||||
|
if (resp.statusCode != 200) {
|
||||||
|
throw 'HTTP ${resp.statusCode}';
|
||||||
|
}
|
||||||
|
if (json.containsKey('total')) {
|
||||||
|
if (total == 0) total = json['total'];
|
||||||
|
if (json.containsKey('data')) {
|
||||||
|
final data = json['data'];
|
||||||
|
if (data is List) {
|
||||||
|
for (final user in data) {
|
||||||
|
final u = DeviceGroupPayload.fromJson(user);
|
||||||
|
int index = tmpDeviceGroups.indexWhere((e) => e.name == u.name);
|
||||||
|
if (index < 0) {
|
||||||
|
tmpDeviceGroups.add(u);
|
||||||
|
} else {
|
||||||
|
tmpDeviceGroups[index] = u;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} while (current * pageSize < total);
|
||||||
|
return true;
|
||||||
|
} catch (err) {
|
||||||
|
debugPrint('get accessible device groups: $err');
|
||||||
|
// old hbbs doesn't support this api
|
||||||
|
// groupLoadError.value =
|
||||||
|
// '${translate('pull_group_failed_tip')}: ${translate(err.toString())}';
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> _getUsers(List<UserPayload> tmpUsers) async {
|
Future<bool> _getUsers(List<UserPayload> tmpUsers) async {
|
||||||
@@ -225,6 +295,7 @@ class GroupModel {
|
|||||||
try {
|
try {
|
||||||
final map = (<String, dynamic>{
|
final map = (<String, dynamic>{
|
||||||
"access_token": bind.mainGetLocalOption(key: 'access_token'),
|
"access_token": bind.mainGetLocalOption(key: 'access_token'),
|
||||||
|
"device_groups": deviceGroups.map((e) => e.toGroupCacheJson()).toList(),
|
||||||
"users": users.map((e) => e.toGroupCacheJson()).toList(),
|
"users": users.map((e) => e.toGroupCacheJson()).toList(),
|
||||||
'peers': peers.map((e) => e.toGroupCacheJson()).toList()
|
'peers': peers.map((e) => e.toGroupCacheJson()).toList()
|
||||||
});
|
});
|
||||||
@@ -244,8 +315,14 @@ class GroupModel {
|
|||||||
if (groupLoading.value) return;
|
if (groupLoading.value) return;
|
||||||
final data = jsonDecode(cache);
|
final data = jsonDecode(cache);
|
||||||
if (data == null || data['access_token'] != access_token) return;
|
if (data == null || data['access_token'] != access_token) return;
|
||||||
|
deviceGroups.clear();
|
||||||
users.clear();
|
users.clear();
|
||||||
peers.clear();
|
peers.clear();
|
||||||
|
if (data['device_groups'] is List) {
|
||||||
|
for (var u in data['device_groups']) {
|
||||||
|
deviceGroups.add(DeviceGroupPayload.fromJson(u));
|
||||||
|
}
|
||||||
|
}
|
||||||
if (data['users'] is List) {
|
if (data['users'] is List) {
|
||||||
for (var u in data['users']) {
|
for (var u in data['users']) {
|
||||||
users.add(UserPayload.fromJson(u));
|
users.add(UserPayload.fromJson(u));
|
||||||
@@ -255,6 +332,7 @@ class GroupModel {
|
|||||||
for (final peer in data['peers']) {
|
for (final peer in data['peers']) {
|
||||||
peers.add(Peer.fromJson(peer));
|
peers.add(Peer.fromJson(peer));
|
||||||
}
|
}
|
||||||
|
_callbackPeerUpdate();
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
debugPrint("load group cache: $e");
|
debugPrint("load group cache: $e");
|
||||||
@@ -263,9 +341,24 @@ class GroupModel {
|
|||||||
|
|
||||||
reset() async {
|
reset() async {
|
||||||
groupLoadError.value = '';
|
groupLoadError.value = '';
|
||||||
|
deviceGroups.clear();
|
||||||
users.clear();
|
users.clear();
|
||||||
peers.clear();
|
peers.clear();
|
||||||
selectedUser.value = '';
|
selectedAccessibleItemName.value = '';
|
||||||
await bind.mainClearGroup();
|
await bind.mainClearGroup();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _callbackPeerUpdate() {
|
||||||
|
for (var listener in _peerIdUpdateListeners.values) {
|
||||||
|
listener();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void addPeerUpdateListener(String key, VoidCallback listener) {
|
||||||
|
_peerIdUpdateListeners[key] = listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
void removePeerUpdateListener(String key) {
|
||||||
|
_peerIdUpdateListeners.remove(key);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import '../common.dart';
|
|||||||
import '../consts.dart';
|
import '../consts.dart';
|
||||||
|
|
||||||
/// Mouse button enum.
|
/// Mouse button enum.
|
||||||
enum MouseButtons { left, right, wheel }
|
enum MouseButtons { left, right, wheel, back }
|
||||||
|
|
||||||
const _kMouseEventDown = 'mousedown';
|
const _kMouseEventDown = 'mousedown';
|
||||||
const _kMouseEventUp = 'mouseup';
|
const _kMouseEventUp = 'mouseup';
|
||||||
@@ -155,6 +155,8 @@ extension ToString on MouseButtons {
|
|||||||
return 'right';
|
return 'right';
|
||||||
case MouseButtons.wheel:
|
case MouseButtons.wheel:
|
||||||
return 'wheel';
|
return 'wheel';
|
||||||
|
case MouseButtons.back:
|
||||||
|
return 'back';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1426,7 +1428,18 @@ class InputModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void onMobileBack() => tap(MouseButtons.right);
|
void onMobileBack() {
|
||||||
|
final minBackButtonVersion = "1.3.8";
|
||||||
|
final peerVersion =
|
||||||
|
parent.target?.ffiModel.pi.version ?? minBackButtonVersion;
|
||||||
|
var btn = MouseButtons.back;
|
||||||
|
// For compatibility with old versions
|
||||||
|
if (versionCmp(peerVersion, minBackButtonVersion) < 0) {
|
||||||
|
btn = MouseButtons.right;
|
||||||
|
}
|
||||||
|
tap(btn);
|
||||||
|
}
|
||||||
|
|
||||||
void onMobileHome() => tap(MouseButtons.wheel);
|
void onMobileHome() => tap(MouseButtons.wheel);
|
||||||
Future<void> onMobileApps() async {
|
Future<void> onMobileApps() async {
|
||||||
sendMouse('down', MouseButtons.wheel);
|
sendMouse('down', MouseButtons.wheel);
|
||||||
|
|||||||
@@ -1500,13 +1500,15 @@ class CanvasModel with ChangeNotifier {
|
|||||||
return max(bottom - MediaQueryData.fromView(ui.window).padding.top, 0);
|
return max(bottom - MediaQueryData.fromView(ui.window).padding.top, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateSize() => _size = getSize();
|
||||||
|
|
||||||
updateViewStyle({refreshMousePos = true, notify = true}) async {
|
updateViewStyle({refreshMousePos = true, notify = true}) async {
|
||||||
final style = await bind.sessionGetViewStyle(sessionId: sessionId);
|
final style = await bind.sessionGetViewStyle(sessionId: sessionId);
|
||||||
if (style == null) {
|
if (style == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_size = getSize();
|
updateSize();
|
||||||
final displayWidth = getDisplayWidth();
|
final displayWidth = getDisplayWidth();
|
||||||
final displayHeight = getDisplayHeight();
|
final displayHeight = getDisplayHeight();
|
||||||
final viewStyle = ViewStyle(
|
final viewStyle = ViewStyle(
|
||||||
@@ -1543,7 +1545,7 @@ class CanvasModel with ChangeNotifier {
|
|||||||
_resetCanvasOffset(int displayWidth, int displayHeight) {
|
_resetCanvasOffset(int displayWidth, int displayHeight) {
|
||||||
_x = (size.width - displayWidth * _scale) / 2;
|
_x = (size.width - displayWidth * _scale) / 2;
|
||||||
_y = (size.height - displayHeight * _scale) / 2;
|
_y = (size.height - displayHeight * _scale) / 2;
|
||||||
if (isMobile && _lastViewStyle.style == kRemoteViewStyleOriginal) {
|
if (isMobile) {
|
||||||
_moveToCenterCursor();
|
_moveToCenterCursor();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1736,7 +1738,8 @@ class CanvasModel with ChangeNotifier {
|
|||||||
_timerMobileFocusCanvasCursor?.cancel();
|
_timerMobileFocusCanvasCursor?.cancel();
|
||||||
_timerMobileFocusCanvasCursor =
|
_timerMobileFocusCanvasCursor =
|
||||||
Timer(Duration(milliseconds: 100), () async {
|
Timer(Duration(milliseconds: 100), () async {
|
||||||
await updateViewStyle(refreshMousePos: false, notify: false);
|
updateSize();
|
||||||
|
_resetCanvasOffset(getDisplayWidth(), getDisplayHeight());
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -2427,6 +2430,8 @@ class CursorModel with ChangeNotifier {
|
|||||||
_x = -10000;
|
_x = -10000;
|
||||||
_x = -10000;
|
_x = -10000;
|
||||||
_image = null;
|
_image = null;
|
||||||
|
_firstUpdateMouseTime = null;
|
||||||
|
gotMouseControl = true;
|
||||||
disposeImages();
|
disposeImages();
|
||||||
|
|
||||||
_clearCache();
|
_clearCache();
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ class Peer {
|
|||||||
String rdpUsername;
|
String rdpUsername;
|
||||||
bool online = false;
|
bool online = false;
|
||||||
String loginName; //login username
|
String loginName; //login username
|
||||||
|
String device_group_name;
|
||||||
bool? sameServer;
|
bool? sameServer;
|
||||||
|
|
||||||
String getId() {
|
String getId() {
|
||||||
@@ -41,6 +42,7 @@ class Peer {
|
|||||||
rdpPort = json['rdpPort'] ?? '',
|
rdpPort = json['rdpPort'] ?? '',
|
||||||
rdpUsername = json['rdpUsername'] ?? '',
|
rdpUsername = json['rdpUsername'] ?? '',
|
||||||
loginName = json['loginName'] ?? '',
|
loginName = json['loginName'] ?? '',
|
||||||
|
device_group_name = json['device_group_name'] ?? '',
|
||||||
sameServer = json['same_server'];
|
sameServer = json['same_server'];
|
||||||
|
|
||||||
Map<String, dynamic> toJson() {
|
Map<String, dynamic> toJson() {
|
||||||
@@ -57,6 +59,7 @@ class Peer {
|
|||||||
"rdpPort": rdpPort,
|
"rdpPort": rdpPort,
|
||||||
"rdpUsername": rdpUsername,
|
"rdpUsername": rdpUsername,
|
||||||
'loginName': loginName,
|
'loginName': loginName,
|
||||||
|
'device_group_name': device_group_name,
|
||||||
'same_server': sameServer,
|
'same_server': sameServer,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -83,6 +86,7 @@ class Peer {
|
|||||||
"hostname": hostname,
|
"hostname": hostname,
|
||||||
"platform": platform,
|
"platform": platform,
|
||||||
"login_name": loginName,
|
"login_name": loginName,
|
||||||
|
"device_group_name": device_group_name,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -99,6 +103,7 @@ class Peer {
|
|||||||
required this.rdpPort,
|
required this.rdpPort,
|
||||||
required this.rdpUsername,
|
required this.rdpUsername,
|
||||||
required this.loginName,
|
required this.loginName,
|
||||||
|
required this.device_group_name,
|
||||||
this.sameServer,
|
this.sameServer,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -116,6 +121,7 @@ class Peer {
|
|||||||
rdpPort: '',
|
rdpPort: '',
|
||||||
rdpUsername: '',
|
rdpUsername: '',
|
||||||
loginName: '',
|
loginName: '',
|
||||||
|
device_group_name: '',
|
||||||
);
|
);
|
||||||
bool equal(Peer other) {
|
bool equal(Peer other) {
|
||||||
return id == other.id &&
|
return id == other.id &&
|
||||||
@@ -129,6 +135,7 @@ class Peer {
|
|||||||
forceAlwaysRelay == other.forceAlwaysRelay &&
|
forceAlwaysRelay == other.forceAlwaysRelay &&
|
||||||
rdpPort == other.rdpPort &&
|
rdpPort == other.rdpPort &&
|
||||||
rdpUsername == other.rdpUsername &&
|
rdpUsername == other.rdpUsername &&
|
||||||
|
device_group_name == other.device_group_name &&
|
||||||
loginName == other.loginName;
|
loginName == other.loginName;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -146,6 +153,7 @@ class Peer {
|
|||||||
rdpPort: other.rdpPort,
|
rdpPort: other.rdpPort,
|
||||||
rdpUsername: other.rdpUsername,
|
rdpUsername: other.rdpUsername,
|
||||||
loginName: other.loginName,
|
loginName: other.loginName,
|
||||||
|
device_group_name: other.device_group_name,
|
||||||
sameServer: other.sameServer);
|
sameServer: other.sameServer);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -157,6 +165,11 @@ class Peers extends ChangeNotifier {
|
|||||||
final String name;
|
final String name;
|
||||||
final String loadEvent;
|
final String loadEvent;
|
||||||
List<Peer> peers = List.empty(growable: true);
|
List<Peer> peers = List.empty(growable: true);
|
||||||
|
// Part of the peers that are not in the rest peers list.
|
||||||
|
// When there're too many peers, we may want to load the front 100 peers first,
|
||||||
|
// so we can see peers in UI quickly. `restPeerIds` is the rest peers' ids.
|
||||||
|
// And then load all peers later.
|
||||||
|
List<String> restPeerIds = List.empty(growable: true);
|
||||||
final GetInitPeers? getInitPeers;
|
final GetInitPeers? getInitPeers;
|
||||||
UpdateEvent event = UpdateEvent.load;
|
UpdateEvent event = UpdateEvent.load;
|
||||||
static const _cbQueryOnlines = 'callback_query_onlines';
|
static const _cbQueryOnlines = 'callback_query_onlines';
|
||||||
@@ -230,6 +243,12 @@ class Peers extends ChangeNotifier {
|
|||||||
} else {
|
} else {
|
||||||
peers = _decodePeers(evt['peers']);
|
peers = _decodePeers(evt['peers']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
restPeerIds = [];
|
||||||
|
if (evt['ids'] != null) {
|
||||||
|
restPeerIds = (evt['ids'] as String).split(',');
|
||||||
|
}
|
||||||
|
|
||||||
for (var peer in peers) {
|
for (var peer in peers) {
|
||||||
final state = onlineStates[peer.id];
|
final state = onlineStates[peer.id];
|
||||||
peer.online = state != null && state != false;
|
peer.online = state != null && state != false;
|
||||||
|
|||||||
@@ -28,14 +28,14 @@ class PeerTabModel with ChangeNotifier {
|
|||||||
'Favorites',
|
'Favorites',
|
||||||
'Discovered',
|
'Discovered',
|
||||||
'Address book',
|
'Address book',
|
||||||
'Group',
|
'Accessible devices',
|
||||||
];
|
];
|
||||||
static const List<IconData> icons = [
|
static const List<IconData> icons = [
|
||||||
Icons.access_time_filled,
|
Icons.access_time_filled,
|
||||||
Icons.star,
|
Icons.star,
|
||||||
Icons.explore,
|
Icons.explore,
|
||||||
IconFont.addressBook,
|
IconFont.addressBook,
|
||||||
Icons.group,
|
IconFont.deviceGroupFill,
|
||||||
];
|
];
|
||||||
List<bool> isEnabled = List.from([
|
List<bool> isEnabled = List.from([
|
||||||
true,
|
true,
|
||||||
|
|||||||
@@ -323,10 +323,10 @@ class ServerModel with ChangeNotifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
toggleClipboard() async {
|
toggleClipboard() async {
|
||||||
_clipboardOk = !_clipboardOk;
|
_clipboardOk = !clipboardOk;
|
||||||
bind.mainSetOption(
|
bind.mainSetOption(
|
||||||
key: kOptionEnableClipboard,
|
key: kOptionEnableClipboard,
|
||||||
value: _clipboardOk ? defaultOptionYes : 'N');
|
value: clipboardOk ? defaultOptionYes : 'N');
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ class StateGlobal {
|
|||||||
final RxDouble _windowBorderWidth = RxDouble(kWindowBorderWidth);
|
final RxDouble _windowBorderWidth = RxDouble(kWindowBorderWidth);
|
||||||
final RxBool showRemoteToolBar = false.obs;
|
final RxBool showRemoteToolBar = false.obs;
|
||||||
final svcStatus = SvcStatus.notReady.obs;
|
final svcStatus = SvcStatus.notReady.obs;
|
||||||
|
final RxInt videoConnCount = 0.obs;
|
||||||
final RxBool isFocused = false.obs;
|
final RxBool isFocused = false.obs;
|
||||||
// for mobile and web
|
// for mobile and web
|
||||||
bool isInMainPage = true;
|
bool isInMainPage = true;
|
||||||
|
|||||||
@@ -2,16 +2,18 @@ import 'dart:convert';
|
|||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_hbb/common.dart';
|
import 'package:flutter_hbb/common.dart';
|
||||||
import 'package:flutter_hbb/models/platform_model.dart';
|
|
||||||
|
|
||||||
void showPeerSelectionDialog(
|
void showPeerSelectionDialog(
|
||||||
{bool singleSelection = false,
|
{bool singleSelection = false,
|
||||||
required Function(List<String>) onPeersCallback}) {
|
required Function(List<String>) onPeersCallback}) async {
|
||||||
final peers = bind.mainLoadRecentPeersSync();
|
// load recent peers, we can directly use the peers in `gFFI.recentPeersModel`.
|
||||||
|
// The plugin is not used for now, so just left it empty here.
|
||||||
|
final peers = '';
|
||||||
if (peers.isEmpty) {
|
if (peers.isEmpty) {
|
||||||
debugPrint("load recent peers sync failed.");
|
// debugPrint("load recent peers failed.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<String, dynamic> map = jsonDecode(peers);
|
Map<String, dynamic> map = jsonDecode(peers);
|
||||||
List<dynamic> peersList = map['peers'] ?? [];
|
List<dynamic> peersList = map['peers'] ?? [];
|
||||||
final selected = List<String>.empty(growable: true);
|
final selected = List<String>.empty(growable: true);
|
||||||
|
|||||||
@@ -1848,9 +1848,5 @@ class RustdeskImpl {
|
|||||||
throw UnimplementedError("sessionGetConnToken");
|
throw UnimplementedError("sessionGetConnToken");
|
||||||
}
|
}
|
||||||
|
|
||||||
String getOsDistroInfo({dynamic hint}) {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
void dispose() {}
|
void dispose() {}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,10 +14,12 @@ struct _MyApplication {
|
|||||||
|
|
||||||
G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION)
|
G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION)
|
||||||
|
|
||||||
|
GtkWidget *find_gl_area(GtkWidget *widget);
|
||||||
|
void try_set_transparent(GtkWindow* window, GdkScreen* screen, FlView* view);
|
||||||
|
|
||||||
extern bool gIsConnectionManager;
|
extern bool gIsConnectionManager;
|
||||||
|
|
||||||
GtkWidget *find_gl_area(GtkWidget *widget);
|
GtkWidget *find_gl_area(GtkWidget *widget);
|
||||||
void try_set_transparent(GtkWindow* window, GdkScreen* screen, FlView* view);
|
|
||||||
|
|
||||||
// Implements GApplication::activate.
|
// Implements GApplication::activate.
|
||||||
static void my_application_activate(GApplication* application) {
|
static void my_application_activate(GApplication* application) {
|
||||||
@@ -70,17 +72,18 @@ static void my_application_activate(GApplication* application) {
|
|||||||
height = 490;
|
height = 490;
|
||||||
}
|
}
|
||||||
gtk_window_set_default_size(window, width, height); // <-- comment this line
|
gtk_window_set_default_size(window, width, height); // <-- comment this line
|
||||||
gtk_widget_show(GTK_WIDGET(window));
|
// gtk_widget_show(GTK_WIDGET(window));
|
||||||
gtk_widget_set_opacity(GTK_WIDGET(window), 0);
|
gtk_widget_set_opacity(GTK_WIDGET(window), 0);
|
||||||
|
|
||||||
g_autoptr(FlDartProject) project = fl_dart_project_new();
|
g_autoptr(FlDartProject) project = fl_dart_project_new();
|
||||||
fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments);
|
fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments);
|
||||||
|
|
||||||
FlView* view = fl_view_new(project);
|
FlView* view = fl_view_new(project);
|
||||||
gtk_widget_show(GTK_WIDGET(view));
|
|
||||||
gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view));
|
gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view));
|
||||||
|
|
||||||
try_set_transparent(window, screen, view);
|
try_set_transparent(window, gtk_window_get_screen(window), view);
|
||||||
|
gtk_widget_show(GTK_WIDGET(window));
|
||||||
|
gtk_widget_show(GTK_WIDGET(view));
|
||||||
|
|
||||||
fl_register_plugins(FL_PLUGIN_REGISTRY(view));
|
fl_register_plugins(FL_PLUGIN_REGISTRY(view));
|
||||||
|
|
||||||
@@ -150,70 +153,12 @@ GtkWidget *find_gl_area(GtkWidget *widget)
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool is_linux_mint()
|
|
||||||
{
|
|
||||||
bool is_mint = false;
|
|
||||||
char line[256];
|
|
||||||
FILE *fp = fopen("/etc/os-release", "r");
|
|
||||||
if (fp == NULL) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
while (fgets(line, sizeof(line), fp)) {
|
|
||||||
if (strstr(line, "ID=linuxmint") != NULL) {
|
|
||||||
is_mint = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fclose(fp);
|
|
||||||
|
|
||||||
return is_mint;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool is_desktop_mate()
|
|
||||||
{
|
|
||||||
const char* desktop = NULL;
|
|
||||||
desktop = getenv("XDG_CURRENT_DESKTOP");
|
|
||||||
printf("Linux desktop, XDG_CURRENT_DESKTOP: %s\n", desktop == NULL ? "" : desktop);
|
|
||||||
if (desktop == NULL) {
|
|
||||||
desktop = getenv("XDG_SESSION_DESKTOP");
|
|
||||||
printf("Linux desktop, XDG_SESSION_DESKTOP: %s\n", desktop == NULL ? "" : desktop);
|
|
||||||
}
|
|
||||||
if (desktop == NULL) {
|
|
||||||
desktop = getenv("DESKTOP_SESSION");
|
|
||||||
printf("Linux desktop, DESKTOP_SESSION: %s\n", desktop == NULL ? "" : desktop);
|
|
||||||
}
|
|
||||||
if (desktop != NULL && strcasecmp(desktop, "mate") == 0) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool skip_setting_transparent()
|
|
||||||
{
|
|
||||||
if (is_desktop_mate()) {
|
|
||||||
printf("Linux desktop, MATE\n");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (is_linux_mint()) {
|
|
||||||
printf("Linux desktop, Linux Mint\n");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// https://github.com/flutter/flutter/issues/152154
|
// https://github.com/flutter/flutter/issues/152154
|
||||||
// Remove this workaround when flutter version is updated.
|
// Remove this workaround when flutter version is updated.
|
||||||
void try_set_transparent(GtkWindow* window, GdkScreen* screen, FlView* view)
|
void try_set_transparent(GtkWindow* window, GdkScreen* screen, FlView* view)
|
||||||
{
|
{
|
||||||
GtkWidget *gl_area = NULL;
|
GtkWidget *gl_area = NULL;
|
||||||
|
|
||||||
if (skip_setting_transparent()) {
|
|
||||||
printf("Skip setting transparent\n");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
printf("Try setting transparent\n");
|
printf("Try setting transparent\n");
|
||||||
|
|
||||||
gl_area = find_gl_area(GTK_WIDGET(view));
|
gl_area = find_gl_area(GTK_WIDGET(view));
|
||||||
|
|||||||
@@ -95,17 +95,17 @@ SPEC CHECKSUMS:
|
|||||||
desktop_drop: 69eeff437544aa619c8db7f4481b3a65f7696898
|
desktop_drop: 69eeff437544aa619c8db7f4481b3a65f7696898
|
||||||
desktop_multi_window: 566489c048b501134f9d7fb6a2354c60a9126486
|
desktop_multi_window: 566489c048b501134f9d7fb6a2354c60a9126486
|
||||||
device_info_plus: 5401765fde0b8d062a2f8eb65510fb17e77cf07f
|
device_info_plus: 5401765fde0b8d062a2f8eb65510fb17e77cf07f
|
||||||
file_selector_macos: 54fdab7caa3ac3fc43c9fac4d7d8d231277f8cf2
|
file_selector_macos: 468fb6b81fac7c0e88d71317f3eec34c3b008ff9
|
||||||
flutter_custom_cursor: 629957115075c672287bd0fa979d863ccf6024f7
|
flutter_custom_cursor: 629957115075c672287bd0fa979d863ccf6024f7
|
||||||
FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24
|
FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24
|
||||||
package_info_plus: 02d7a575e80f194102bef286361c6c326e4c29ce
|
package_info_plus: 02d7a575e80f194102bef286361c6c326e4c29ce
|
||||||
path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46
|
path_provider_foundation: 3784922295ac71e43754bd15e0653ccfd36a147c
|
||||||
screen_retriever: 59634572a57080243dd1bf715e55b6c54f241a38
|
screen_retriever: 59634572a57080243dd1bf715e55b6c54f241a38
|
||||||
sqflite: 673a0e54cc04b7d6dba8d24fb8095b31c3a99eec
|
sqflite: 673a0e54cc04b7d6dba8d24fb8095b31c3a99eec
|
||||||
texture_rgba_renderer: cbed959a3c127122194a364e14b8577bd62dc8f2
|
texture_rgba_renderer: cbed959a3c127122194a364e14b8577bd62dc8f2
|
||||||
uni_links_desktop: 45900fb319df48fcdea2df0756e9c2626696b026
|
uni_links_desktop: 45900fb319df48fcdea2df0756e9c2626696b026
|
||||||
url_launcher_macos: 5f437abeda8c85500ceb03f5c1938a8c5a705399
|
url_launcher_macos: d2691c7dd33ed713bf3544850a623080ec693d95
|
||||||
video_player_avfoundation: 7c6c11d8470e1675df7397027218274b6d2360b3
|
video_player_avfoundation: 02011213dab73ae3687df27ce441fbbcc82b5579
|
||||||
wakelock_plus: 4783562c9a43d209c458cb9b30692134af456269
|
wakelock_plus: 4783562c9a43d209c458cb9b30692134af456269
|
||||||
window_manager: 3a1844359a6295ab1e47659b1a777e36773cd6e8
|
window_manager: 3a1844359a6295ab1e47659b1a777e36773cd6e8
|
||||||
window_size: 339dafa0b27a95a62a843042038fa6c3c48de195
|
window_size: 339dafa0b27a95a62a843042038fa6c3c48de195
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import Cocoa
|
import Cocoa
|
||||||
import FlutterMacOS
|
import FlutterMacOS
|
||||||
|
|
||||||
@NSApplicationMain
|
@main
|
||||||
class AppDelegate: FlutterAppDelegate {
|
class AppDelegate: FlutterAppDelegate {
|
||||||
var launched = false;
|
var launched = false;
|
||||||
override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
|
override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
|
||||||
|
|||||||
@@ -11,4 +11,4 @@ PRODUCT_NAME = RustDesk
|
|||||||
PRODUCT_BUNDLE_IDENTIFIER = com.carriez.flutterHbb
|
PRODUCT_BUNDLE_IDENTIFIER = com.carriez.flutterHbb
|
||||||
|
|
||||||
// The copyright displayed in application information
|
// The copyright displayed in application information
|
||||||
PRODUCT_COPYRIGHT = Copyright © 2024 Purslane Ltd. All rights reserved.
|
PRODUCT_COPYRIGHT = Copyright © 2025 Purslane Ltd. All rights reserved.
|
||||||
|
|||||||
@@ -1351,26 +1351,26 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: url_launcher
|
name: url_launcher
|
||||||
sha256: c512655380d241a337521703af62d2c122bf7b77a46ff7dd750092aa9433499c
|
sha256: "9d06212b1362abc2f0f0d78e6f09f726608c74e3b9462e8368bb03314aa8d603"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.2.4"
|
version: "6.3.1"
|
||||||
url_launcher_android:
|
url_launcher_android:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: url_launcher_android
|
name: url_launcher_android
|
||||||
sha256: "507dc655b1d9cb5ebc756032eb785f114e415f91557b73bf60b7e201dfedeb2f"
|
sha256: "6fc2f56536ee873eeb867ad176ae15f304ccccc357848b351f6f0d8d4a40d193"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.2.2"
|
version: "6.3.14"
|
||||||
url_launcher_ios:
|
url_launcher_ios:
|
||||||
dependency: transitive
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: url_launcher_ios
|
name: url_launcher_ios
|
||||||
sha256: "75bb6fe3f60070407704282a2d295630cab232991eb52542b18347a8a941df03"
|
sha256: "16a513b6c12bb419304e72ea0ae2ab4fed569920d1c7cb850263fe3acc824626"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.2.4"
|
version: "6.3.2"
|
||||||
url_launcher_linux:
|
url_launcher_linux:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev
|
|||||||
# Read more about iOS versioning at
|
# Read more about iOS versioning at
|
||||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||||
# 1.1.9-1 works for android, but for ios it becomes 1.1.91, need to set it to 1.1.9-a.1 for iOS, will get 1.1.9.1, but iOS store not allow 4 numbers
|
# 1.1.9-1 works for android, but for ios it becomes 1.1.91, need to set it to 1.1.9-a.1 for iOS, will get 1.1.9.1, but iOS store not allow 4 numbers
|
||||||
version: 1.3.4+53
|
version: 1.3.8+57
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: '^3.1.0'
|
sdk: '^3.1.0'
|
||||||
@@ -35,7 +35,8 @@ dependencies:
|
|||||||
wakelock_plus: ^1.1.3
|
wakelock_plus: ^1.1.3
|
||||||
#firebase_analytics: ^9.1.5
|
#firebase_analytics: ^9.1.5
|
||||||
package_info_plus: ^4.2.0
|
package_info_plus: ^4.2.0
|
||||||
url_launcher: ^6.2.1
|
url_launcher: ^6.3.1
|
||||||
|
url_launcher_ios: ^6.3.2
|
||||||
toggle_switch: ^2.1.0
|
toggle_switch: ^2.1.0
|
||||||
dash_chat_2:
|
dash_chat_2:
|
||||||
git:
|
git:
|
||||||
@@ -160,6 +161,9 @@ flutter:
|
|||||||
- family: AddressBook
|
- family: AddressBook
|
||||||
fonts:
|
fonts:
|
||||||
- asset: assets/address_book.ttf
|
- asset: assets/address_book.ttf
|
||||||
|
- family: DeviceGroup
|
||||||
|
fonts:
|
||||||
|
- asset: assets/device_group.ttf
|
||||||
|
|
||||||
# An image asset can refer to one or more resolution-specific "variants", see
|
# An image asset can refer to one or more resolution-specific "variants", see
|
||||||
# https://flutter.dev/assets-and-images/#resolution-aware.
|
# https://flutter.dev/assets-and-images/#resolution-aware.
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ BEGIN
|
|||||||
VALUE "FileDescription", "RustDesk Remote Desktop" "\0"
|
VALUE "FileDescription", "RustDesk Remote Desktop" "\0"
|
||||||
VALUE "FileVersion", VERSION_AS_STRING "\0"
|
VALUE "FileVersion", VERSION_AS_STRING "\0"
|
||||||
VALUE "InternalName", "rustdesk" "\0"
|
VALUE "InternalName", "rustdesk" "\0"
|
||||||
VALUE "LegalCopyright", "Copyright © 2024 Purslane Ltd. All rights reserved." "\0"
|
VALUE "LegalCopyright", "Copyright © 2025 Purslane Ltd. All rights reserved." "\0"
|
||||||
VALUE "OriginalFilename", "rustdesk.exe" "\0"
|
VALUE "OriginalFilename", "rustdesk.exe" "\0"
|
||||||
VALUE "ProductName", "RustDesk" "\0"
|
VALUE "ProductName", "RustDesk" "\0"
|
||||||
VALUE "ProductVersion", VERSION_AS_STRING "\0"
|
VALUE "ProductVersion", VERSION_AS_STRING "\0"
|
||||||
|
|||||||
@@ -34,7 +34,6 @@ parking_lot = {version = "0.12"}
|
|||||||
|
|
||||||
[target.'cfg(any(target_os = "linux", target_os = "macos"))'.dependencies]
|
[target.'cfg(any(target_os = "linux", target_os = "macos"))'.dependencies]
|
||||||
rand = {version = "0.8", optional = true}
|
rand = {version = "0.8", optional = true}
|
||||||
fuser = {version = "0.13", optional = true}
|
|
||||||
libc = {version = "0.2", optional = true}
|
libc = {version = "0.2", optional = true}
|
||||||
dashmap = {version ="5.5", optional = true}
|
dashmap = {version ="5.5", optional = true}
|
||||||
utf16string = {version = "0.2", optional = true}
|
utf16string = {version = "0.2", optional = true}
|
||||||
@@ -44,6 +43,7 @@ once_cell = {version = "1.18", optional = true}
|
|||||||
percent-encoding = {version ="2.3", optional = true}
|
percent-encoding = {version ="2.3", optional = true}
|
||||||
x11-clipboard = {git="https://github.com/clslaid/x11-clipboard", branch = "feat/store-batch", optional = true}
|
x11-clipboard = {git="https://github.com/clslaid/x11-clipboard", branch = "feat/store-batch", optional = true}
|
||||||
x11rb = {version = "0.12", features = ["all-extensions"], optional = true}
|
x11rb = {version = "0.12", features = ["all-extensions"], optional = true}
|
||||||
|
fuser = {version = "0.15", default-features = false, optional = true}
|
||||||
|
|
||||||
[target.'cfg(target_os = "macos")'.dependencies]
|
[target.'cfg(target_os = "macos")'.dependencies]
|
||||||
cacao = {git="https://github.com/clslaid/cacao", branch = "feat/set-file-urls", optional = true}
|
cacao = {git="https://github.com/clslaid/cacao", branch = "feat/set-file-urls", optional = true}
|
||||||
|
|||||||
@@ -1,24 +1,23 @@
|
|||||||
#[allow(dead_code)]
|
use std::sync::{Arc, Mutex, RwLock};
|
||||||
use std::{
|
|
||||||
path::PathBuf,
|
|
||||||
sync::{Arc, Mutex, RwLock},
|
|
||||||
};
|
|
||||||
|
|
||||||
#[cfg(any(target_os = "windows", feature = "unix-file-copy-paste",))]
|
#[cfg(target_os = "windows")]
|
||||||
use hbb_common::{allow_err, bail};
|
use hbb_common::ResultType;
|
||||||
|
#[cfg(any(target_os = "windows", feature = "unix-file-copy-paste"))]
|
||||||
|
use hbb_common::{allow_err, log};
|
||||||
use hbb_common::{
|
use hbb_common::{
|
||||||
lazy_static,
|
lazy_static,
|
||||||
tokio::sync::{
|
tokio::sync::{
|
||||||
mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender},
|
mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender},
|
||||||
Mutex as TokioMutex,
|
Mutex as TokioMutex,
|
||||||
},
|
},
|
||||||
ResultType,
|
|
||||||
};
|
};
|
||||||
use serde_derive::{Deserialize, Serialize};
|
use serde_derive::{Deserialize, Serialize};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
pub mod context_send;
|
pub mod context_send;
|
||||||
pub mod platform;
|
pub mod platform;
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
pub use context_send::*;
|
pub use context_send::*;
|
||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
@@ -28,8 +27,10 @@ const ERR_CODE_INVALID_PARAMETER: u32 = 0x00000002;
|
|||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
const ERR_CODE_SEND_MSG: u32 = 0x00000003;
|
const ERR_CODE_SEND_MSG: u32 = 0x00000003;
|
||||||
|
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
pub(crate) use platform::create_cliprdr_context;
|
pub(crate) use platform::create_cliprdr_context;
|
||||||
|
|
||||||
|
// to-do: This trait may be removed, because unix file copy paste does not need it.
|
||||||
/// Ability to handle Clipboard File from remote rustdesk client
|
/// Ability to handle Clipboard File from remote rustdesk client
|
||||||
///
|
///
|
||||||
/// # Note
|
/// # Note
|
||||||
@@ -41,7 +42,6 @@ pub trait CliprdrServiceContext: Send + Sync {
|
|||||||
fn set_is_stopped(&mut self) -> Result<(), CliprdrError>;
|
fn set_is_stopped(&mut self) -> Result<(), CliprdrError>;
|
||||||
/// clear the content on clipboard
|
/// clear the content on clipboard
|
||||||
fn empty_clipboard(&mut self, conn_id: i32) -> Result<bool, CliprdrError>;
|
fn empty_clipboard(&mut self, conn_id: i32) -> Result<bool, CliprdrError>;
|
||||||
|
|
||||||
/// run as a server for clipboard RPC
|
/// run as a server for clipboard RPC
|
||||||
fn server_clip_file(&mut self, conn_id: i32, msg: ClipboardFile) -> Result<(), CliprdrError>;
|
fn server_clip_file(&mut self, conn_id: i32, msg: ClipboardFile) -> Result<(), CliprdrError>;
|
||||||
}
|
}
|
||||||
@@ -63,9 +63,11 @@ pub enum CliprdrError {
|
|||||||
#[error("failure to read clipboard")]
|
#[error("failure to read clipboard")]
|
||||||
OpenClipboard,
|
OpenClipboard,
|
||||||
#[error("failure to read file metadata or content")]
|
#[error("failure to read file metadata or content")]
|
||||||
FileError { path: PathBuf, err: std::io::Error },
|
FileError { path: String, err: std::io::Error },
|
||||||
#[error("invalid request")]
|
#[error("invalid request")]
|
||||||
InvalidRequest { description: String },
|
InvalidRequest { description: String },
|
||||||
|
#[error("common request")]
|
||||||
|
CommonError { description: String },
|
||||||
#[error("unknown cliprdr error")]
|
#[error("unknown cliprdr error")]
|
||||||
Unknown(u32),
|
Unknown(u32),
|
||||||
}
|
}
|
||||||
@@ -107,6 +109,7 @@ pub enum ClipboardFile {
|
|||||||
stream_id: i32,
|
stream_id: i32,
|
||||||
requested_data: Vec<u8>,
|
requested_data: Vec<u8>,
|
||||||
},
|
},
|
||||||
|
TryEmpty,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct MsgChannel {
|
struct MsgChannel {
|
||||||
@@ -198,42 +201,67 @@ pub fn get_rx_cliprdr_server(conn_id: i32) -> Arc<TokioMutex<UnboundedReceiver<C
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(any(target_os = "windows", feature = "unix-file-copy-paste",))]
|
pub fn remove_channel_by_conn_id(conn_id: i32) {
|
||||||
|
let mut lock = VEC_MSG_CHANNEL.write().unwrap();
|
||||||
|
if let Some(index) = lock.iter().position(|x| x.conn_id == conn_id) {
|
||||||
|
lock.remove(index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(any(target_os = "windows", feature = "unix-file-copy-paste"))]
|
||||||
#[inline]
|
#[inline]
|
||||||
fn send_data(conn_id: i32, data: ClipboardFile) -> ResultType<()> {
|
pub fn send_data(conn_id: i32, data: ClipboardFile) -> Result<(), CliprdrError> {
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
return send_data_to_channel(conn_id, data);
|
return send_data_to_channel(conn_id, data);
|
||||||
#[cfg(not(target_os = "windows"))]
|
#[cfg(not(target_os = "windows"))]
|
||||||
if conn_id == 0 {
|
if conn_id == 0 {
|
||||||
send_data_to_all(data);
|
let _ = send_data_to_all(data);
|
||||||
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
send_data_to_channel(conn_id, data);
|
send_data_to_channel(conn_id, data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[cfg(any(target_os = "windows", feature = "unix-file-copy-paste",))]
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn send_data_to_channel(conn_id: i32, data: ClipboardFile) -> ResultType<()> {
|
#[cfg(any(target_os = "windows", feature = "unix-file-copy-paste"))]
|
||||||
|
fn send_data_to_channel(conn_id: i32, data: ClipboardFile) -> Result<(), CliprdrError> {
|
||||||
if let Some(msg_channel) = VEC_MSG_CHANNEL
|
if let Some(msg_channel) = VEC_MSG_CHANNEL
|
||||||
.read()
|
.read()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.iter()
|
.iter()
|
||||||
.find(|x| x.conn_id == conn_id)
|
.find(|x| x.conn_id == conn_id)
|
||||||
{
|
{
|
||||||
msg_channel.sender.send(data)?;
|
msg_channel
|
||||||
Ok(())
|
.sender
|
||||||
|
.send(data)
|
||||||
|
.map_err(|e| CliprdrError::CommonError {
|
||||||
|
description: e.to_string(),
|
||||||
|
})
|
||||||
} else {
|
} else {
|
||||||
bail!("conn_id not found");
|
Err(CliprdrError::InvalidRequest {
|
||||||
|
description: "conn_id not found".to_string(),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "unix-file-copy-paste")]
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn send_data_to_all(data: ClipboardFile) -> ResultType<()> {
|
#[cfg(target_os = "windows")]
|
||||||
|
pub fn send_data_exclude(conn_id: i32, data: ClipboardFile) {
|
||||||
|
// Need more tests to see if it's necessary to handle the error.
|
||||||
|
for msg_channel in VEC_MSG_CHANNEL.read().unwrap().iter() {
|
||||||
|
if msg_channel.conn_id != conn_id {
|
||||||
|
allow_err!(msg_channel.sender.send(data.clone()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
#[cfg(feature = "unix-file-copy-paste")]
|
||||||
|
fn send_data_to_all(data: ClipboardFile) {
|
||||||
// Need more tests to see if it's necessary to handle the error.
|
// Need more tests to see if it's necessary to handle the error.
|
||||||
for msg_channel in VEC_MSG_CHANNEL.read().unwrap().iter() {
|
for msg_channel in VEC_MSG_CHANNEL.read().unwrap().iter() {
|
||||||
allow_err!(msg_channel.sender.send(data.clone()));
|
allow_err!(msg_channel.sender.send(data.clone()));
|
||||||
}
|
}
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|||||||
@@ -1,6 +1,3 @@
|
|||||||
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
|
||||||
use crate::{CliprdrError, CliprdrServiceContext};
|
|
||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
pub mod windows;
|
pub mod windows;
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
@@ -16,76 +13,4 @@ pub fn create_cliprdr_context(
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "unix-file-copy-paste")]
|
#[cfg(feature = "unix-file-copy-paste")]
|
||||||
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
|
||||||
/// use FUSE for file pasting on these platforms
|
|
||||||
pub mod fuse;
|
|
||||||
#[cfg(feature = "unix-file-copy-paste")]
|
|
||||||
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
|
||||||
pub mod unix;
|
pub mod unix;
|
||||||
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
|
||||||
pub fn create_cliprdr_context(
|
|
||||||
_enable_files: bool,
|
|
||||||
_enable_others: bool,
|
|
||||||
_response_wait_timeout_secs: u32,
|
|
||||||
) -> crate::ResultType<Box<dyn crate::CliprdrServiceContext>> {
|
|
||||||
#[cfg(feature = "unix-file-copy-paste")]
|
|
||||||
{
|
|
||||||
use std::{fs::Permissions, os::unix::prelude::PermissionsExt};
|
|
||||||
|
|
||||||
use hbb_common::{config::APP_NAME, log};
|
|
||||||
|
|
||||||
if !_enable_files {
|
|
||||||
return Ok(Box::new(DummyCliprdrContext {}) as Box<_>);
|
|
||||||
}
|
|
||||||
|
|
||||||
let timeout = std::time::Duration::from_secs(_response_wait_timeout_secs as u64);
|
|
||||||
|
|
||||||
let app_name = APP_NAME.read().unwrap().clone();
|
|
||||||
|
|
||||||
let mnt_path = format!("/tmp/{}/{}", app_name, "cliprdr");
|
|
||||||
|
|
||||||
// this function must be called after the main IPC is up
|
|
||||||
std::fs::create_dir(&mnt_path).ok();
|
|
||||||
std::fs::set_permissions(&mnt_path, Permissions::from_mode(0o777)).ok();
|
|
||||||
|
|
||||||
log::info!("clear previously mounted cliprdr FUSE");
|
|
||||||
if let Err(e) = std::process::Command::new("umount").arg(&mnt_path).status() {
|
|
||||||
log::warn!("umount {:?} may fail: {:?}", mnt_path, e);
|
|
||||||
}
|
|
||||||
|
|
||||||
let unix_ctx = unix::ClipboardContext::new(timeout, mnt_path.parse()?)?;
|
|
||||||
log::debug!("start cliprdr FUSE");
|
|
||||||
unix_ctx.run()?;
|
|
||||||
|
|
||||||
Ok(Box::new(unix_ctx) as Box<_>)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(feature = "unix-file-copy-paste"))]
|
|
||||||
return Ok(Box::new(DummyCliprdrContext {}) as Box<_>);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
|
||||||
struct DummyCliprdrContext {}
|
|
||||||
|
|
||||||
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
|
||||||
impl CliprdrServiceContext for DummyCliprdrContext {
|
|
||||||
fn set_is_stopped(&mut self) -> Result<(), CliprdrError> {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
fn empty_clipboard(&mut self, _conn_id: i32) -> Result<bool, CliprdrError> {
|
|
||||||
Ok(true)
|
|
||||||
}
|
|
||||||
fn server_clip_file(
|
|
||||||
&mut self,
|
|
||||||
_conn_id: i32,
|
|
||||||
_msg: crate::ClipboardFile,
|
|
||||||
) -> Result<(), crate::CliprdrError> {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "unix-file-copy-paste")]
|
|
||||||
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
|
||||||
// begin of epoch used by microsoft
|
|
||||||
// 1601-01-01 00:00:00 + LDAP_EPOCH_DELTA*(100 ns) = 1970-01-01 00:00:00
|
|
||||||
const LDAP_EPOCH_DELTA: u64 = 116444772610000000;
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user