mirror of
https://github.com/rustdesk/rustdesk.git
synced 2026-03-14 16:51:04 +03:00
Compare commits
196 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
a23822074e | ||
|
|
3d17bf4990 | ||
|
|
fd67be4a16 | ||
|
|
e6edf39305 | ||
|
|
34d2c62781 | ||
|
|
bd0a33e467 | ||
|
|
b8d36b6558 | ||
|
|
f38d89aaee | ||
|
|
773b9d6645 | ||
|
|
dea99ffb3a | ||
|
|
3251045e22 | ||
|
|
dc58c85e30 | ||
|
|
5a2a94d2cc | ||
|
|
f330953f4f | ||
|
|
8d4c86fe7f | ||
|
|
f8c2713c5b | ||
|
|
082a66b282 | ||
|
|
743b0ce8ce | ||
|
|
9d9b67aca5 | ||
|
|
d60b5a6ca0 | ||
|
|
e0ed6ee986 | ||
|
|
d3f0c80e94 | ||
|
|
b32ff87c6e | ||
|
|
b91b49229a | ||
|
|
afc8bb71dc | ||
|
|
4f86169f7f | ||
|
|
2cf43042e6 | ||
|
|
734fb8d6f7 | ||
|
|
3c7f6d3127 | ||
|
|
b99c540210 | ||
|
|
84dab0e96f | ||
|
|
458a88fb89 | ||
|
|
8a70932cd6 | ||
|
|
edfae98a01 | ||
|
|
d61c99b105 | ||
|
|
30a11bfe0a | ||
|
|
9d2bdfefb1 | ||
|
|
34b93c6f83 | ||
|
|
152d0ce74b | ||
|
|
32a3bcdc4f | ||
|
|
314c93b210 | ||
|
|
b64f6271e2 | ||
|
|
ac044c4049 | ||
|
|
0973f51df9 | ||
|
|
02b046bdbf | ||
|
|
02c274aeb6 | ||
|
|
ab6a6ca17d | ||
|
|
28d38cd71d | ||
|
|
b487f297b8 | ||
|
|
64654ee7cf | ||
|
|
1c99eb5500 | ||
|
|
74dd0c8fa0 | ||
|
|
d26fea41ee | ||
|
|
bc211c8031 | ||
|
|
d4cb7d68c5 | ||
|
|
608d7d55d5 | ||
|
|
4a49fbe4a6 | ||
|
|
f760e21ff8 | ||
|
|
251e1a3487 | ||
|
|
b990ff3782 | ||
|
|
c5426b0fbc | ||
|
|
8b710f62c8 | ||
|
|
0707e791e8 | ||
|
|
a07392e6b8 | ||
|
|
9125a68f81 | ||
|
|
9ee77a9b92 | ||
|
|
304e0e465d | ||
|
|
5d2bb9c995 | ||
|
|
ddc172bdfa | ||
|
|
06c7bc137f | ||
|
|
9e4cc91a14 | ||
|
|
0a28d09ff8 | ||
|
|
ab89d84a8f | ||
|
|
0aa98eac6d | ||
|
|
d4aa2b7ce4 | ||
|
|
35b4535ebc | ||
|
|
f0be80c253 | ||
|
|
a79a9f697b | ||
|
|
6082bb2754 | ||
|
|
4e6a43288e | ||
|
|
72a1f1161e | ||
|
|
912f5265f1 | ||
|
|
5eb2c31207 | ||
|
|
062c8d582c | ||
|
|
68b07505ab | ||
|
|
d3efcd4223 | ||
|
|
a277b022ff | ||
|
|
740c5358ab | ||
|
|
7978e0301d | ||
|
|
0f070b0108 | ||
|
|
6f0cb3b8c2 | ||
|
|
d0ef52e418 | ||
|
|
69277dd16b | ||
|
|
faf97c770c | ||
|
|
78088360ca | ||
|
|
5cfd1701fb | ||
|
|
a4bd23c9de | ||
|
|
12c1337b7b | ||
|
|
040253b319 | ||
|
|
44fa83d080 | ||
|
|
4f7e10bac6 | ||
|
|
d1fdcf1b16 | ||
|
|
4c12b83068 | ||
|
|
f86c88b3d8 | ||
|
|
697dd87383 | ||
|
|
0b8cccd8be | ||
|
|
32dbc0c8fb | ||
|
|
bae4a2c710 | ||
|
|
711ed28846 | ||
|
|
e6c5064ce5 | ||
|
|
0f5f9f6524 | ||
|
|
ce7867c1c0 | ||
|
|
415d2c5c60 | ||
|
|
a289eae07c | ||
|
|
1c9b456456 | ||
|
|
eba19e67ff | ||
|
|
adc5a7be51 | ||
|
|
26d23d588a |
@@ -12,3 +12,5 @@ rustflags = [
|
|||||||
#rustflags = [
|
#rustflags = [
|
||||||
# "-C", "link-args=-Wl,-Bstatic -lc -Wl,-Bdynamic"
|
# "-C", "link-args=-Wl,-Bstatic -lc -Wl,-Bdynamic"
|
||||||
#]
|
#]
|
||||||
|
[net]
|
||||||
|
git-fetch-with-cli = true
|
||||||
|
|||||||
8
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
8
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
@@ -26,8 +26,8 @@ body:
|
|||||||
- type: input
|
- type: input
|
||||||
id: os
|
id: os
|
||||||
attributes:
|
attributes:
|
||||||
label: Operating system(s) on local side and remote side
|
label: Operating system(s) on local (controlling) side and remote (controlled) side
|
||||||
description: What operating system(s) do you see this bug on? local side -> remote side.
|
description: What operating system(s) do you see this bug on? local (controlling) side -> remote (controlled) side.
|
||||||
placeholder: |
|
placeholder: |
|
||||||
Windows 10 -> osx
|
Windows 10 -> osx
|
||||||
validations:
|
validations:
|
||||||
@@ -35,8 +35,8 @@ body:
|
|||||||
- type: input
|
- type: input
|
||||||
id: version
|
id: version
|
||||||
attributes:
|
attributes:
|
||||||
label: RustDesk Version(s) on local side and remote side
|
label: RustDesk Version(s) on local (controlling) side and remote (controlled) side
|
||||||
description: What RustDesk version(s) do you see this bug on? local side -> remote side.
|
description: What RustDesk version(s) do you see this bug on? local (controlling) side -> remote (controlled) side.
|
||||||
placeholder: |
|
placeholder: |
|
||||||
1.1.9 -> 1.1.8
|
1.1.9 -> 1.1.8
|
||||||
validations:
|
validations:
|
||||||
|
|||||||
42
.github/patches/flutter_3.24.4_dropdown_menu_enableFilter.diff
vendored
Normal file
42
.github/patches/flutter_3.24.4_dropdown_menu_enableFilter.diff
vendored
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
diff --git a/packages/flutter/lib/src/material/dropdown_menu.dart b/packages/flutter/lib/src/material/dropdown_menu.dart
|
||||||
|
index 7e634cd2aa..c1e9acc295 100644
|
||||||
|
--- a/packages/flutter/lib/src/material/dropdown_menu.dart
|
||||||
|
+++ b/packages/flutter/lib/src/material/dropdown_menu.dart
|
||||||
|
@@ -475,7 +475,7 @@ class _DropdownMenuState<T> extends State<DropdownMenu<T>> {
|
||||||
|
final GlobalKey _leadingKey = GlobalKey();
|
||||||
|
late List<GlobalKey> buttonItemKeys;
|
||||||
|
final MenuController _controller = MenuController();
|
||||||
|
- late bool _enableFilter;
|
||||||
|
+ bool _enableFilter = false;
|
||||||
|
late List<DropdownMenuEntry<T>> filteredEntries;
|
||||||
|
List<Widget>? _initialMenu;
|
||||||
|
int? currentHighlight;
|
||||||
|
@@ -524,6 +524,11 @@ class _DropdownMenuState<T> extends State<DropdownMenu<T>> {
|
||||||
|
}
|
||||||
|
_localTextEditingController = widget.controller ?? TextEditingController();
|
||||||
|
}
|
||||||
|
+ if (oldWidget.enableFilter != widget.enableFilter) {
|
||||||
|
+ if (!widget.enableFilter) {
|
||||||
|
+ _enableFilter = false;
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
if (oldWidget.enableSearch != widget.enableSearch) {
|
||||||
|
if (!widget.enableSearch) {
|
||||||
|
currentHighlight = null;
|
||||||
|
@@ -663,6 +668,7 @@ class _DropdownMenuState<T> extends State<DropdownMenu<T>> {
|
||||||
|
);
|
||||||
|
currentHighlight = widget.enableSearch ? i : null;
|
||||||
|
widget.onSelected?.call(entry.value);
|
||||||
|
+ _enableFilter = false;
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
requestFocusOnHover: false,
|
||||||
|
@@ -735,6 +741,8 @@ class _DropdownMenuState<T> extends State<DropdownMenu<T>> {
|
||||||
|
if (_enableFilter) {
|
||||||
|
filteredEntries = widget.filterCallback?.call(filteredEntries, _localTextEditingController!.text)
|
||||||
|
?? filter(widget.dropdownMenuEntries, _localTextEditingController!);
|
||||||
|
+ } else {
|
||||||
|
+ filteredEntries = widget.dropdownMenuEntries;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (widget.enableSearch) {
|
||||||
13
.github/workflows/bridge.yml
vendored
13
.github/workflows/bridge.yml
vendored
@@ -6,7 +6,7 @@ on:
|
|||||||
workflow_call:
|
workflow_call:
|
||||||
|
|
||||||
env:
|
env:
|
||||||
FLUTTER_VERSION: "3.19.6"
|
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 +25,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,12 +75,13 @@ jobs:
|
|||||||
- name: Install flutter rust bridge deps
|
- name: Install flutter rust bridge deps
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
cargo install flutter_rust_bridge_codegen --version ${{ env.FLUTTER_RUST_BRIDGE_VERSION }} --features "uuid"
|
cargo install flutter_rust_bridge_codegen --version ${{ env.FLUTTER_RUST_BRIDGE_VERSION }} --features "uuid" --locked
|
||||||
pushd flutter && flutter pub get && popd
|
pushd flutter && sed -i -e 's/extended_text: 14.0.0/extended_text: 13.0.0/g' pubspec.yaml && flutter pub get && popd
|
||||||
|
|
||||||
- name: Run flutter rust bridge
|
- name: Run flutter rust bridge
|
||||||
run: |
|
run: |
|
||||||
~/.cargo/bin/flutter_rust_bridge_codegen --rust-input ./src/flutter_ffi.rs --dart-output ./flutter/lib/generated_bridge.dart
|
~/.cargo/bin/flutter_rust_bridge_codegen --rust-input ./src/flutter_ffi.rs --dart-output ./flutter/lib/generated_bridge.dart --c-output ./flutter/macos/Runner/bridge_generated.h
|
||||||
|
cp ./flutter/macos/Runner/bridge_generated.h ./flutter/ios/Runner/bridge_generated.h
|
||||||
|
|
||||||
- name: Upload Artifact
|
- name: Upload Artifact
|
||||||
uses: actions/upload-artifact@master
|
uses: actions/upload-artifact@master
|
||||||
@@ -89,3 +92,5 @@ jobs:
|
|||||||
./src/bridge_generated.io.rs
|
./src/bridge_generated.io.rs
|
||||||
./flutter/lib/generated_bridge.dart
|
./flutter/lib/generated_bridge.dart
|
||||||
./flutter/lib/generated_bridge.freezed.dart
|
./flutter/lib/generated_bridge.freezed.dart
|
||||||
|
./flutter/macos/Runner/bridge_generated.h
|
||||||
|
./flutter/ios/Runner/bridge_generated.h
|
||||||
|
|||||||
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
|
||||||
|
|||||||
281
.github/workflows/flutter-build.yml
vendored
281
.github/workflows/flutter-build.yml
vendored
@@ -20,21 +20,21 @@ on:
|
|||||||
env:
|
env:
|
||||||
SCITER_RUST_VERSION: "1.75" # https://github.com/rustdesk/rustdesk/discussions/7503, also 1.78 has ABI change which causes our sciter version not working, https://blog.rust-lang.org/2024/03/30/i128-layout-update.html
|
SCITER_RUST_VERSION: "1.75" # https://github.com/rustdesk/rustdesk/discussions/7503, also 1.78 has ABI change which causes our sciter version not working, https://blog.rust-lang.org/2024/03/30/i128-layout-update.html
|
||||||
RUST_VERSION: "1.75" # sciter failed on m1 with 1.78 because of https://blog.rust-lang.org/2024/03/30/i128-layout-update.html
|
RUST_VERSION: "1.75" # sciter failed on m1 with 1.78 because of https://blog.rust-lang.org/2024/03/30/i128-layout-update.html
|
||||||
|
MAC_RUST_VERSION: "1.81" # 1.81 is requred for macos, because of https://github.com/yury/cidre requires 1.81
|
||||||
CARGO_NDK_VERSION: "3.1.2"
|
CARGO_NDK_VERSION: "3.1.2"
|
||||||
SCITER_ARMV7_CMAKE_VERSION: "3.29.7"
|
SCITER_ARMV7_CMAKE_VERSION: "3.29.7"
|
||||||
SCITER_NASM_DEBVERSION: "2.14-1"
|
SCITER_NASM_DEBVERSION: "2.14-1"
|
||||||
LLVM_VERSION: "15.0.6"
|
LLVM_VERSION: "15.0.6"
|
||||||
FLUTTER_VERSION: "3.19.6"
|
FLUTTER_VERSION: "3.24.5"
|
||||||
ANDROID_FLUTTER_VERSION: "3.13.9" # >= 3.16 is very slow on my android phone, but work well on most of others. We may switch to new flutter after changing to texture rendering (I believe it can solve my problem).
|
ANDROID_FLUTTER_VERSION: "3.24.5"
|
||||||
FLUTTER_RUST_BRIDGE_VERSION: "1.80.1"
|
|
||||||
# for arm64 linux because official Dart SDK does not work
|
# for arm64 linux because official Dart SDK does not work
|
||||||
FLUTTER_ELINUX_VERSION: "3.16.9"
|
FLUTTER_ELINUX_VERSION: "3.16.9"
|
||||||
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: 2024.11.16
|
||||||
VCPKG_COMMIT_ID: "1de2026f28ead93ff1773e6e680387643e914ea1"
|
VCPKG_COMMIT_ID: "b2cb0da531c2f1f740045bfe7c4dac59f0b2b69c"
|
||||||
VERSION: "1.3.2"
|
VERSION: "1.3.7"
|
||||||
NDK_VERSION: "r27b"
|
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 }}"
|
||||||
@@ -46,6 +46,9 @@ env:
|
|||||||
SIGN_BASE_URL: "${{ secrets.SIGN_BASE_URL }}"
|
SIGN_BASE_URL: "${{ secrets.SIGN_BASE_URL }}"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
generate-bridge:
|
||||||
|
uses: ./.github/workflows/bridge.yml
|
||||||
|
|
||||||
build-RustDeskTempTopMostWindow:
|
build-RustDeskTempTopMostWindow:
|
||||||
uses: ./.github/workflows/third-party-RustDeskTempTopMostWindow.yml
|
uses: ./.github/workflows/third-party-RustDeskTempTopMostWindow.yml
|
||||||
with:
|
with:
|
||||||
@@ -59,7 +62,7 @@ jobs:
|
|||||||
|
|
||||||
build-for-windows-flutter:
|
build-for-windows-flutter:
|
||||||
name: ${{ matrix.job.target }}
|
name: ${{ matrix.job.target }}
|
||||||
needs: [build-RustDeskTempTopMostWindow]
|
needs: [build-RustDeskTempTopMostWindow, generate-bridge]
|
||||||
runs-on: ${{ matrix.job.os }}
|
runs-on: ${{ matrix.job.os }}
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
@@ -84,6 +87,14 @@ jobs:
|
|||||||
|
|
||||||
- name: Checkout source code
|
- name: Checkout source code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
submodules: recursive
|
||||||
|
|
||||||
|
- name: Restore bridge files
|
||||||
|
uses: actions/download-artifact@master
|
||||||
|
with:
|
||||||
|
name: bridge-artifact
|
||||||
|
path: ./
|
||||||
|
|
||||||
- name: Install LLVM and Clang
|
- name: Install LLVM and Clang
|
||||||
uses: KyleMayes/install-llvm-action@v1
|
uses: KyleMayes/install-llvm-action@v1
|
||||||
@@ -95,7 +106,22 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
channel: "stable"
|
channel: "stable"
|
||||||
flutter-version: ${{ env.FLUTTER_VERSION }}
|
flutter-version: ${{ env.FLUTTER_VERSION }}
|
||||||
cache: true
|
|
||||||
|
# https://github.com/flutter/flutter/issues/155685
|
||||||
|
- name: Replace engine with rustdesk custom flutter engine
|
||||||
|
run: |
|
||||||
|
flutter doctor -v
|
||||||
|
flutter precache --windows
|
||||||
|
Invoke-WebRequest -Uri https://github.com/rustdesk/engine/releases/download/main/windows-x64-release.zip -OutFile windows-x64-release.zip
|
||||||
|
Expand-Archive -Path windows-x64-release.zip -DestinationPath windows-x64-release
|
||||||
|
mv -Force windows-x64-release/* C:/hostedtoolcache/windows/flutter/stable-${{ env.FLUTTER_VERSION }}-x64/bin/cache/artifacts/engine/windows-x64-release/
|
||||||
|
|
||||||
|
- name: Patch flutter
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
cp .github/patches/flutter_3.24.4_dropdown_menu_enableFilter.diff $(dirname $(dirname $(which flutter)))
|
||||||
|
cd $(dirname $(dirname $(which flutter)))
|
||||||
|
[[ "3.24.5" == ${{env.FLUTTER_VERSION}} ]] && git apply flutter_3.24.4_dropdown_menu_enableFilter.diff
|
||||||
|
|
||||||
- name: Install Rust toolchain
|
- name: Install Rust toolchain
|
||||||
uses: dtolnay/rust-toolchain@v1
|
uses: dtolnay/rust-toolchain@v1
|
||||||
@@ -108,13 +134,6 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
prefix-key: ${{ matrix.job.os }}
|
prefix-key: ${{ matrix.job.os }}
|
||||||
|
|
||||||
- name: Install flutter rust bridge deps
|
|
||||||
run: |
|
|
||||||
git config --global core.longpaths true
|
|
||||||
cargo install flutter_rust_bridge_codegen --version ${{ env.FLUTTER_RUST_BRIDGE_VERSION }} --features "uuid"
|
|
||||||
Push-Location flutter ; flutter pub get ; Pop-Location
|
|
||||||
~/.cargo/bin/flutter_rust_bridge_codegen --rust-input ./src/flutter_ffi.rs --dart-output ./flutter/lib/generated_bridge.dart
|
|
||||||
|
|
||||||
- name: Setup vcpkg with Github Actions binary cache
|
- name: Setup vcpkg with Github Actions binary cache
|
||||||
uses: lukka/run-vcpkg@v11
|
uses: lukka/run-vcpkg@v11
|
||||||
with:
|
with:
|
||||||
@@ -139,6 +158,7 @@ jobs:
|
|||||||
done
|
done
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
head -n 100 "${VCPKG_ROOT}/buildtrees/ffmpeg/build-${{ matrix.job.vcpkg-triplet }}-rel-out.log" || true
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|
||||||
- name: Build rustdesk
|
- name: Build rustdesk
|
||||||
@@ -258,6 +278,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
|
||||||
@@ -299,6 +321,7 @@ jobs:
|
|||||||
done
|
done
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
head -n 100 "${VCPKG_ROOT}/buildtrees/ffmpeg/build-${{ matrix.job.vcpkg-triplet }}-rel-out.log" || true
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|
||||||
- name: Build rustdesk
|
- name: Build rustdesk
|
||||||
@@ -374,6 +397,7 @@ jobs:
|
|||||||
# use build-for-macOS instead
|
# use build-for-macOS instead
|
||||||
if: false
|
if: false
|
||||||
runs-on: [self-hosted, macOS, ARM64]
|
runs-on: [self-hosted, macOS, ARM64]
|
||||||
|
needs: [generate-bridge]
|
||||||
steps:
|
steps:
|
||||||
- name: Export GitHub Actions cache environment variables
|
- name: Export GitHub Actions cache environment variables
|
||||||
uses: actions/github-script@v6
|
uses: actions/github-script@v6
|
||||||
@@ -384,13 +408,14 @@ jobs:
|
|||||||
|
|
||||||
- name: Checkout source code
|
- name: Checkout source code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
submodules: recursive
|
||||||
|
|
||||||
- name: Install flutter rust bridge deps
|
- name: Restore bridge files
|
||||||
shell: bash
|
uses: actions/download-artifact@master
|
||||||
run: |
|
with:
|
||||||
cargo install flutter_rust_bridge_codegen --version ${{ env.FLUTTER_RUST_BRIDGE_VERSION }} --features "uuid"
|
name: bridge-artifact
|
||||||
pushd flutter && flutter pub get && popd
|
path: ./
|
||||||
~/.cargo/bin/flutter_rust_bridge_codegen --rust-input ./src/flutter_ffi.rs --dart-output ./flutter/lib/generated_bridge.dart --c-output ./flutter/macos/Runner/bridge_generated.h
|
|
||||||
|
|
||||||
- name: Build rustdesk
|
- name: Build rustdesk
|
||||||
run: |
|
run: |
|
||||||
@@ -446,6 +471,7 @@ jobs:
|
|||||||
if: ${{ inputs.upload-artifact }}
|
if: ${{ inputs.upload-artifact }}
|
||||||
name: build rustdesk ios ipa
|
name: build rustdesk ios ipa
|
||||||
runs-on: ${{ matrix.job.os }}
|
runs-on: ${{ matrix.job.os }}
|
||||||
|
needs: [generate-bridge]
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
@@ -469,12 +495,20 @@ 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:
|
||||||
channel: "stable"
|
channel: "stable"
|
||||||
flutter-version: ${{ env.FLUTTER_VERSION }}
|
flutter-version: ${{ env.FLUTTER_VERSION }}
|
||||||
|
|
||||||
|
- name: Patch flutter
|
||||||
|
run: |
|
||||||
|
cd $(dirname $(dirname $(which flutter)))
|
||||||
|
[[ "3.24.5" == ${{env.FLUTTER_VERSION}} ]] && git apply ${{ github.workspace }}/.github/patches/flutter_3.24.4_dropdown_menu_enableFilter.diff
|
||||||
|
|
||||||
- name: Setup vcpkg with Github Actions binary cache
|
- name: Setup vcpkg with Github Actions binary cache
|
||||||
uses: lukka/run-vcpkg@v11
|
uses: lukka/run-vcpkg@v11
|
||||||
with:
|
with:
|
||||||
@@ -496,6 +530,7 @@ jobs:
|
|||||||
done
|
done
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
head -n 100 "${VCPKG_ROOT}/buildtrees/ffmpeg/build-${{ matrix.job.vcpkg-triplet }}-rel-out.log" || true
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|
||||||
- name: Install Rust toolchain
|
- name: Install Rust toolchain
|
||||||
@@ -510,17 +545,22 @@ jobs:
|
|||||||
prefix-key: rustdesk-lib-cache-ios
|
prefix-key: rustdesk-lib-cache-ios
|
||||||
key: ${{ matrix.job.target }}
|
key: ${{ matrix.job.target }}
|
||||||
|
|
||||||
- name: Install flutter rust bridge deps
|
- name: Restore bridge files
|
||||||
shell: bash
|
uses: actions/download-artifact@master
|
||||||
run: |
|
with:
|
||||||
cargo install flutter_rust_bridge_codegen --version ${{ env.FLUTTER_RUST_BRIDGE_VERSION }} --features "uuid"
|
name: bridge-artifact
|
||||||
pushd flutter && flutter pub get && popd
|
path: ./
|
||||||
~/.cargo/bin/flutter_rust_bridge_codegen --rust-input ./src/flutter_ffi.rs --dart-output ./flutter/lib/generated_bridge.dart --c-output ./flutter/ios/Runner/bridge_generated.h
|
|
||||||
|
|
||||||
- name: Build rustdesk lib
|
- name: Build rustdesk lib
|
||||||
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
|
||||||
@@ -550,6 +590,7 @@ jobs:
|
|||||||
#if: ${{ inputs.upload-artifact }}
|
#if: ${{ inputs.upload-artifact }}
|
||||||
if: false
|
if: false
|
||||||
runs-on: [self-hosted, macOS, ARM64]
|
runs-on: [self-hosted, macOS, ARM64]
|
||||||
|
needs: [generate-bridge]
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
steps:
|
steps:
|
||||||
@@ -562,15 +603,16 @@ 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"
|
||||||
|
|
||||||
- name: Install flutter rust bridge deps
|
- name: Restore bridge files
|
||||||
shell: bash
|
uses: actions/download-artifact@master
|
||||||
run: |
|
with:
|
||||||
cargo install flutter_rust_bridge_codegen --version ${{ env.FLUTTER_RUST_BRIDGE_VERSION }} --features "uuid"
|
name: bridge-artifact
|
||||||
pushd flutter && flutter pub get && popd
|
path: ./
|
||||||
~/.cargo/bin/flutter_rust_bridge_codegen --rust-input ./src/flutter_ffi.rs --dart-output ./flutter/lib/generated_bridge.dart --c-output ./flutter/ios/Runner/bridge_generated.h
|
|
||||||
|
|
||||||
- name: Build rustdesk lib
|
- name: Build rustdesk lib
|
||||||
run: |
|
run: |
|
||||||
@@ -605,6 +647,7 @@ jobs:
|
|||||||
build-for-macOS:
|
build-for-macOS:
|
||||||
name: ${{ matrix.job.target }}
|
name: ${{ matrix.job.target }}
|
||||||
runs-on: ${{ matrix.job.os }}
|
runs-on: ${{ matrix.job.os }}
|
||||||
|
needs: [generate-bridge]
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
@@ -614,13 +657,15 @@ jobs:
|
|||||||
os: macos-13, #macos-latest or macos-14 use M1 now, https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners/about-github-hosted-runners#:~:text=14%20GB-,macos%2Dlatest%20or%20macos%2D14,-The%20macos%2Dlatestlabel
|
os: macos-13, #macos-latest or macos-14 use M1 now, https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners/about-github-hosted-runners#:~:text=14%20GB-,macos%2Dlatest%20or%20macos%2D14,-The%20macos%2Dlatestlabel
|
||||||
extra-build-args: "",
|
extra-build-args: "",
|
||||||
arch: x86_64,
|
arch: x86_64,
|
||||||
|
vcpkg-triplet: x64-osx,
|
||||||
}
|
}
|
||||||
- {
|
- {
|
||||||
target: aarch64-apple-darwin,
|
target: aarch64-apple-darwin,
|
||||||
os: macos-latest,
|
os: macos-latest,
|
||||||
# extra-build-args: "--disable-flutter-texture-render", # disable this for mac, because we see a lot of users reporting flickering both on arm and x64, and we can not confirm if texture rendering has better performance if htere is no vram, https://github.com/rustdesk/rustdesk/issues/6296
|
# extra-build-args: "--disable-flutter-texture-render", # disable this for mac, because we see a lot of users reporting flickering both on arm and x64, and we can not confirm if texture rendering has better performance if htere is no vram, https://github.com/rustdesk/rustdesk/issues/6296
|
||||||
extra-build-args: "",
|
extra-build-args: "--screencapturekit",
|
||||||
arch: aarch64,
|
arch: aarch64,
|
||||||
|
vcpkg-triplet: arm64-osx,
|
||||||
}
|
}
|
||||||
steps:
|
steps:
|
||||||
- name: Export GitHub Actions cache environment variables
|
- name: Export GitHub Actions cache environment variables
|
||||||
@@ -632,6 +677,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
|
||||||
@@ -668,7 +715,13 @@ jobs:
|
|||||||
|
|
||||||
- name: Install build runtime
|
- name: Install build runtime
|
||||||
run: |
|
run: |
|
||||||
brew install llvm create-dmg nasm cmake gcc wget ninja pkg-config
|
brew install llvm create-dmg nasm cmake gcc wget ninja
|
||||||
|
# pkg-config is handled in a separate step, because it may be already installed by `macos-latest`(14.7.1) runner
|
||||||
|
if command -v pkg-config &>/dev/null; then
|
||||||
|
echo "pkg-config is already installed"
|
||||||
|
else
|
||||||
|
brew install pkg-config
|
||||||
|
fi
|
||||||
|
|
||||||
- name: Install flutter
|
- name: Install flutter
|
||||||
uses: subosito/flutter-action@v2
|
uses: subosito/flutter-action@v2
|
||||||
@@ -676,6 +729,11 @@ jobs:
|
|||||||
channel: "stable"
|
channel: "stable"
|
||||||
flutter-version: ${{ env.FLUTTER_VERSION }}
|
flutter-version: ${{ env.FLUTTER_VERSION }}
|
||||||
|
|
||||||
|
- name: Patch flutter
|
||||||
|
run: |
|
||||||
|
cd $(dirname $(dirname $(which flutter)))
|
||||||
|
[[ "3.24.5" == ${{env.FLUTTER_VERSION}} ]] && git apply ${{ github.workspace }}/.github/patches/flutter_3.24.4_dropdown_menu_enableFilter.diff
|
||||||
|
|
||||||
- name: Workaround for flutter issue
|
- name: Workaround for flutter issue
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
@@ -687,7 +745,7 @@ jobs:
|
|||||||
- name: Install Rust toolchain
|
- name: Install Rust toolchain
|
||||||
uses: dtolnay/rust-toolchain@v1
|
uses: dtolnay/rust-toolchain@v1
|
||||||
with:
|
with:
|
||||||
toolchain: ${{ env.RUST_VERSION }}
|
toolchain: ${{ env.MAC_RUST_VERSION }}
|
||||||
targets: ${{ matrix.job.target }}
|
targets: ${{ matrix.job.target }}
|
||||||
components: "rustfmt"
|
components: "rustfmt"
|
||||||
|
|
||||||
@@ -695,12 +753,11 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
prefix-key: ${{ matrix.job.os }}
|
prefix-key: ${{ matrix.job.os }}
|
||||||
|
|
||||||
- name: Install flutter rust bridge deps
|
- name: Restore bridge files
|
||||||
shell: bash
|
uses: actions/download-artifact@master
|
||||||
run: |
|
with:
|
||||||
cargo install flutter_rust_bridge_codegen --version ${{ env.FLUTTER_RUST_BRIDGE_VERSION }} --features "uuid"
|
name: bridge-artifact
|
||||||
pushd flutter && flutter pub get && popd
|
path: ./
|
||||||
~/.cargo/bin/flutter_rust_bridge_codegen --rust-input ./src/flutter_ffi.rs --dart-output ./flutter/lib/generated_bridge.dart --c-output ./flutter/macos/Runner/bridge_generated.h
|
|
||||||
|
|
||||||
- name: Setup vcpkg with Github Actions binary cache
|
- name: Setup vcpkg with Github Actions binary cache
|
||||||
uses: lukka/run-vcpkg@v11
|
uses: lukka/run-vcpkg@v11
|
||||||
@@ -722,6 +779,7 @@ jobs:
|
|||||||
done
|
done
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
head -n 100 "${VCPKG_ROOT}/buildtrees/ffmpeg/build-${{ matrix.job.vcpkg-triplet }}-rel-out.log" || true
|
||||||
|
|
||||||
- name: Show version information (Rust, cargo, Clang)
|
- name: Show version information (Rust, cargo, Clang)
|
||||||
shell: bash
|
shell: bash
|
||||||
@@ -735,6 +793,13 @@ jobs:
|
|||||||
|
|
||||||
- name: Build rustdesk
|
- name: Build rustdesk
|
||||||
run: |
|
run: |
|
||||||
|
if [ "${{ matrix.job.target }}" = "aarch64-apple-darwin" ]; then
|
||||||
|
MIN_MACOS_VERSION="12.3"
|
||||||
|
sed -i -e "s/MACOSX_DEPLOYMENT_TARGET\=[0-9]*.[0-9]*/MACOSX_DEPLOYMENT_TARGET=${MIN_MACOS_VERSION}/" build.py
|
||||||
|
sed -i -e "s/platform :osx, '.*'/platform :osx, '${MIN_MACOS_VERSION}'/" flutter/macos/Podfile
|
||||||
|
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
|
||||||
|
fi
|
||||||
./build.py --flutter --hwcodec ${{ matrix.job.extra-build-args }}
|
./build.py --flutter --hwcodec ${{ matrix.job.extra-build-args }}
|
||||||
|
|
||||||
- name: create unsigned dmg
|
- name: create unsigned dmg
|
||||||
@@ -822,11 +887,8 @@ jobs:
|
|||||||
tag_name: ${{ env.TAG_NAME }}
|
tag_name: ${{ env.TAG_NAME }}
|
||||||
files: rustdesk-${{ env.VERSION }}-unsigned.tar.gz
|
files: rustdesk-${{ env.VERSION }}-unsigned.tar.gz
|
||||||
|
|
||||||
generate-bridge-linux:
|
|
||||||
uses: ./.github/workflows/bridge.yml
|
|
||||||
|
|
||||||
build-rustdesk-android:
|
build-rustdesk-android:
|
||||||
needs: [generate-bridge-linux]
|
needs: [generate-bridge]
|
||||||
name: build rustdesk android apk ${{ matrix.job.target }}
|
name: build rustdesk android apk ${{ matrix.job.target }}
|
||||||
runs-on: ${{ matrix.job.os }}
|
runs-on: ${{ matrix.job.os }}
|
||||||
strategy:
|
strategy:
|
||||||
@@ -894,7 +956,6 @@ jobs:
|
|||||||
libpam0g-dev \
|
libpam0g-dev \
|
||||||
libpulse-dev \
|
libpulse-dev \
|
||||||
libva-dev \
|
libva-dev \
|
||||||
libvdpau-dev \
|
|
||||||
libxcb-randr0-dev \
|
libxcb-randr0-dev \
|
||||||
libxcb-shape0-dev \
|
libxcb-shape0-dev \
|
||||||
libxcb-xfixes0-dev \
|
libxcb-xfixes0-dev \
|
||||||
@@ -903,18 +964,27 @@ jobs:
|
|||||||
llvm-10-dev \
|
llvm-10-dev \
|
||||||
nasm \
|
nasm \
|
||||||
ninja-build \
|
ninja-build \
|
||||||
openjdk-11-jdk-headless \
|
openjdk-17-jdk-headless \
|
||||||
pkg-config \
|
pkg-config \
|
||||||
tree \
|
tree \
|
||||||
wget
|
wget
|
||||||
|
|
||||||
- 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:
|
||||||
channel: "stable"
|
channel: "stable"
|
||||||
flutter-version: ${{ env.ANDROID_FLUTTER_VERSION }}
|
flutter-version: ${{ env.ANDROID_FLUTTER_VERSION }}
|
||||||
|
|
||||||
|
- name: Patch flutter
|
||||||
|
run: |
|
||||||
|
cd $(dirname $(dirname $(which flutter)))
|
||||||
|
[[ "3.24.5" == ${{env.ANDROID_FLUTTER_VERSION}} ]] && git apply ${{ github.workspace }}/.github/patches/flutter_3.24.4_dropdown_menu_enableFilter.diff
|
||||||
|
|
||||||
- uses: nttld/setup-ndk@v1
|
- uses: nttld/setup-ndk@v1
|
||||||
id: setup-ndk
|
id: setup-ndk
|
||||||
with:
|
with:
|
||||||
@@ -974,7 +1044,7 @@ jobs:
|
|||||||
key: ${{ matrix.job.target }}
|
key: ${{ matrix.job.target }}
|
||||||
|
|
||||||
- name: fix android for flutter 3.13
|
- name: fix android for flutter 3.13
|
||||||
if: $${{ env.ANDROID_FLUTTER_VERSION == '3.13.9' }}
|
if: ${{ env.ANDROID_FLUTTER_VERSION == '3.13.9' }}
|
||||||
run: |
|
run: |
|
||||||
cd flutter
|
cd flutter
|
||||||
sed -i 's/uni_links_desktop/#uni_links_desktop/g' pubspec.yaml
|
sed -i 's/uni_links_desktop/#uni_links_desktop/g' pubspec.yaml
|
||||||
@@ -989,7 +1059,7 @@ jobs:
|
|||||||
ANDROID_NDK_ROOT: ${{ steps.setup-ndk.outputs.ndk-path }}
|
ANDROID_NDK_ROOT: ${{ steps.setup-ndk.outputs.ndk-path }}
|
||||||
run: |
|
run: |
|
||||||
rustup target add ${{ matrix.job.target }}
|
rustup target add ${{ matrix.job.target }}
|
||||||
cargo install cargo-ndk --version ${{ env.CARGO_NDK_VERSION }}
|
cargo install cargo-ndk --version ${{ env.CARGO_NDK_VERSION }} --locked
|
||||||
case ${{ matrix.job.target }} in
|
case ${{ matrix.job.target }} in
|
||||||
aarch64-linux-android)
|
aarch64-linux-android)
|
||||||
./flutter/ndk_arm64.sh
|
./flutter/ndk_arm64.sh
|
||||||
@@ -1022,9 +1092,9 @@ jobs:
|
|||||||
- name: Build rustdesk
|
- name: Build rustdesk
|
||||||
shell: bash
|
shell: bash
|
||||||
env:
|
env:
|
||||||
JAVA_HOME: /usr/lib/jvm/java-11-openjdk-amd64
|
JAVA_HOME: /usr/lib/jvm/java-17-openjdk-amd64
|
||||||
run: |
|
run: |
|
||||||
export PATH=/usr/lib/jvm/java-11-openjdk-amd64/bin:$PATH
|
export PATH=/usr/lib/jvm/java-17-openjdk-amd64/bin:$PATH
|
||||||
# temporary use debug sign config
|
# temporary use debug sign config
|
||||||
sed -i "s/signingConfigs.release/signingConfigs.debug/g" ./flutter/android/app/build.gradle
|
sed -i "s/signingConfigs.release/signingConfigs.debug/g" ./flutter/android/app/build.gradle
|
||||||
case ${{ matrix.job.target }} in
|
case ${{ matrix.job.target }} in
|
||||||
@@ -1069,6 +1139,14 @@ jobs:
|
|||||||
mkdir -p signed-apk; pushd signed-apk
|
mkdir -p signed-apk; pushd signed-apk
|
||||||
mv ../rustdesk-${{ env.VERSION }}-${{ matrix.job.arch }}${{ matrix.job.suffix }}.apk .
|
mv ../rustdesk-${{ env.VERSION }}-${{ matrix.job.arch }}${{ matrix.job.suffix }}.apk .
|
||||||
|
|
||||||
|
# https://github.com/r0adkll/sign-android-release/issues/84#issuecomment-1889636075
|
||||||
|
- name: Setup sign tool version variable
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
BUILD_TOOL_VERSION=$(ls /usr/local/lib/android/sdk/build-tools/ | tail -n 1)
|
||||||
|
echo "ANDROID_SIGN_TOOL_VERSION=$BUILD_TOOL_VERSION" >> $GITHUB_ENV
|
||||||
|
echo Last build tool version is: $BUILD_TOOL_VERSION
|
||||||
|
|
||||||
- uses: r0adkll/sign-android-release@v1
|
- uses: r0adkll/sign-android-release@v1
|
||||||
name: Sign app APK
|
name: Sign app APK
|
||||||
if: env.ANDROID_SIGNING_KEY != null
|
if: env.ANDROID_SIGNING_KEY != null
|
||||||
@@ -1080,8 +1158,8 @@ jobs:
|
|||||||
keyStorePassword: ${{ secrets.ANDROID_KEY_STORE_PASSWORD }}
|
keyStorePassword: ${{ secrets.ANDROID_KEY_STORE_PASSWORD }}
|
||||||
keyPassword: ${{ secrets.ANDROID_KEY_PASSWORD }}
|
keyPassword: ${{ secrets.ANDROID_KEY_PASSWORD }}
|
||||||
env:
|
env:
|
||||||
# override default build-tools version (29.0.3) -- optional
|
# env.ANDROID_SIGN_TOOL_VERSION is set by Step "Setup sign tool version variable"
|
||||||
BUILD_TOOLS_VERSION: "30.0.2"
|
BUILD_TOOLS_VERSION: ${{ env.ANDROID_SIGN_TOOL_VERSION }}
|
||||||
|
|
||||||
- name: Upload Artifacts
|
- name: Upload Artifacts
|
||||||
if: env.ANDROID_SIGNING_KEY != null && env.UPLOAD_ARTIFACT == 'true'
|
if: env.ANDROID_SIGNING_KEY != null && env.UPLOAD_ARTIFACT == 'true'
|
||||||
@@ -1157,7 +1235,6 @@ jobs:
|
|||||||
libpam0g-dev \
|
libpam0g-dev \
|
||||||
libpulse-dev \
|
libpulse-dev \
|
||||||
libva-dev \
|
libva-dev \
|
||||||
libvdpau-dev \
|
|
||||||
libxcb-randr0-dev \
|
libxcb-randr0-dev \
|
||||||
libxcb-shape0-dev \
|
libxcb-shape0-dev \
|
||||||
libxcb-xfixes0-dev \
|
libxcb-xfixes0-dev \
|
||||||
@@ -1166,19 +1243,27 @@ jobs:
|
|||||||
llvm-10-dev \
|
llvm-10-dev \
|
||||||
nasm \
|
nasm \
|
||||||
ninja-build \
|
ninja-build \
|
||||||
openjdk-11-jdk-headless \
|
openjdk-17-jdk-headless \
|
||||||
pkg-config \
|
pkg-config \
|
||||||
tree \
|
tree \
|
||||||
wget
|
wget
|
||||||
|
|
||||||
- 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:
|
||||||
channel: "stable"
|
channel: "stable"
|
||||||
flutter-version: ${{ env.ANDROID_FLUTTER_VERSION }}
|
flutter-version: ${{ env.ANDROID_FLUTTER_VERSION }}
|
||||||
|
|
||||||
|
- name: Patch flutter
|
||||||
|
run: |
|
||||||
|
cd $(dirname $(dirname $(which flutter)))
|
||||||
|
[[ "3.24.5" == ${{env.ANDROID_FLUTTER_VERSION}} ]] && git apply ${{ github.workspace }}/.github/patches/flutter_3.24.4_dropdown_menu_enableFilter.diff
|
||||||
|
|
||||||
- name: Restore bridge files
|
- name: Restore bridge files
|
||||||
uses: actions/download-artifact@master
|
uses: actions/download-artifact@master
|
||||||
with:
|
with:
|
||||||
@@ -1211,7 +1296,7 @@ jobs:
|
|||||||
path: ./flutter/android/app/src/main/jniLibs/x86
|
path: ./flutter/android/app/src/main/jniLibs/x86
|
||||||
|
|
||||||
- name: fix android for flutter 3.13
|
- name: fix android for flutter 3.13
|
||||||
if: $${{ env.ANDROID_FLUTTER_VERSION == '3.13.9' }}
|
if: ${{ env.ANDROID_FLUTTER_VERSION == '3.13.9' }}
|
||||||
run: |
|
run: |
|
||||||
cd flutter
|
cd flutter
|
||||||
sed -i 's/uni_links_desktop/#uni_links_desktop/g' pubspec.yaml
|
sed -i 's/uni_links_desktop/#uni_links_desktop/g' pubspec.yaml
|
||||||
@@ -1223,9 +1308,9 @@ jobs:
|
|||||||
- name: Build rustdesk
|
- name: Build rustdesk
|
||||||
shell: bash
|
shell: bash
|
||||||
env:
|
env:
|
||||||
JAVA_HOME: /usr/lib/jvm/java-11-openjdk-amd64
|
JAVA_HOME: /usr/lib/jvm/java-17-openjdk-amd64
|
||||||
run: |
|
run: |
|
||||||
export PATH=/usr/lib/jvm/java-11-openjdk-amd64/bin:$PATH
|
export PATH=/usr/lib/jvm/java-17-openjdk-amd64/bin:$PATH
|
||||||
# temporary use debug sign config
|
# temporary use debug sign config
|
||||||
sed -i "s/signingConfigs.release/signingConfigs.debug/g" ./flutter/android/app/build.gradle
|
sed -i "s/signingConfigs.release/signingConfigs.debug/g" ./flutter/android/app/build.gradle
|
||||||
mv ./flutter/android/app/src/main/jniLibs/arm64-v8a/liblibrustdesk.so ./flutter/android/app/src/main/jniLibs/arm64-v8a/librustdesk.so
|
mv ./flutter/android/app/src/main/jniLibs/arm64-v8a/liblibrustdesk.so ./flutter/android/app/src/main/jniLibs/arm64-v8a/librustdesk.so
|
||||||
@@ -1245,6 +1330,14 @@ jobs:
|
|||||||
mkdir -p signed-apk
|
mkdir -p signed-apk
|
||||||
mv ./flutter/build/app/outputs/flutter-apk/app-${{ env.reltype }}.apk signed-apk/rustdesk-${{ env.VERSION }}-universal${{ env.suffix }}.apk
|
mv ./flutter/build/app/outputs/flutter-apk/app-${{ env.reltype }}.apk signed-apk/rustdesk-${{ env.VERSION }}-universal${{ env.suffix }}.apk
|
||||||
|
|
||||||
|
# https://github.com/r0adkll/sign-android-release/issues/84#issuecomment-1889636075
|
||||||
|
- name: Setup sign tool version variable
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
BUILD_TOOL_VERSION=$(ls /usr/local/lib/android/sdk/build-tools/ | tail -n 1)
|
||||||
|
echo "ANDROID_SIGN_TOOL_VERSION=$BUILD_TOOL_VERSION" >> $GITHUB_ENV
|
||||||
|
echo Last build tool version is: $BUILD_TOOL_VERSION
|
||||||
|
|
||||||
- uses: r0adkll/sign-android-release@v1
|
- uses: r0adkll/sign-android-release@v1
|
||||||
name: Sign app APK
|
name: Sign app APK
|
||||||
if: env.ANDROID_SIGNING_KEY != null
|
if: env.ANDROID_SIGNING_KEY != null
|
||||||
@@ -1256,8 +1349,8 @@ jobs:
|
|||||||
keyStorePassword: ${{ secrets.ANDROID_KEY_STORE_PASSWORD }}
|
keyStorePassword: ${{ secrets.ANDROID_KEY_STORE_PASSWORD }}
|
||||||
keyPassword: ${{ secrets.ANDROID_KEY_PASSWORD }}
|
keyPassword: ${{ secrets.ANDROID_KEY_PASSWORD }}
|
||||||
env:
|
env:
|
||||||
# override default build-tools version (29.0.3) -- optional
|
# env.ANDROID_SIGN_TOOL_VERSION is set by Step "Setup sign tool version variable"
|
||||||
BUILD_TOOLS_VERSION: "30.0.2"
|
BUILD_TOOLS_VERSION: ${{ env.ANDROID_SIGN_TOOL_VERSION }}
|
||||||
|
|
||||||
- name: Upload Artifacts
|
- name: Upload Artifacts
|
||||||
if: env.ANDROID_SIGNING_KEY != null && env.UPLOAD_ARTIFACT == 'true'
|
if: env.ANDROID_SIGNING_KEY != null && env.UPLOAD_ARTIFACT == 'true'
|
||||||
@@ -1285,7 +1378,7 @@ jobs:
|
|||||||
signed-apk/rustdesk-${{ env.VERSION }}-universal${{ env.suffix }}.apk
|
signed-apk/rustdesk-${{ env.VERSION }}-universal${{ env.suffix }}.apk
|
||||||
|
|
||||||
build-rustdesk-linux:
|
build-rustdesk-linux:
|
||||||
needs: [generate-bridge-linux]
|
needs: [generate-bridge]
|
||||||
name: build rustdesk linux ${{ matrix.job.target }}
|
name: build rustdesk linux ${{ matrix.job.target }}
|
||||||
runs-on: ${{ matrix.job.on }}
|
runs-on: ${{ matrix.job.on }}
|
||||||
strategy:
|
strategy:
|
||||||
@@ -1328,6 +1421,8 @@ jobs:
|
|||||||
|
|
||||||
- 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' }}
|
||||||
@@ -1376,6 +1471,7 @@ jobs:
|
|||||||
- name: Install vcpkg dependencies
|
- name: Install vcpkg dependencies
|
||||||
if: matrix.job.arch == 'x86_64' || env.UPLOAD_ARTIFACT == 'true'
|
if: matrix.job.arch == 'x86_64' || env.UPLOAD_ARTIFACT == 'true'
|
||||||
run: |
|
run: |
|
||||||
|
sudo apt install -y libva-dev && apt show libva-dev
|
||||||
if ! $VCPKG_ROOT/vcpkg \
|
if ! $VCPKG_ROOT/vcpkg \
|
||||||
install \
|
install \
|
||||||
--triplet ${{ matrix.job.vcpkg-triplet }} \
|
--triplet ${{ matrix.job.vcpkg-triplet }} \
|
||||||
@@ -1389,6 +1485,7 @@ jobs:
|
|||||||
done
|
done
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
head -n 100 "${VCPKG_ROOT}/buildtrees/ffmpeg/build-${{ matrix.job.vcpkg-triplet }}-rel-out.log" || true
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|
||||||
- name: Restore bridge files
|
- name: Restore bridge files
|
||||||
@@ -1433,7 +1530,6 @@ jobs:
|
|||||||
libpam0g-dev \
|
libpam0g-dev \
|
||||||
libpulse-dev \
|
libpulse-dev \
|
||||||
libva-dev \
|
libva-dev \
|
||||||
libvdpau-dev \
|
|
||||||
libxcb-randr0-dev \
|
libxcb-randr0-dev \
|
||||||
libxcb-shape0-dev \
|
libxcb-shape0-dev \
|
||||||
libxcb-xfixes0-dev \
|
libxcb-xfixes0-dev \
|
||||||
@@ -1520,6 +1616,19 @@ jobs:
|
|||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
|
if [[ "3.24.5" == ${{ env.FLUTTER_VERSION }} ]]; then
|
||||||
|
case ${{ matrix.job.arch }} in
|
||||||
|
aarch64)
|
||||||
|
pushd /opt/flutter-elinux/flutter
|
||||||
|
;;
|
||||||
|
x86_64)
|
||||||
|
pushd /opt/flutter
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
git apply ${{ github.workspace }}/.github/patches/flutter_3.24.4_dropdown_menu_enableFilter.diff
|
||||||
|
popd
|
||||||
|
fi
|
||||||
|
|
||||||
# build flutter
|
# build flutter
|
||||||
pushd /workspace
|
pushd /workspace
|
||||||
export CARGO_INCREMENTAL=0
|
export CARGO_INCREMENTAL=0
|
||||||
@@ -1642,6 +1751,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: |
|
||||||
@@ -1693,7 +1804,6 @@ jobs:
|
|||||||
libpam0g-dev \
|
libpam0g-dev \
|
||||||
libpulse-dev \
|
libpulse-dev \
|
||||||
libva-dev \
|
libva-dev \
|
||||||
libvdpau-dev \
|
|
||||||
libxcb-randr0-dev \
|
libxcb-randr0-dev \
|
||||||
libxcb-shape0-dev \
|
libxcb-shape0-dev \
|
||||||
libxcb-xfixes0-dev \
|
libxcb-xfixes0-dev \
|
||||||
@@ -1771,6 +1881,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:"
|
||||||
@@ -1781,6 +1893,7 @@ jobs:
|
|||||||
done
|
done
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
head -n 100 "${VCPKG_ROOT}/buildtrees/ffmpeg/build-${{ matrix.job.vcpkg-triplet }}-rel-out.log" || true
|
||||||
# build rustdesk
|
# build rustdesk
|
||||||
python3 ./res/inline-sciter.py
|
python3 ./res/inline-sciter.py
|
||||||
export CARGO_INCREMENTAL=0
|
export CARGO_INCREMENTAL=0
|
||||||
@@ -1830,6 +1943,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
|
||||||
@@ -1902,6 +2017,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
|
||||||
@@ -1927,36 +2044,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
|
||||||
@@ -1978,6 +2076,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: |
|
||||||
@@ -1989,7 +2089,12 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
channel: "stable"
|
channel: "stable"
|
||||||
flutter-version: ${{ env.FLUTTER_VERSION }}
|
flutter-version: ${{ env.FLUTTER_VERSION }}
|
||||||
cache: true
|
|
||||||
|
- name: Patch flutter
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
cd $(dirname $(dirname $(which flutter)))
|
||||||
|
[[ "3.24.5" == ${{env.FLUTTER_VERSION}} ]] && git apply ${{ github.workspace }}/.github/patches/flutter_3.24.4_dropdown_menu_enableFilter.diff
|
||||||
|
|
||||||
# https://rustdesk.com/docs/en/dev/build/web/
|
# https://rustdesk.com/docs/en/dev/build/web/
|
||||||
- name: Build web
|
- name: Build web
|
||||||
|
|||||||
16
.github/workflows/playground.yml
vendored
16
.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.2"
|
VERSION: "1.3.7"
|
||||||
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
|
||||||
@@ -149,7 +150,7 @@ jobs:
|
|||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
sed -i '' 's/3.1.0/2.17.0/g' flutter/pubspec.yaml;
|
sed -i '' 's/3.1.0/2.17.0/g' flutter/pubspec.yaml;
|
||||||
cargo install flutter_rust_bridge_codegen --version ${{ matrix.job.bridge }} --features "uuid"
|
cargo install flutter_rust_bridge_codegen --version ${{ matrix.job.bridge }} --features "uuid" --locked
|
||||||
# below works for mac to make buildable on 3.13.9
|
# below works for mac to make buildable on 3.13.9
|
||||||
# pushd flutter/lib; find . -name "*.dart" | xargs -I{} sed -i '' 's/textScaler: TextScaler.linear(\(.*\)),/textScaleFactor: \1,/g' {}; popd;
|
# pushd flutter/lib; find . -name "*.dart" | xargs -I{} sed -i '' 's/textScaler: TextScaler.linear(\(.*\)),/textScaleFactor: \1,/g' {}; popd;
|
||||||
pushd flutter && flutter pub get && popd
|
pushd flutter && flutter pub get && popd
|
||||||
@@ -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: |
|
||||||
@@ -302,7 +304,7 @@ jobs:
|
|||||||
- name: Install flutter rust bridge deps
|
- name: Install flutter rust bridge deps
|
||||||
run: |
|
run: |
|
||||||
git config --global core.longpaths true
|
git config --global core.longpaths true
|
||||||
cargo install flutter_rust_bridge_codegen --version ${{ env.FLUTTER_RUST_BRIDGE_VERSION }} --features "uuid"
|
cargo install flutter_rust_bridge_codegen --version ${{ env.FLUTTER_RUST_BRIDGE_VERSION }} --features "uuid" --locked
|
||||||
sed -i 's/uni_links_desktop/#uni_links_desktop/g' flutter/pubspec.yaml
|
sed -i 's/uni_links_desktop/#uni_links_desktop/g' flutter/pubspec.yaml
|
||||||
pushd flutter/lib; find . | grep dart | xargs sed -i 's/textScaler: TextScaler.linear(\(.*\)),/textScaleFactor: \1,/g'; popd;
|
pushd flutter/lib; find . | grep dart | xargs sed -i 's/textScaler: TextScaler.linear(\(.*\)),/textScaleFactor: \1,/g'; popd;
|
||||||
pushd flutter ; flutter pub get ; popd
|
pushd flutter ; flutter pub get ; popd
|
||||||
@@ -347,7 +349,7 @@ jobs:
|
|||||||
ANDROID_NDK_ROOT: ${{ steps.setup-ndk.outputs.ndk-path }}
|
ANDROID_NDK_ROOT: ${{ steps.setup-ndk.outputs.ndk-path }}
|
||||||
run: |
|
run: |
|
||||||
rustup target add ${{ matrix.job.target }}
|
rustup target add ${{ matrix.job.target }}
|
||||||
cargo install cargo-ndk --version ${{ env.CARGO_NDK_VERSION }}
|
cargo install cargo-ndk --version ${{ env.CARGO_NDK_VERSION }} --locked
|
||||||
case ${{ matrix.job.target }} in
|
case ${{ matrix.job.target }} in
|
||||||
aarch64-linux-android)
|
aarch64-linux-android)
|
||||||
./flutter/ndk_arm64.sh
|
./flutter/ndk_arm64.sh
|
||||||
|
|||||||
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
|
||||||
94
Cargo.lock
generated
94
Cargo.lock
generated
@@ -723,9 +723,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 +735,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",
|
||||||
]
|
]
|
||||||
@@ -893,6 +893,20 @@ dependencies = [
|
|||||||
"regex",
|
"regex",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cidre"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "git+https://github.com/yury/cidre.git?rev=f05c428#f05c4288f9870c9fab53272ddafd6ec01c7b2dbf"
|
||||||
|
dependencies = [
|
||||||
|
"cidre-macros",
|
||||||
|
"parking_lot",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cidre-macros"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "git+https://github.com/yury/cidre.git?rev=f05c428#f05c4288f9870c9fab53272ddafd6ec01c7b2dbf"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cipher"
|
name = "cipher"
|
||||||
version = "0.4.4"
|
version = "0.4.4"
|
||||||
@@ -1276,10 +1290,10 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "cpal"
|
name = "cpal"
|
||||||
version = "0.15.3"
|
version = "0.15.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "git+https://github.com/rustdesk-org/cpal?branch=osx-screencapturekit#6b374bcaed076750ca8fce6da518ab39b882e14a"
|
||||||
checksum = "873dab07c8f743075e57f524c583985fbaf745602acbe916a01539364369a779"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"alsa",
|
"alsa",
|
||||||
|
"cidre",
|
||||||
"core-foundation-sys 0.8.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
"core-foundation-sys 0.8.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"coreaudio-rs",
|
"coreaudio-rs",
|
||||||
"dasp_sample",
|
"dasp_sample",
|
||||||
@@ -1567,6 +1581,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#a831d47bcacb4615b394968287697924a8f62be1"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"regex",
|
||||||
|
"winapi 0.3.9",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "deranged"
|
name = "deranged"
|
||||||
version = "0.3.11"
|
version = "0.3.11"
|
||||||
@@ -1683,7 +1707,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.8.4",
|
"libloading 0.7.4",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2237,9 +2261,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",
|
||||||
@@ -2247,9 +2271,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"
|
||||||
@@ -2264,9 +2288,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"
|
||||||
@@ -2298,9 +2322,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",
|
||||||
@@ -2309,21 +2333,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",
|
||||||
@@ -2887,6 +2911,7 @@ dependencies = [
|
|||||||
"bytes",
|
"bytes",
|
||||||
"chrono",
|
"chrono",
|
||||||
"confy",
|
"confy",
|
||||||
|
"default_net",
|
||||||
"directories-next",
|
"directories-next",
|
||||||
"dirs-next",
|
"dirs-next",
|
||||||
"dlopen",
|
"dlopen",
|
||||||
@@ -2911,6 +2936,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",
|
||||||
@@ -3050,8 +3076,8 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hwcodec"
|
name = "hwcodec"
|
||||||
version = "0.7.0"
|
version = "0.7.1"
|
||||||
source = "git+https://github.com/rustdesk-org/hwcodec#8bbd05bb300ad07cc345356ad85570f9ea99fbfa"
|
source = "git+https://github.com/rustdesk-org/hwcodec#c4d6b1c5c4ddc7548868306004cf5d4eb614a36f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bindgen 0.59.2",
|
"bindgen 0.59.2",
|
||||||
"cc",
|
"cc",
|
||||||
@@ -4149,7 +4175,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 2.0.2",
|
"proc-macro-crate 1.3.1",
|
||||||
"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",
|
||||||
@@ -4368,9 +4394,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",
|
||||||
@@ -4400,9 +4426,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",
|
||||||
@@ -5205,7 +5231,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#961d25cc00c6b3ef80f444e6a7bed9872e2c35ea"
|
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",
|
||||||
@@ -5452,7 +5478,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",
|
||||||
]
|
]
|
||||||
@@ -5480,7 +5506,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustdesk"
|
name = "rustdesk"
|
||||||
version = "1.3.2"
|
version = "1.3.7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"android-wakelock",
|
"android-wakelock",
|
||||||
"android_logger",
|
"android_logger",
|
||||||
@@ -5580,7 +5606,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustdesk-portable-packer"
|
name = "rustdesk-portable-packer"
|
||||||
version = "1.3.2"
|
version = "1.3.7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"brotli",
|
"brotli",
|
||||||
"dirs 5.0.1",
|
"dirs 5.0.1",
|
||||||
@@ -5799,7 +5825,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",
|
||||||
@@ -6445,7 +6471,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "tfc"
|
name = "tfc"
|
||||||
version = "0.7.0"
|
version = "0.7.0"
|
||||||
source = "git+https://github.com/rustdesk-org/The-Fat-Controller?branch=history/rebase_upstream_20240722#de9c8ba480f166a9fc90aaa47bb0e84b443ea9c6"
|
source = "git+https://github.com/rustdesk-org/The-Fat-Controller?branch=history/rebase_upstream_20240722#78bb80a8e596e4c14ae57c8448f5fca75f91f2b0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"core-graphics 0.23.2",
|
"core-graphics 0.23.2",
|
||||||
|
|||||||
16
Cargo.toml
16
Cargo.toml
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "rustdesk"
|
name = "rustdesk"
|
||||||
version = "1.3.2"
|
version = "1.3.7"
|
||||||
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 = []
|
||||||
@@ -36,6 +40,7 @@ unix-file-copy-paste = [
|
|||||||
"dep:once_cell",
|
"dep:once_cell",
|
||||||
"clipboard/unix-file-copy-paste",
|
"clipboard/unix-file-copy-paste",
|
||||||
]
|
]
|
||||||
|
screencapturekit = ["cpal/screencapturekit"]
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
@@ -78,13 +83,14 @@ 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(any(target_os = "android", target_os = "linux")))'.dependencies]
|
[target.'cfg(not(target_os = "linux"))'.dependencies]
|
||||||
cpal = "0.15"
|
# https://github.com/rustdesk/rustdesk/discussions/10197, not use cpal on linux
|
||||||
|
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" }
|
||||||
@@ -150,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"
|
||||||
|
|||||||
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
|
||||||
|
|||||||
@@ -25,9 +25,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
|
||||||
|
|
||||||
@@ -172,6 +175,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.2
|
version: 1.3.7
|
||||||
exec: usr/lib/rustdesk/rustdesk
|
exec: usr/share/rustdesk/rustdesk
|
||||||
exec_args: $@
|
exec_args: $@
|
||||||
apt:
|
apt:
|
||||||
arch:
|
arch:
|
||||||
@@ -47,9 +47,9 @@ AppDir:
|
|||||||
- libasound2
|
- libasound2
|
||||||
- libsystemd0
|
- libsystemd0
|
||||||
- curl
|
- curl
|
||||||
|
- libva2
|
||||||
- libva-drm2
|
- libva-drm2
|
||||||
- libva-x11-2
|
- libva-x11-2
|
||||||
- libvdpau1
|
|
||||||
- libgstreamer-plugins-base1.0-0
|
- libgstreamer-plugins-base1.0-0
|
||||||
- gstreamer1.0-pipewire
|
- gstreamer1.0-pipewire
|
||||||
- libwayland-client0
|
- libwayland-client0
|
||||||
@@ -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.2
|
version: 1.3.7
|
||||||
exec: usr/lib/rustdesk/rustdesk
|
exec: usr/share/rustdesk/rustdesk
|
||||||
exec_args: $@
|
exec_args: $@
|
||||||
apt:
|
apt:
|
||||||
arch:
|
arch:
|
||||||
@@ -50,9 +50,9 @@ AppDir:
|
|||||||
- libasound2
|
- libasound2
|
||||||
- libsystemd0
|
- libsystemd0
|
||||||
- curl
|
- curl
|
||||||
|
- libva2
|
||||||
- libva-drm2
|
- libva-drm2
|
||||||
- libva-x11-2
|
- libva-x11-2
|
||||||
- libvdpau1
|
|
||||||
- libgstreamer-plugins-base1.0-0
|
- libgstreamer-plugins-base1.0-0
|
||||||
- gstreamer1.0-pipewire
|
- gstreamer1.0-pipewire
|
||||||
- libwayland-client0
|
- libwayland-client0
|
||||||
@@ -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:
|
||||||
|
|||||||
50
build.py
50
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(
|
||||||
@@ -111,7 +112,7 @@ def make_parser():
|
|||||||
'--hwcodec',
|
'--hwcodec',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help='Enable feature hwcodec' + (
|
help='Enable feature hwcodec' + (
|
||||||
'' if windows or osx else ', need libva-dev, libvdpau-dev.')
|
'' if windows or osx else ', need libva-dev.')
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--vram',
|
'--vram',
|
||||||
@@ -143,6 +144,12 @@ def make_parser():
|
|||||||
"--package",
|
"--package",
|
||||||
type=str
|
type=str
|
||||||
)
|
)
|
||||||
|
if osx:
|
||||||
|
parser.add_argument(
|
||||||
|
'--screencapturekit',
|
||||||
|
action='store_true',
|
||||||
|
help='Enable feature screencapturekit'
|
||||||
|
)
|
||||||
return parser
|
return parser
|
||||||
|
|
||||||
|
|
||||||
@@ -274,6 +281,9 @@ def get_features(args):
|
|||||||
features.append('flutter')
|
features.append('flutter')
|
||||||
if args.unix_file_copy_paste:
|
if args.unix_file_copy_paste:
|
||||||
features.append('unix-file-copy-paste')
|
features.append('unix-file-copy-paste')
|
||||||
|
if osx:
|
||||||
|
if args.screencapturekit:
|
||||||
|
features.append('screencapturekit')
|
||||||
print("features:", features)
|
print("features:", features)
|
||||||
return features
|
return features
|
||||||
|
|
||||||
@@ -289,7 +299,7 @@ Version: %s
|
|||||||
Architecture: %s
|
Architecture: %s
|
||||||
Maintainer: rustdesk <info@rustdesk.com>
|
Maintainer: rustdesk <info@rustdesk.com>
|
||||||
Homepage: https://rustdesk.com
|
Homepage: https://rustdesk.com
|
||||||
Depends: libgtk-3-0, libxcb-randr0, libxdo3, libxfixes3, libxcb-shape0, libxcb-xfixes0, libasound2, libsystemd0, curl, libva-drm2, libva-x11-2, libvdpau1, libgstreamer-plugins-base1.0-0, libpam0g, gstreamer1.0-pipewire%s
|
Depends: libgtk-3-0, libxcb-randr0, libxdo3, libxfixes3, libxcb-shape0, libxcb-xfixes0, libasound2, libsystemd0, curl, libva2, libva-drm2, libva-x11-2, libgstreamer-plugins-base1.0-0, libpam0g, gstreamer1.0-pipewire%s
|
||||||
Recommends: libayatana-appindicator3-1
|
Recommends: libayatana-appindicator3-1
|
||||||
Description: A remote control software.
|
Description: A remote control software.
|
||||||
|
|
||||||
@@ -312,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/')
|
||||||
@@ -322,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(
|
||||||
@@ -345,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/')
|
||||||
@@ -357,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/')
|
||||||
@@ -365,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(
|
||||||
@@ -382,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/')
|
||||||
@@ -395,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")
|
||||||
@@ -612,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__":
|
||||||
|
|||||||
9
build.rs
9
build.rs
@@ -1,7 +1,7 @@
|
|||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
fn build_windows() {
|
fn build_windows() {
|
||||||
let file = "src/platform/windows.cc";
|
let file = "src/platform/windows.cc";
|
||||||
let file2 = "src/platform/windows_delete_test_cert.cc";
|
let file2 = "src/platform/windows_delete_test_cert.cc";
|
||||||
cc::Build::new().file(file).file(file2).compile("windows");
|
cc::Build::new().file(file).file(file2).compile("windows");
|
||||||
println!("cargo:rustc-link-lib=WtsApi32");
|
println!("cargo:rustc-link-lib=WtsApi32");
|
||||||
println!("cargo:rerun-if-changed={}", file);
|
println!("cargo:rerun-if-changed={}", file);
|
||||||
@@ -61,7 +61,11 @@ fn install_android_deps() {
|
|||||||
let target = format!("{}-android", target_arch);
|
let target = format!("{}-android", target_arch);
|
||||||
let vcpkg_root = std::env::var("VCPKG_ROOT").unwrap();
|
let vcpkg_root = std::env::var("VCPKG_ROOT").unwrap();
|
||||||
let mut path: std::path::PathBuf = vcpkg_root.into();
|
let mut path: std::path::PathBuf = vcpkg_root.into();
|
||||||
path.push("installed");
|
if let Ok(vcpkg_root) = std::env::var("VCPKG_INSTALLED_ROOT") {
|
||||||
|
path = vcpkg_root.into();
|
||||||
|
} else {
|
||||||
|
path.push("installed");
|
||||||
|
}
|
||||||
path.push(target);
|
path.push(target);
|
||||||
println!(
|
println!(
|
||||||
"{}",
|
"{}",
|
||||||
@@ -72,7 +76,6 @@ fn install_android_deps() {
|
|||||||
);
|
);
|
||||||
println!("cargo:rustc-link-lib=ndk_compat");
|
println!("cargo:rustc-link-lib=ndk_compat");
|
||||||
println!("cargo:rustc-link-lib=oboe");
|
println!("cargo:rustc-link-lib=oboe");
|
||||||
println!("cargo:rustc-link-lib=oboe_wrapper");
|
|
||||||
println!("cargo:rustc-link-lib=c++");
|
println!("cargo:rustc-link-lib=c++");
|
||||||
println!("cargo:rustc-link-lib=OpenSLES");
|
println!("cargo:rustc-link-lib=OpenSLES");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,12 @@ 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
|
## 스냅샷
|
||||||
|
|
||||||

|

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

|

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

|

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

|

|
||||||
|
|
||||||
## [Публічні сервери](#публічні-сервери)
|
|
||||||
|
|
||||||
RustDesk підтримується безкоштовним європейським сервером, любʼязно наданим [Codext GmbH](https://codext.link/rustdesk?utm_source=github)
|
|
||||||
|
|||||||
@@ -135,8 +135,8 @@ docker build -t "rustdesk-builder" . # 构建容器
|
|||||||
```
|
```
|
||||||
在Dockerfile的RUN apt update之前插入两行:
|
在Dockerfile的RUN apt update之前插入两行:
|
||||||
|
|
||||||
RUN sed -i "s/deb.debian.org/mirrors.163.com/g" /etc/apt/sources.list
|
RUN sed -i "s|deb.debian.org|mirrors.aliyun.com|g" /etc/apt/sources.list && \
|
||||||
RUN sed -i "s/security.debian.org/mirrors.163.com/g" /etc/apt/sources.list
|
sed -i "s|security.debian.org|mirrors.aliyun.com|g" /etc/apt/sources.list
|
||||||
```
|
```
|
||||||
|
|
||||||
2. 修改容器系统中的 cargo 源,在`RUN ./rustup.sh -y`后插入下面代码:
|
2. 修改容器系统中的 cargo 源,在`RUN ./rustup.sh -y`后插入下面代码:
|
||||||
|
|||||||
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"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,9 @@
|
|||||||
import com.google.protobuf.gradle.*
|
import com.google.protobuf.gradle.*
|
||||||
plugins {
|
plugins {
|
||||||
id "com.google.protobuf" version "0.9.4"
|
id "com.google.protobuf" version "0.9.4"
|
||||||
|
id "com.android.application"
|
||||||
|
id "kotlin-android"
|
||||||
|
id "dev.flutter.flutter-gradle-plugin"
|
||||||
}
|
}
|
||||||
|
|
||||||
def keystoreProperties = new Properties()
|
def keystoreProperties = new Properties()
|
||||||
@@ -17,11 +20,6 @@ if (localPropertiesFile.exists()) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def flutterRoot = localProperties.getProperty('flutter.sdk')
|
|
||||||
if (flutterRoot == null) {
|
|
||||||
throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
|
|
||||||
}
|
|
||||||
|
|
||||||
def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
|
def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
|
||||||
if (flutterVersionCode == null) {
|
if (flutterVersionCode == null) {
|
||||||
flutterVersionCode = '1'
|
flutterVersionCode = '1'
|
||||||
@@ -32,10 +30,6 @@ if (flutterVersionName == null) {
|
|||||||
flutterVersionName = '1.0'
|
flutterVersionName = '1.0'
|
||||||
}
|
}
|
||||||
|
|
||||||
apply plugin: 'com.android.application'
|
|
||||||
apply plugin: 'kotlin-android'
|
|
||||||
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
|
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation 'com.google.protobuf:protobuf-javalite:3.20.1'
|
implementation 'com.google.protobuf:protobuf-javalite:3.20.1'
|
||||||
}
|
}
|
||||||
@@ -57,7 +51,7 @@ protobuf {
|
|||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion 33
|
compileSdkVersion 34
|
||||||
sourceSets {
|
sourceSets {
|
||||||
main.java.srcDirs += 'src/main/kotlin'
|
main.java.srcDirs += 'src/main/kotlin'
|
||||||
|
|
||||||
@@ -105,7 +99,6 @@ flutter {
|
|||||||
dependencies {
|
dependencies {
|
||||||
implementation "androidx.media:media:1.6.0"
|
implementation "androidx.media:media:1.6.0"
|
||||||
implementation 'com.github.getActivity:XXPermissions:18.5'
|
implementation 'com.github.getActivity:XXPermissions:18.5'
|
||||||
implementation("org.jetbrains.kotlin:kotlin-stdlib") { version { strictly("$kotlin_version") } }
|
implementation("org.jetbrains.kotlin:kotlin-stdlib") { version { strictly("1.9.10") } }
|
||||||
implementation 'com.caverock:androidsvg-aar:1.4'
|
implementation 'com.caverock:androidsvg-aar:1.4'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,13 @@
|
|||||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||||
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
|
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
|
||||||
|
|
||||||
|
<queries>
|
||||||
|
<intent>
|
||||||
|
<!-- https://developer.android.com/training/package-visibility/use-cases#open-urls-custom-tabs -->
|
||||||
|
<action android:name="android.support.customtabs.action.CustomTabsService" />
|
||||||
|
</intent>
|
||||||
|
</queries>
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:label="RustDesk"
|
android:label="RustDesk"
|
||||||
|
|||||||
@@ -304,7 +304,13 @@ class FloatingWindowService : Service(), View.OnTouchListener {
|
|||||||
val popupMenu = PopupMenu(this, floatingView)
|
val popupMenu = PopupMenu(this, floatingView)
|
||||||
val idShowRustDesk = 0
|
val idShowRustDesk = 0
|
||||||
popupMenu.menu.add(0, idShowRustDesk, 0, translate("Show RustDesk"))
|
popupMenu.menu.add(0, idShowRustDesk, 0, translate("Show RustDesk"))
|
||||||
val idStopService = 1
|
// For host side, clipboard sync
|
||||||
|
val idSyncClipboard = 1
|
||||||
|
val isServiceSyncEnabled = (MainActivity.rdClipboardManager?.isCaptureStarted ?: false) && FFI.isServiceClipboardEnabled()
|
||||||
|
if (isServiceSyncEnabled) {
|
||||||
|
popupMenu.menu.add(0, idSyncClipboard, 0, translate("Update client clipboard"))
|
||||||
|
}
|
||||||
|
val idStopService = 2
|
||||||
popupMenu.menu.add(0, idStopService, 0, translate("Stop service"))
|
popupMenu.menu.add(0, idStopService, 0, translate("Stop service"))
|
||||||
popupMenu.setOnMenuItemClickListener { menuItem ->
|
popupMenu.setOnMenuItemClickListener { menuItem ->
|
||||||
when (menuItem.itemId) {
|
when (menuItem.itemId) {
|
||||||
@@ -312,6 +318,10 @@ class FloatingWindowService : Service(), View.OnTouchListener {
|
|||||||
openMainActivity()
|
openMainActivity()
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
idSyncClipboard -> {
|
||||||
|
syncClipboard()
|
||||||
|
true
|
||||||
|
}
|
||||||
idStopService -> {
|
idStopService -> {
|
||||||
stopMainService()
|
stopMainService()
|
||||||
true
|
true
|
||||||
@@ -340,6 +350,10 @@ class FloatingWindowService : Service(), View.OnTouchListener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun syncClipboard() {
|
||||||
|
MainActivity.rdClipboardManager?.syncClipboard(false)
|
||||||
|
}
|
||||||
|
|
||||||
private fun stopMainService() {
|
private fun stopMainService() {
|
||||||
MainActivity.flutterMethodChannel?.invokeMethod("stop_service", null)
|
MainActivity.flutterMethodChannel?.invokeMethod("stop_service", null)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -280,20 +280,20 @@ class InputService : AccessibilityService() {
|
|||||||
|
|
||||||
var textToCommit: String? = null
|
var textToCommit: String? = null
|
||||||
|
|
||||||
if (keyboardMode == KeyboardMode.Legacy) {
|
// [down] indicates the key's state(down or up).
|
||||||
if (keyEvent.hasChr() && keyEvent.getDown()) {
|
// [press] indicates a click event(down and up).
|
||||||
|
// https://github.com/rustdesk/rustdesk/blob/3a7594755341f023f56fa4b6a43b60d6b47df88d/flutter/lib/models/input_model.dart#L688
|
||||||
|
if (keyEvent.hasSeq()) {
|
||||||
|
textToCommit = keyEvent.getSeq()
|
||||||
|
} else if (keyboardMode == KeyboardMode.Legacy) {
|
||||||
|
if (keyEvent.hasChr() && (keyEvent.getDown() || keyEvent.getPress())) {
|
||||||
val chr = keyEvent.getChr()
|
val chr = keyEvent.getChr()
|
||||||
if (chr != null) {
|
if (chr != null) {
|
||||||
textToCommit = String(Character.toChars(chr))
|
textToCommit = String(Character.toChars(chr))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (keyboardMode == KeyboardMode.Translate) {
|
} else if (keyboardMode == KeyboardMode.Translate) {
|
||||||
if (keyEvent.hasSeq() && keyEvent.getDown()) {
|
} else {
|
||||||
val seq = keyEvent.getSeq()
|
|
||||||
if (seq != null) {
|
|
||||||
textToCommit = seq
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Log.d(logTag, "onKeyEvent $keyEvent textToCommit:$textToCommit")
|
Log.d(logTag, "onKeyEvent $keyEvent textToCommit:$textToCommit")
|
||||||
@@ -320,6 +320,10 @@ class InputService : AccessibilityService() {
|
|||||||
} else {
|
} else {
|
||||||
ke?.let { event ->
|
ke?.let { event ->
|
||||||
inputConnection.sendKeyEvent(event)
|
inputConnection.sendKeyEvent(event)
|
||||||
|
if (keyEvent.getPress()) {
|
||||||
|
val actionUpEvent = KeyEventAndroid(KeyEventAndroid.ACTION_UP, event.keyCode)
|
||||||
|
inputConnection.sendKeyEvent(actionUpEvent)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -333,6 +337,10 @@ class InputService : AccessibilityService() {
|
|||||||
for (item in possibleNodes) {
|
for (item in possibleNodes) {
|
||||||
val success = trySendKeyEvent(event, item, textToCommit)
|
val success = trySendKeyEvent(event, item, textToCommit)
|
||||||
if (success) {
|
if (success) {
|
||||||
|
if (keyEvent.getPress()) {
|
||||||
|
val actionUpEvent = KeyEventAndroid(KeyEventAndroid.ACTION_UP, event.keyCode)
|
||||||
|
trySendKeyEvent(actionUpEvent, item, textToCommit)
|
||||||
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,14 +31,12 @@ object KeyEventConverter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var action = 0
|
var action = 0
|
||||||
if (keyEventProto.getDown()) {
|
if (keyEventProto.getDown() || keyEventProto.getPress()) {
|
||||||
action = KeyEvent.ACTION_DOWN
|
action = KeyEvent.ACTION_DOWN
|
||||||
} else {
|
} else {
|
||||||
action = KeyEvent.ACTION_UP
|
action = KeyEvent.ACTION_UP
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: The last parameter is the repeat count, not modifiers ?
|
|
||||||
// https://developer.android.com/reference/android/view/KeyEvent#KeyEvent(long,%20long,%20int,%20int,%20int)
|
|
||||||
return KeyEvent(0, 0, action, chrValue, 0, modifiers)
|
return KeyEvent(0, 0, action, chrValue, 0, modifiers)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ import android.content.ComponentName
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.ServiceConnection
|
import android.content.ServiceConnection
|
||||||
|
import android.content.ClipboardManager
|
||||||
|
import android.os.Bundle
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.IBinder
|
import android.os.IBinder
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
@@ -36,6 +38,9 @@ import kotlin.concurrent.thread
|
|||||||
class MainActivity : FlutterActivity() {
|
class MainActivity : FlutterActivity() {
|
||||||
companion object {
|
companion object {
|
||||||
var flutterMethodChannel: MethodChannel? = null
|
var flutterMethodChannel: MethodChannel? = null
|
||||||
|
private var _rdClipboardManager: RdClipboardManager? = null
|
||||||
|
val rdClipboardManager: RdClipboardManager?
|
||||||
|
get() = _rdClipboardManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
private val channelTag = "mChannel"
|
private val channelTag = "mChannel"
|
||||||
@@ -85,6 +90,14 @@ class MainActivity : FlutterActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
if (_rdClipboardManager == null) {
|
||||||
|
_rdClipboardManager = RdClipboardManager(getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager)
|
||||||
|
FFI.setClipboardManager(_rdClipboardManager!!)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
Log.e(logTag, "onDestroy")
|
Log.e(logTag, "onDestroy")
|
||||||
mainService?.let {
|
mainService?.let {
|
||||||
@@ -207,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))
|
||||||
|
|||||||
@@ -302,6 +302,8 @@ class MainService : Service() {
|
|||||||
stopCapture()
|
stopCapture()
|
||||||
FFI.refreshScreen()
|
FFI.refreshScreen()
|
||||||
startCapture()
|
startCapture()
|
||||||
|
} else {
|
||||||
|
FFI.refreshScreen()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -431,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
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -439,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)`.
|
||||||
|
|||||||
@@ -0,0 +1,197 @@
|
|||||||
|
package com.carriez.flutter_hbb
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer
|
||||||
|
import java.util.Timer
|
||||||
|
import java.util.TimerTask
|
||||||
|
|
||||||
|
import android.content.ClipData
|
||||||
|
import android.content.ClipDescription
|
||||||
|
import android.content.ClipboardManager
|
||||||
|
import android.util.Log
|
||||||
|
import androidx.annotation.Keep
|
||||||
|
|
||||||
|
import hbb.MessageOuterClass.ClipboardFormat
|
||||||
|
import hbb.MessageOuterClass.Clipboard
|
||||||
|
import hbb.MessageOuterClass.MultiClipboards
|
||||||
|
|
||||||
|
import ffi.FFI
|
||||||
|
|
||||||
|
class RdClipboardManager(private val clipboardManager: ClipboardManager) {
|
||||||
|
private val logTag = "RdClipboardManager"
|
||||||
|
private val supportedMimeTypes = arrayOf(
|
||||||
|
ClipDescription.MIMETYPE_TEXT_PLAIN,
|
||||||
|
ClipDescription.MIMETYPE_TEXT_HTML
|
||||||
|
)
|
||||||
|
|
||||||
|
// 1. Avoid listening to the same clipboard data updated by `rustUpdateClipboard`.
|
||||||
|
// 2. Avoid sending the clipboard data before enabling client clipboard.
|
||||||
|
// 1) Disable clipboard
|
||||||
|
// 2) Copy text "a"
|
||||||
|
// 3) Enable clipboard
|
||||||
|
// 4) Switch to another app
|
||||||
|
// 5) Switch back to the app
|
||||||
|
// 6) "a" should not be sent to the client, because it's copied before enabling clipboard
|
||||||
|
//
|
||||||
|
// It's okay to that `rustEnableClientClipboard(false)` is called after `rustUpdateClipboard`,
|
||||||
|
// though the `lastUpdatedClipData` will be set to null once.
|
||||||
|
private var lastUpdatedClipData: ClipData? = null
|
||||||
|
private var isClientEnabled = true;
|
||||||
|
private var _isCaptureStarted = false;
|
||||||
|
|
||||||
|
val isCaptureStarted: Boolean
|
||||||
|
get() = _isCaptureStarted
|
||||||
|
|
||||||
|
fun checkPrimaryClip(isClient: Boolean) {
|
||||||
|
val clipData = clipboardManager.primaryClip
|
||||||
|
if (clipData != null && clipData.itemCount > 0) {
|
||||||
|
// Only handle the first item in the clipboard for now.
|
||||||
|
val clip = clipData.getItemAt(0)
|
||||||
|
// Ignore the `isClipboardDataEqual()` check if it's a host operation.
|
||||||
|
// Because it's an action manually triggered by the user.
|
||||||
|
if (isClient) {
|
||||||
|
if (lastUpdatedClipData != null && isClipboardDataEqual(clipData, lastUpdatedClipData!!)) {
|
||||||
|
Log.d(logTag, "Clipboard data is the same as last update, ignore")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val mimeTypeCount = clipData.description.getMimeTypeCount()
|
||||||
|
val mimeTypes = mutableListOf<String>()
|
||||||
|
for (i in 0 until mimeTypeCount) {
|
||||||
|
mimeTypes.add(clipData.description.getMimeType(i))
|
||||||
|
}
|
||||||
|
var text: CharSequence? = null;
|
||||||
|
var html: String? = null;
|
||||||
|
if (isSupportedMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN)) {
|
||||||
|
text = clip?.text
|
||||||
|
}
|
||||||
|
if (isSupportedMimeType(ClipDescription.MIMETYPE_TEXT_HTML)) {
|
||||||
|
text = clip?.text
|
||||||
|
html = clip?.htmlText
|
||||||
|
}
|
||||||
|
var count = 0
|
||||||
|
val clips = MultiClipboards.newBuilder()
|
||||||
|
if (text != null) {
|
||||||
|
val content = com.google.protobuf.ByteString.copyFromUtf8(text.toString())
|
||||||
|
clips.addClipboards(Clipboard.newBuilder().setFormat(ClipboardFormat.Text).setContent(content).build())
|
||||||
|
count++
|
||||||
|
}
|
||||||
|
if (html != null) {
|
||||||
|
val content = com.google.protobuf.ByteString.copyFromUtf8(html)
|
||||||
|
clips.addClipboards(Clipboard.newBuilder().setFormat(ClipboardFormat.Html).setContent(content).build())
|
||||||
|
count++
|
||||||
|
}
|
||||||
|
if (count > 0) {
|
||||||
|
val clipsBytes = clips.build().toByteArray()
|
||||||
|
val isClientFlag = if (isClient) 1 else 0
|
||||||
|
val clipsBuf = ByteBuffer.allocateDirect(clipsBytes.size + 1).apply {
|
||||||
|
put(isClientFlag.toByte())
|
||||||
|
put(clipsBytes)
|
||||||
|
}
|
||||||
|
clipsBuf.flip()
|
||||||
|
lastUpdatedClipData = clipData
|
||||||
|
Log.d(logTag, "${if (isClient) "client" else "host"}, send clipboard data to the remote")
|
||||||
|
FFI.onClipboardUpdate(clipsBuf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun isSupportedMimeType(mimeType: String): Boolean {
|
||||||
|
return supportedMimeTypes.contains(mimeType)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun isClipboardDataEqual(left: ClipData, right: ClipData): Boolean {
|
||||||
|
if (left.description.getMimeTypeCount() != right.description.getMimeTypeCount()) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
val mimeTypeCount = left.description.getMimeTypeCount()
|
||||||
|
for (i in 0 until mimeTypeCount) {
|
||||||
|
if (left.description.getMimeType(i) != right.description.getMimeType(i)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (left.itemCount != right.itemCount) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for (i in 0 until left.itemCount) {
|
||||||
|
val mimeType = left.description.getMimeType(i)
|
||||||
|
if (!isSupportedMimeType(mimeType)) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
val leftItem = left.getItemAt(i)
|
||||||
|
val rightItem = right.getItemAt(i)
|
||||||
|
if (mimeType == ClipDescription.MIMETYPE_TEXT_PLAIN || mimeType == ClipDescription.MIMETYPE_TEXT_HTML) {
|
||||||
|
if (leftItem.text != rightItem.text || leftItem.htmlText != rightItem.htmlText) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setCaptureStarted(started: Boolean) {
|
||||||
|
_isCaptureStarted = started
|
||||||
|
}
|
||||||
|
|
||||||
|
@Keep
|
||||||
|
fun rustEnableClientClipboard(enable: Boolean) {
|
||||||
|
Log.d(logTag, "rustEnableClientClipboard: enable: $enable")
|
||||||
|
isClientEnabled = enable
|
||||||
|
lastUpdatedClipData = null
|
||||||
|
}
|
||||||
|
|
||||||
|
fun syncClipboard(isClient: Boolean) {
|
||||||
|
Log.d(logTag, "syncClipboard: isClient: $isClient, isClientEnabled: $isClientEnabled")
|
||||||
|
if (isClient && !isClientEnabled) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
checkPrimaryClip(isClient)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Keep
|
||||||
|
fun rustUpdateClipboard(clips: ByteArray) {
|
||||||
|
val clips = MultiClipboards.parseFrom(clips)
|
||||||
|
var mimeTypes = mutableListOf<String>()
|
||||||
|
var text: String? = null
|
||||||
|
var html: String? = null
|
||||||
|
for (clip in clips.getClipboardsList()) {
|
||||||
|
when (clip.format) {
|
||||||
|
ClipboardFormat.Text -> {
|
||||||
|
mimeTypes.add(ClipDescription.MIMETYPE_TEXT_PLAIN)
|
||||||
|
text = String(clip.content.toByteArray(), Charsets.UTF_8)
|
||||||
|
}
|
||||||
|
ClipboardFormat.Html -> {
|
||||||
|
mimeTypes.add(ClipDescription.MIMETYPE_TEXT_HTML)
|
||||||
|
html = String(clip.content.toByteArray(), Charsets.UTF_8)
|
||||||
|
}
|
||||||
|
ClipboardFormat.ImageRgba -> {
|
||||||
|
}
|
||||||
|
ClipboardFormat.ImagePng -> {
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
Log.e(logTag, "Unsupported clipboard format: ${clip.format}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val clipDescription = ClipDescription("clipboard", mimeTypes.toTypedArray())
|
||||||
|
var item: ClipData.Item? = null
|
||||||
|
if (text == null) {
|
||||||
|
Log.e(logTag, "No text content in clipboard")
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
if (html == null) {
|
||||||
|
item = ClipData.Item(text)
|
||||||
|
} else {
|
||||||
|
item = ClipData.Item(text, html)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (item == null) {
|
||||||
|
Log.e(logTag, "No item in clipboard")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val clipData = ClipData(clipDescription, item)
|
||||||
|
lastUpdatedClipData = clipData
|
||||||
|
clipboardManager.setPrimaryClip(clipData)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,12 +5,15 @@ package ffi
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import java.nio.ByteBuffer
|
import java.nio.ByteBuffer
|
||||||
|
|
||||||
|
import com.carriez.flutter_hbb.RdClipboardManager
|
||||||
|
|
||||||
object FFI {
|
object FFI {
|
||||||
init {
|
init {
|
||||||
System.loadLibrary("rustdesk")
|
System.loadLibrary("rustdesk")
|
||||||
}
|
}
|
||||||
|
|
||||||
external fun init(ctx: Context)
|
external fun init(ctx: Context)
|
||||||
|
external fun setClipboardManager(clipboardManager: RdClipboardManager)
|
||||||
external fun startServer(app_dir: String, custom_client_config: String)
|
external fun startServer(app_dir: String, custom_client_config: String)
|
||||||
external fun startService()
|
external fun startService()
|
||||||
external fun onVideoFrameUpdate(buf: ByteBuffer)
|
external fun onVideoFrameUpdate(buf: ByteBuffer)
|
||||||
@@ -20,4 +23,6 @@ object FFI {
|
|||||||
external fun setFrameRawEnable(name: String, value: Boolean)
|
external fun setFrameRawEnable(name: String, value: Boolean)
|
||||||
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 isServiceClipboardEnabled(): Boolean
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,18 +1,3 @@
|
|||||||
buildscript {
|
|
||||||
ext.kotlin_version = '1.9.10'
|
|
||||||
repositories {
|
|
||||||
google()
|
|
||||||
jcenter()
|
|
||||||
maven { url 'https://jitpack.io' }
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
classpath 'com.android.tools.build:gradle:7.0.0'
|
|
||||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
|
||||||
classpath 'com.google.gms:google-services:4.3.14'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
allprojects {
|
allprojects {
|
||||||
repositories {
|
repositories {
|
||||||
google()
|
google()
|
||||||
@@ -24,6 +9,8 @@ allprojects {
|
|||||||
rootProject.buildDir = '../build'
|
rootProject.buildDir = '../build'
|
||||||
subprojects {
|
subprojects {
|
||||||
project.buildDir = "${rootProject.buildDir}/${project.name}"
|
project.buildDir = "${rootProject.buildDir}/${project.name}"
|
||||||
|
}
|
||||||
|
subprojects {
|
||||||
project.evaluationDependsOn(':app')
|
project.evaluationDependsOn(':app')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
|
|||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-all.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.4-all.zip
|
||||||
|
|||||||
@@ -1,11 +1,25 @@
|
|||||||
include ':app'
|
pluginManagement {
|
||||||
|
def flutterSdkPath = {
|
||||||
|
def properties = new Properties()
|
||||||
|
file("local.properties").withInputStream { properties.load(it) }
|
||||||
|
def flutterSdkPath = properties.getProperty("flutter.sdk")
|
||||||
|
assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
|
||||||
|
return flutterSdkPath
|
||||||
|
}()
|
||||||
|
|
||||||
def localPropertiesFile = new File(rootProject.projectDir, "local.properties")
|
includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
|
||||||
def properties = new Properties()
|
|
||||||
|
|
||||||
assert localPropertiesFile.exists()
|
repositories {
|
||||||
localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) }
|
google()
|
||||||
|
mavenCentral()
|
||||||
|
gradlePluginPortal()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
def flutterSdkPath = properties.getProperty("flutter.sdk")
|
plugins {
|
||||||
assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
|
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
|
||||||
apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle"
|
id "com.android.application" version "7.3.0" apply false
|
||||||
|
id "org.jetbrains.kotlin.android" version "1.9.10" apply false
|
||||||
|
}
|
||||||
|
|
||||||
|
include ":app"
|
||||||
|
|||||||
@@ -68,6 +68,7 @@ function build {
|
|||||||
pushd "$SCRIPTDIR/.."
|
pushd "$SCRIPTDIR/.."
|
||||||
$VCPKG_ROOT/vcpkg install --triplet $VCPKG_TARGET --x-install-root="$VCPKG_ROOT/installed"
|
$VCPKG_ROOT/vcpkg install --triplet $VCPKG_TARGET --x-install-root="$VCPKG_ROOT/installed"
|
||||||
popd
|
popd
|
||||||
|
head -n 100 "${VCPKG_ROOT}/buildtrees/ffmpeg/build-$VCPKG_TARGET-rel-out.log" || true
|
||||||
echo "*** [$ANDROID_ABI][Finished] Build and install vcpkg dependencies"
|
echo "*** [$ANDROID_ABI][Finished] Build and install vcpkg dependencies"
|
||||||
|
|
||||||
if [ -d "$VCPKG_ROOT/installed/arm-neon-android" ]; then
|
if [ -d "$VCPKG_ROOT/installed/arm-neon-android" ]; then
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
set -x
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Script to build F-Droid release of RustDesk
|
# Script to build F-Droid release of RustDesk
|
||||||
#
|
#
|
||||||
@@ -23,6 +21,43 @@ set -x
|
|||||||
# + build: perform actual build of APK file
|
# + build: perform actual build of APK file
|
||||||
#
|
#
|
||||||
|
|
||||||
|
# Start of functions
|
||||||
|
|
||||||
|
# Install Flutter of version `VERSION` from Github repository
|
||||||
|
# into directory `FLUTTER_DIR` and apply patches if needed
|
||||||
|
|
||||||
|
prepare_flutter() {
|
||||||
|
VERSION="${1}"
|
||||||
|
FLUTTER_DIR="${2}"
|
||||||
|
|
||||||
|
if [ ! -f "${FLUTTER_DIR}/bin/flutter" ]; then
|
||||||
|
git clone https://github.com/flutter/flutter "${FLUTTER_DIR}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
pushd "${FLUTTER_DIR}"
|
||||||
|
|
||||||
|
git restore .
|
||||||
|
git checkout "${VERSION}"
|
||||||
|
|
||||||
|
# Patch flutter
|
||||||
|
|
||||||
|
if dpkg --compare-versions "${VERSION}" ge "3.24.4"; then
|
||||||
|
git apply "${ROOTDIR}/.github/patches/flutter_3.24.4_dropdown_menu_enableFilter.diff"
|
||||||
|
fi
|
||||||
|
|
||||||
|
flutter config --no-analytics
|
||||||
|
|
||||||
|
popd # ${FLUTTER_DIR}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Start of script
|
||||||
|
|
||||||
|
set -x
|
||||||
|
|
||||||
|
# Note current working directory as root dir for patches
|
||||||
|
|
||||||
|
ROOTDIR="${PWD}"
|
||||||
|
|
||||||
# Parse command-line arguments
|
# Parse command-line arguments
|
||||||
|
|
||||||
VERNAME="${1}"
|
VERNAME="${1}"
|
||||||
@@ -101,18 +136,27 @@ prebuild)
|
|||||||
.env.CARGO_NDK_VERSION \
|
.env.CARGO_NDK_VERSION \
|
||||||
.github/workflows/flutter-build.yml)"
|
.github/workflows/flutter-build.yml)"
|
||||||
|
|
||||||
|
# Flutter used to compile main Rustdesk library
|
||||||
|
|
||||||
FLUTTER_VERSION="$(yq -r \
|
FLUTTER_VERSION="$(yq -r \
|
||||||
.env.ANDROID_FLUTTER_VERSION \
|
.env.ANDROID_FLUTTER_VERSION \
|
||||||
.github/workflows/flutter-build.yml)"
|
.github/workflows/flutter-build.yml)"
|
||||||
|
|
||||||
if [ -z "${FLUTTER_VERSION}" ]; then
|
if [ -z "${FLUTTER_VERSION}" ]; then
|
||||||
FLUTTER_VERSION="$(yq -r \
|
FLUTTER_VERSION="$(yq -r \
|
||||||
.env.FLUTTER_VERSION \
|
.env.FLUTTER_VERSION \
|
||||||
.github/workflows/flutter-build.yml)"
|
.github/workflows/flutter-build.yml)"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Flutter used to compile Flutter<->Rust bridge files
|
||||||
|
|
||||||
|
FLUTTER_BRIDGE_VERSION="$(yq -r \
|
||||||
|
.env.FLUTTER_VERSION \
|
||||||
|
.github/workflows/bridge.yml)"
|
||||||
|
|
||||||
FLUTTER_RUST_BRIDGE_VERSION="$(yq -r \
|
FLUTTER_RUST_BRIDGE_VERSION="$(yq -r \
|
||||||
.env.FLUTTER_RUST_BRIDGE_VERSION \
|
.env.FLUTTER_RUST_BRIDGE_VERSION \
|
||||||
.github/workflows/flutter-build.yml)"
|
.github/workflows/bridge.yml)"
|
||||||
|
|
||||||
NDK_VERSION="$(yq -r \
|
NDK_VERSION="$(yq -r \
|
||||||
.env.NDK_VERSION \
|
.env.NDK_VERSION \
|
||||||
@@ -127,6 +171,7 @@ prebuild)
|
|||||||
.github/workflows/flutter-build.yml)"
|
.github/workflows/flutter-build.yml)"
|
||||||
|
|
||||||
if [ -z "${CARGO_NDK_VERSION}" ] || [ -z "${FLUTTER_VERSION}" ] ||
|
if [ -z "${CARGO_NDK_VERSION}" ] || [ -z "${FLUTTER_VERSION}" ] ||
|
||||||
|
[ -z "${FLUTTER_BRIDGE_VERSION}" ] ||
|
||||||
[ -z "${FLUTTER_RUST_BRIDGE_VERSION}" ] ||
|
[ -z "${FLUTTER_RUST_BRIDGE_VERSION}" ] ||
|
||||||
[ -z "${NDK_VERSION}" ] || [ -z "${RUST_VERSION}" ] ||
|
[ -z "${NDK_VERSION}" ] || [ -z "${RUST_VERSION}" ] ||
|
||||||
[ -z "${VCPKG_COMMIT_ID}" ]; then
|
[ -z "${VCPKG_COMMIT_ID}" ]; then
|
||||||
@@ -163,24 +208,6 @@ prebuild)
|
|||||||
sdkmanager --install "ndk;${NDK_VERSION}"
|
sdkmanager --install "ndk;${NDK_VERSION}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Install Flutter
|
|
||||||
|
|
||||||
if [ ! -f "${HOME}/flutter/bin/flutter" ]; then
|
|
||||||
pushd "${HOME}"
|
|
||||||
|
|
||||||
git clone https://github.com/flutter/flutter
|
|
||||||
|
|
||||||
pushd flutter
|
|
||||||
|
|
||||||
git reset --hard "${FLUTTER_VERSION}"
|
|
||||||
|
|
||||||
flutter config --no-analytics
|
|
||||||
|
|
||||||
popd # flutter
|
|
||||||
|
|
||||||
popd # ${HOME}
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Install Rust
|
# Install Rust
|
||||||
|
|
||||||
if [ ! -f "${HOME}/rustup/rustup-init.sh" ]; then
|
if [ ! -f "${HOME}/rustup/rustup-init.sh" ]; then
|
||||||
@@ -205,14 +232,18 @@ prebuild)
|
|||||||
|
|
||||||
cargo install \
|
cargo install \
|
||||||
cargo-ndk \
|
cargo-ndk \
|
||||||
--version "${CARGO_NDK_VERSION}"
|
--version "${CARGO_NDK_VERSION}" \
|
||||||
|
--locked
|
||||||
|
|
||||||
# Install rust bridge generator
|
# Install rust bridge generator
|
||||||
|
|
||||||
cargo install cargo-expand
|
cargo install \
|
||||||
|
cargo-expand \
|
||||||
|
--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" \
|
||||||
|
--locked
|
||||||
|
|
||||||
# Populate native vcpkg dependencies
|
# Populate native vcpkg dependencies
|
||||||
|
|
||||||
@@ -275,12 +306,66 @@ prebuild)
|
|||||||
|
|
||||||
git apply res/fdroid/patches/*.patch
|
git apply res/fdroid/patches/*.patch
|
||||||
|
|
||||||
|
# If Flutter version used to generate bridge files differs from Flutter
|
||||||
|
# version used to compile Rustdesk library, generate bridge using the
|
||||||
|
# `FLUTTER_BRIDGE_VERSION` an restore the pubspec later
|
||||||
|
|
||||||
|
if [ "${FLUTTER_VERSION}" != "${FLUTTER_BRIDGE_VERSION}" ]; then
|
||||||
|
# Install Flutter bridge version
|
||||||
|
|
||||||
|
prepare_flutter "${FLUTTER_BRIDGE_VERSION}" "${HOME}/flutter"
|
||||||
|
|
||||||
|
# Save changes
|
||||||
|
|
||||||
|
git add .
|
||||||
|
|
||||||
|
# Edit pubspec to make flutter bridge version work
|
||||||
|
|
||||||
|
sed \
|
||||||
|
-i \
|
||||||
|
-e 's/extended_text: 14.0.0/extended_text: 13.0.0/g' \
|
||||||
|
flutter/pubspec.yaml
|
||||||
|
|
||||||
|
# Download Flutter dependencies
|
||||||
|
|
||||||
|
pushd flutter
|
||||||
|
|
||||||
|
flutter clean
|
||||||
|
flutter packages pub get
|
||||||
|
|
||||||
|
popd # flutter
|
||||||
|
|
||||||
|
# Generate FFI bindings
|
||||||
|
|
||||||
|
flutter_rust_bridge_codegen \
|
||||||
|
--rust-input ./src/flutter_ffi.rs \
|
||||||
|
--dart-output ./flutter/lib/generated_bridge.dart
|
||||||
|
|
||||||
|
# Add bridge files to save-list
|
||||||
|
|
||||||
|
git add -f ./flutter/lib/generated_bridge.* ./src/bridge_generated.*
|
||||||
|
|
||||||
|
# Restore everything
|
||||||
|
|
||||||
|
git checkout '*'
|
||||||
|
git clean -dffx
|
||||||
|
git reset
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Install Flutter version for RustDesk library build
|
||||||
|
|
||||||
|
prepare_flutter "${FLUTTER_VERSION}" "${HOME}/flutter"
|
||||||
|
|
||||||
|
# gms is not in thoes files now, but we still keep the following line for future reference(maybe).
|
||||||
|
|
||||||
sed \
|
sed \
|
||||||
-i \
|
-i \
|
||||||
-e '/gms/d' \
|
-e '/gms/d' \
|
||||||
flutter/android/build.gradle \
|
flutter/android/build.gradle \
|
||||||
flutter/android/app/build.gradle
|
flutter/android/app/build.gradle
|
||||||
|
|
||||||
|
# `firebase_analytics` is not in these files now, but we still keep the following lines.
|
||||||
|
|
||||||
sed \
|
sed \
|
||||||
-i \
|
-i \
|
||||||
-e '/firebase_analytics/d' \
|
-e '/firebase_analytics/d' \
|
||||||
@@ -296,34 +381,6 @@ prebuild)
|
|||||||
-e '/firebase/Id' \
|
-e '/firebase/Id' \
|
||||||
flutter/lib/main.dart
|
flutter/lib/main.dart
|
||||||
|
|
||||||
if [ "${FLUTTER_VERSION}" = "3.13.9" ]; then
|
|
||||||
# Fix for android 3.13.9
|
|
||||||
# https://github.com/rustdesk/rustdesk/blob/285e974d1a52c891d5fcc28e963d724e085558bc/.github/workflows/flutter-build.yml#L862
|
|
||||||
|
|
||||||
sed \
|
|
||||||
-i \
|
|
||||||
-e 's/extended_text: .*/extended_text: 11.1.0/' \
|
|
||||||
-e 's/uni_links_desktop/#uni_links_desktop/g' \
|
|
||||||
flutter/pubspec.yaml
|
|
||||||
|
|
||||||
set --
|
|
||||||
|
|
||||||
while read -r _1; do
|
|
||||||
set -- "$@" "${_1}"
|
|
||||||
done 0<<.a
|
|
||||||
$(find flutter/lib/ -type f -name "*dart*")
|
|
||||||
.a
|
|
||||||
|
|
||||||
sed \
|
|
||||||
-i \
|
|
||||||
-e 's/textScaler: TextScaler.linear(\(.*\)),/textScaleFactor: \1,/g' \
|
|
||||||
"$@"
|
|
||||||
|
|
||||||
set --
|
|
||||||
fi
|
|
||||||
|
|
||||||
sed -i "s/FLUTTER_VERSION_PLACEHOLDER/${FLUTTER_VERSION}/" flutter-sdk/.gclient
|
|
||||||
|
|
||||||
;;
|
;;
|
||||||
build)
|
build)
|
||||||
# build: perform actual build of APK file
|
# build: perform actual build of APK file
|
||||||
@@ -335,9 +392,12 @@ build)
|
|||||||
# '.github/workflows/flutter-build.yml'
|
# '.github/workflows/flutter-build.yml'
|
||||||
#
|
#
|
||||||
|
|
||||||
|
# Flutter used to compile main Rustdesk library
|
||||||
|
|
||||||
FLUTTER_VERSION="$(yq -r \
|
FLUTTER_VERSION="$(yq -r \
|
||||||
.env.ANDROID_FLUTTER_VERSION \
|
.env.ANDROID_FLUTTER_VERSION \
|
||||||
.github/workflows/flutter-build.yml)"
|
.github/workflows/flutter-build.yml)"
|
||||||
|
|
||||||
if [ -z "${FLUTTER_VERSION}" ]; then
|
if [ -z "${FLUTTER_VERSION}" ]; then
|
||||||
FLUTTER_VERSION="$(yq -r \
|
FLUTTER_VERSION="$(yq -r \
|
||||||
.env.FLUTTER_VERSION \
|
.env.FLUTTER_VERSION \
|
||||||
@@ -373,16 +433,11 @@ build)
|
|||||||
|
|
||||||
pushd flutter
|
pushd flutter
|
||||||
|
|
||||||
|
flutter clean
|
||||||
flutter packages pub get
|
flutter packages pub get
|
||||||
|
|
||||||
popd # flutter
|
popd # flutter
|
||||||
|
|
||||||
# Generate FFI bindings
|
|
||||||
|
|
||||||
flutter_rust_bridge_codegen \
|
|
||||||
--rust-input ./src/flutter_ffi.rs \
|
|
||||||
--dart-output ./flutter/lib/generated_bridge.dart
|
|
||||||
|
|
||||||
# Build host android deps
|
# Build host android deps
|
||||||
|
|
||||||
bash flutter/build_android_deps.sh "${ANDROID_ABI}"
|
bash flutter/build_android_deps.sh "${ANDROID_ABI}"
|
||||||
|
|||||||
@@ -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 -
|
|
||||||
@@ -21,6 +21,6 @@
|
|||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>1.0</string>
|
<string>1.0</string>
|
||||||
<key>MinimumOSVersion</key>
|
<key>MinimumOSVersion</key>
|
||||||
<string>12.0</string>
|
<string>13.0</string>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|||||||
@@ -1,10 +1,7 @@
|
|||||||
# Uncomment this line to define a global platform for your project
|
|
||||||
# platform :ios, '12.0'
|
|
||||||
|
|
||||||
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
|
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
|
||||||
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
|
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
|
||||||
|
|
||||||
platform :ios, '12.0'
|
platform :ios, '13.0'
|
||||||
|
|
||||||
project 'Runner', {
|
project 'Runner', {
|
||||||
'Debug' => :debug,
|
'Debug' => :debug,
|
||||||
|
|||||||
@@ -133,10 +133,10 @@ 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
|
||||||
|
|
||||||
PODFILE CHECKSUM: d4cb12ad5d3bdb3352770b1d3db237584e155156
|
PODFILE CHECKSUM: 83d1b0fb6fc8613d8312a03b8e1540d37cfc5d2c
|
||||||
|
|
||||||
COCOAPODS: 1.15.2
|
COCOAPODS: 1.15.2
|
||||||
|
|||||||
@@ -347,7 +347,7 @@
|
|||||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||||
MTL_ENABLE_DEBUG_INFO = NO;
|
MTL_ENABLE_DEBUG_INFO = NO;
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
SUPPORTED_PLATFORMS = iphoneos;
|
SUPPORTED_PLATFORMS = iphoneos;
|
||||||
@@ -491,7 +491,7 @@
|
|||||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||||
MTL_ENABLE_DEBUG_INFO = YES;
|
MTL_ENABLE_DEBUG_INFO = YES;
|
||||||
ONLY_ACTIVE_ARCH = YES;
|
ONLY_ACTIVE_ARCH = YES;
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
@@ -541,7 +541,7 @@
|
|||||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||||
MTL_ENABLE_DEBUG_INFO = NO;
|
MTL_ENABLE_DEBUG_INFO = NO;
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
SUPPORTED_PLATFORMS = iphoneos;
|
SUPPORTED_PLATFORMS = iphoneos;
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -64,6 +64,9 @@ int androidVersion = 0;
|
|||||||
// So we need to use this flag to enable/disable resizable.
|
// So we need to use this flag to enable/disable resizable.
|
||||||
bool _linuxWindowResizable = true;
|
bool _linuxWindowResizable = true;
|
||||||
|
|
||||||
|
// Only used on Windows(window manager).
|
||||||
|
bool _ignoreDevicePixelRatio = true;
|
||||||
|
|
||||||
/// only available for Windows target
|
/// only available for Windows target
|
||||||
int windowsBuildNumber = 0;
|
int windowsBuildNumber = 0;
|
||||||
DesktopType? desktopType;
|
DesktopType? desktopType;
|
||||||
@@ -1611,12 +1614,6 @@ Widget getPlatformImage(String platform, {double size = 50}) {
|
|||||||
return SvgPicture.asset('assets/$platform.svg', height: size, width: size);
|
return SvgPicture.asset('assets/$platform.svg', height: size, width: size);
|
||||||
}
|
}
|
||||||
|
|
||||||
class OffsetDevicePixelRatio {
|
|
||||||
Offset offset;
|
|
||||||
final double devicePixelRatio;
|
|
||||||
OffsetDevicePixelRatio(this.offset, this.devicePixelRatio);
|
|
||||||
}
|
|
||||||
|
|
||||||
class LastWindowPosition {
|
class LastWindowPosition {
|
||||||
double? width;
|
double? width;
|
||||||
double? height;
|
double? height;
|
||||||
@@ -1699,8 +1696,10 @@ Future<void> saveWindowPosition(WindowType type, {int? windowId}) async {
|
|||||||
if (isFullscreen || isMaximized) {
|
if (isFullscreen || isMaximized) {
|
||||||
setPreFrame();
|
setPreFrame();
|
||||||
} else {
|
} else {
|
||||||
position = await windowManager.getPosition();
|
position = await windowManager.getPosition(
|
||||||
sz = await windowManager.getSize();
|
ignoreDevicePixelRatio: _ignoreDevicePixelRatio);
|
||||||
|
sz = await windowManager.getSize(
|
||||||
|
ignoreDevicePixelRatio: _ignoreDevicePixelRatio);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
@@ -1818,7 +1817,7 @@ bool isPointInRect(Offset point, Rect rect) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// return null means center
|
/// return null means center
|
||||||
Future<OffsetDevicePixelRatio?> _adjustRestoreMainWindowOffset(
|
Future<Offset?> _adjustRestoreMainWindowOffset(
|
||||||
double? left,
|
double? left,
|
||||||
double? top,
|
double? top,
|
||||||
double? width,
|
double? width,
|
||||||
@@ -1832,13 +1831,9 @@ Future<OffsetDevicePixelRatio?> _adjustRestoreMainWindowOffset(
|
|||||||
double? frameTop;
|
double? frameTop;
|
||||||
double? frameRight;
|
double? frameRight;
|
||||||
double? frameBottom;
|
double? frameBottom;
|
||||||
double devicePixelRatio = 1.0;
|
|
||||||
|
|
||||||
if (isDesktop || isWebDesktop) {
|
if (isDesktop || isWebDesktop) {
|
||||||
for (final screen in await window_size.getScreenList()) {
|
for (final screen in await window_size.getScreenList()) {
|
||||||
if (isPointInRect(Offset(left, top), screen.visibleFrame)) {
|
|
||||||
devicePixelRatio = screen.scaleFactor;
|
|
||||||
}
|
|
||||||
frameLeft = frameLeft == null
|
frameLeft = frameLeft == null
|
||||||
? screen.visibleFrame.left
|
? screen.visibleFrame.left
|
||||||
: min(screen.visibleFrame.left, frameLeft);
|
: min(screen.visibleFrame.left, frameLeft);
|
||||||
@@ -1872,7 +1867,7 @@ Future<OffsetDevicePixelRatio?> _adjustRestoreMainWindowOffset(
|
|||||||
top < frameTop!) {
|
top < frameTop!) {
|
||||||
return null;
|
return null;
|
||||||
} else {
|
} else {
|
||||||
return OffsetDevicePixelRatio(Offset(left, top), devicePixelRatio);
|
return Offset(left, top);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1932,47 +1927,23 @@ Future<bool> restoreWindowPosition(WindowType type,
|
|||||||
}
|
}
|
||||||
|
|
||||||
final size = await _adjustRestoreMainWindowSize(lpos.width, lpos.height);
|
final size = await _adjustRestoreMainWindowSize(lpos.width, lpos.height);
|
||||||
final offsetDevicePixelRatio = await _adjustRestoreMainWindowOffset(
|
final offsetLeftTop = await _adjustRestoreMainWindowOffset(
|
||||||
lpos.offsetWidth,
|
lpos.offsetWidth,
|
||||||
lpos.offsetHeight,
|
lpos.offsetHeight,
|
||||||
size.width,
|
size.width,
|
||||||
size.height,
|
size.height,
|
||||||
);
|
);
|
||||||
debugPrint(
|
debugPrint(
|
||||||
"restore lpos: ${size.width}/${size.height}, offset:${offsetDevicePixelRatio?.offset.dx}/${offsetDevicePixelRatio?.offset.dy}, devicePixelRatio:${offsetDevicePixelRatio?.devicePixelRatio}, isMaximized: ${lpos.isMaximized}, isFullscreen: ${lpos.isFullscreen}");
|
"restore lpos: ${size.width}/${size.height}, offset:${offsetLeftTop?.dx}/${offsetLeftTop?.dy}, isMaximized: ${lpos.isMaximized}, isFullscreen: ${lpos.isFullscreen}");
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case WindowType.Main:
|
case WindowType.Main:
|
||||||
// https://github.com/rustdesk/rustdesk/issues/8038
|
|
||||||
// `setBounds()` in `window_manager` will use the current devicePixelRatio.
|
|
||||||
// So we need to adjust the offset by the scale factor.
|
|
||||||
// https://github.com/rustdesk-org/window_manager/blob/f19acdb008645366339444a359a45c3257c8b32e/windows/window_manager.cpp#L701
|
|
||||||
if (isWindows) {
|
|
||||||
double? curDevicePixelRatio;
|
|
||||||
Offset curPos = await windowManager.getPosition();
|
|
||||||
for (final screen in await window_size.getScreenList()) {
|
|
||||||
if (isPointInRect(curPos, screen.visibleFrame)) {
|
|
||||||
curDevicePixelRatio = screen.scaleFactor;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (curDevicePixelRatio != null &&
|
|
||||||
curDevicePixelRatio != 0 &&
|
|
||||||
offsetDevicePixelRatio != null) {
|
|
||||||
if (offsetDevicePixelRatio.devicePixelRatio != 0) {
|
|
||||||
final scale =
|
|
||||||
offsetDevicePixelRatio.devicePixelRatio / curDevicePixelRatio;
|
|
||||||
offsetDevicePixelRatio.offset =
|
|
||||||
offsetDevicePixelRatio.offset.scale(scale, scale);
|
|
||||||
debugPrint(
|
|
||||||
"restore new offset: ${offsetDevicePixelRatio.offset.dx}/${offsetDevicePixelRatio.offset.dy}, scale:$scale");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
restorePos() async {
|
restorePos() async {
|
||||||
if (offsetDevicePixelRatio == null) {
|
if (offsetLeftTop == null) {
|
||||||
await windowManager.center();
|
await windowManager.center();
|
||||||
} else {
|
} else {
|
||||||
await windowManager.setPosition(offsetDevicePixelRatio.offset);
|
await windowManager.setPosition(offsetLeftTop,
|
||||||
|
ignoreDevicePixelRatio: _ignoreDevicePixelRatio);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (lpos.isMaximized == true) {
|
if (lpos.isMaximized == true) {
|
||||||
@@ -1981,20 +1952,39 @@ Future<bool> restoreWindowPosition(WindowType type,
|
|||||||
await windowManager.maximize();
|
await windowManager.maximize();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (!bind.isIncomingOnly() || bind.isOutgoingOnly()) {
|
final storeSize = !bind.isIncomingOnly() || bind.isOutgoingOnly();
|
||||||
await windowManager.setSize(size);
|
if (isWindows) {
|
||||||
|
if (storeSize) {
|
||||||
|
// We need to set the window size first to avoid the incorrect size in some special cases.
|
||||||
|
// E.g. There are two monitors, the left one is 100% DPI and the right one is 175% DPI.
|
||||||
|
// The window belongs to the left monitor, but if it is moved a little to the right, it will belong to the right monitor.
|
||||||
|
// After restoring, the size will be incorrect.
|
||||||
|
// See known issue in https://github.com/rustdesk/rustdesk/pull/9840
|
||||||
|
await windowManager.setSize(size,
|
||||||
|
ignoreDevicePixelRatio: _ignoreDevicePixelRatio);
|
||||||
|
}
|
||||||
|
await restorePos();
|
||||||
|
if (storeSize) {
|
||||||
|
await windowManager.setSize(size,
|
||||||
|
ignoreDevicePixelRatio: _ignoreDevicePixelRatio);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (storeSize) {
|
||||||
|
await windowManager.setSize(size,
|
||||||
|
ignoreDevicePixelRatio: _ignoreDevicePixelRatio);
|
||||||
|
}
|
||||||
|
await restorePos();
|
||||||
}
|
}
|
||||||
await restorePos();
|
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
default:
|
default:
|
||||||
final wc = WindowController.fromWindowId(windowId!);
|
final wc = WindowController.fromWindowId(windowId!);
|
||||||
restoreFrame() async {
|
restoreFrame() async {
|
||||||
if (offsetDevicePixelRatio == null) {
|
if (offsetLeftTop == null) {
|
||||||
await wc.center();
|
await wc.center();
|
||||||
} else {
|
} else {
|
||||||
final frame = Rect.fromLTWH(offsetDevicePixelRatio.offset.dx,
|
final frame = Rect.fromLTWH(
|
||||||
offsetDevicePixelRatio.offset.dy, size.width, size.height);
|
offsetLeftTop.dx, offsetLeftTop.dy, size.width, size.height);
|
||||||
await wc.setFrame(frame);
|
await wc.setFrame(frame);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2255,7 +2245,10 @@ List<String>? urlLinkToCmdArgs(Uri uri) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var key = uri.queryParameters["key"];
|
var queryParameters =
|
||||||
|
uri.queryParameters.map((k, v) => MapEntry(k.toLowerCase(), v));
|
||||||
|
|
||||||
|
var key = queryParameters["key"];
|
||||||
if (id != null) {
|
if (id != null) {
|
||||||
if (key != null) {
|
if (key != null) {
|
||||||
id = "$id?key=$key";
|
id = "$id?key=$key";
|
||||||
@@ -2264,7 +2257,7 @@ List<String>? urlLinkToCmdArgs(Uri uri) {
|
|||||||
|
|
||||||
if (isMobile) {
|
if (isMobile) {
|
||||||
if (id != null) {
|
if (id != null) {
|
||||||
final forceRelay = uri.queryParameters["relay"] != null;
|
final forceRelay = queryParameters["relay"] != null;
|
||||||
connect(Get.context!, id, forceRelay: forceRelay);
|
connect(Get.context!, id, forceRelay: forceRelay);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -2274,7 +2267,7 @@ List<String>? urlLinkToCmdArgs(Uri uri) {
|
|||||||
if (command != null && id != null) {
|
if (command != null && id != null) {
|
||||||
args.add(command);
|
args.add(command);
|
||||||
args.add(id);
|
args.add(id);
|
||||||
var param = uri.queryParameters;
|
var param = queryParameters;
|
||||||
String? password = param["password"];
|
String? password = param["password"];
|
||||||
if (password != null) args.addAll(['--password', password]);
|
if (password != null) args.addAll(['--password', password]);
|
||||||
String? switch_uuid = param["switch_uuid"];
|
String? switch_uuid = param["switch_uuid"];
|
||||||
@@ -2508,9 +2501,20 @@ Future<void> onActiveWindowChanged() async {
|
|||||||
// But the app will not close.
|
// But the app will not close.
|
||||||
//
|
//
|
||||||
// No idea why we need to delay here, `terminate()` itself is also an async function.
|
// No idea why we need to delay here, `terminate()` itself is also an async function.
|
||||||
Future.delayed(Duration.zero, () {
|
//
|
||||||
RdPlatformChannel.instance.terminate();
|
// A quick workaround, use `Timer.periodic` to avoid the app not closing.
|
||||||
});
|
// Because `await windowManager.close()` and `RdPlatformChannel.instance.terminate()`
|
||||||
|
// may not work since `Flutter 3.24.4`, see the following logs.
|
||||||
|
// A delay will allow the app to close.
|
||||||
|
//
|
||||||
|
//```
|
||||||
|
// embedder.cc (2725): 'FlutterPlatformMessageCreateResponseHandle' returned 'kInvalidArguments'. Engine handle was invalid.
|
||||||
|
// 2024-11-11 11:41:11.546 RustDesk[90272:2567686] Failed to create a FlutterPlatformMessageResponseHandle (2)
|
||||||
|
// embedder.cc (2672): 'FlutterEngineSendPlatformMessage' returned 'kInvalidArguments'. Invalid engine handle.
|
||||||
|
// 2024-11-11 11:41:11.565 RustDesk[90272:2567686] Failed to send message to Flutter engine on channel 'flutter/lifecycle' (2).
|
||||||
|
// ```
|
||||||
|
periodic_immediate(
|
||||||
|
Duration(milliseconds: 30), RdPlatformChannel.instance.terminate);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2603,7 +2607,7 @@ class ServerConfig {
|
|||||||
config['relay'] = relayServer.trim();
|
config['relay'] = relayServer.trim();
|
||||||
config['api'] = apiServer.trim();
|
config['api'] = apiServer.trim();
|
||||||
config['key'] = key.trim();
|
config['key'] = key.trim();
|
||||||
return base64Encode(Uint8List.fromList(jsonEncode(config).codeUnits))
|
return base64UrlEncode(Uint8List.fromList(jsonEncode(config).codeUnits))
|
||||||
.split('')
|
.split('')
|
||||||
.reversed
|
.reversed
|
||||||
.join();
|
.join();
|
||||||
@@ -2726,30 +2730,6 @@ Future<bool> osxRequestAudio() async {
|
|||||||
return await kMacOSPermChannel.invokeMethod("requestRecordAudio");
|
return await kMacOSPermChannel.invokeMethod("requestRecordAudio");
|
||||||
}
|
}
|
||||||
|
|
||||||
class DraggableNeverScrollableScrollPhysics extends ScrollPhysics {
|
|
||||||
/// Creates scroll physics that does not let the user scroll.
|
|
||||||
const DraggableNeverScrollableScrollPhysics({super.parent});
|
|
||||||
|
|
||||||
@override
|
|
||||||
DraggableNeverScrollableScrollPhysics applyTo(ScrollPhysics? ancestor) {
|
|
||||||
return DraggableNeverScrollableScrollPhysics(parent: buildParent(ancestor));
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool shouldAcceptUserOffset(ScrollMetrics position) {
|
|
||||||
// TODO: find a better solution to check if the offset change is caused by the scrollbar.
|
|
||||||
// Workaround: when dragging with the scrollbar, it always triggers an [IdleScrollActivity].
|
|
||||||
if (position is ScrollPositionWithSingleContext) {
|
|
||||||
// ignore: invalid_use_of_protected_member, invalid_use_of_visible_for_testing_member
|
|
||||||
return position.activity is IdleScrollActivity;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool get allowImplicitScrolling => false;
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget futureBuilder(
|
Widget futureBuilder(
|
||||||
{required Future? future, required Widget Function(dynamic data) hasData}) {
|
{required Future? future, required Widget Function(dynamic data) hasData}) {
|
||||||
return FutureBuilder(
|
return FutureBuilder(
|
||||||
@@ -2829,7 +2809,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(
|
||||||
@@ -2841,6 +2821,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(
|
||||||
@@ -3623,3 +3608,33 @@ List<SubWindowResizeEdge>? get subWindowManagerEnableResizeEdges => isWindows
|
|||||||
void earlyAssert() {
|
void earlyAssert() {
|
||||||
assert('\1' == '1');
|
assert('\1' == '1');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void checkUpdate() {
|
||||||
|
if (!isWeb) {
|
||||||
|
if (!bind.isCustomClient()) {
|
||||||
|
platformFFI.registerEventHandler(
|
||||||
|
kCheckSoftwareUpdateFinish, kCheckSoftwareUpdateFinish,
|
||||||
|
(Map<String, dynamic> evt) async {
|
||||||
|
if (evt['url'] is String) {
|
||||||
|
stateGlobal.updateUrl.value = evt['url'];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Timer(const Duration(seconds: 1), () async {
|
||||||
|
bind.mainGetSoftwareUpdateUrl();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
|
||||||
|
import 'package:bot_toast/bot_toast.dart';
|
||||||
import 'package:dropdown_button2/dropdown_button2.dart';
|
import 'package:dropdown_button2/dropdown_button2.dart';
|
||||||
import 'package:dynamic_layouts/dynamic_layouts.dart';
|
import 'package:dynamic_layouts/dynamic_layouts.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
@@ -285,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
|
||||||
@@ -316,13 +317,14 @@ class _AddressBookState extends State<AddressBook> {
|
|||||||
|
|
||||||
Widget _buildTags() {
|
Widget _buildTags() {
|
||||||
return Obx(() {
|
return Obx(() {
|
||||||
final List tags;
|
List tags;
|
||||||
if (gFFI.abModel.sortTags.value) {
|
if (gFFI.abModel.sortTags.value) {
|
||||||
tags = gFFI.abModel.currentAbTags.toList();
|
tags = gFFI.abModel.currentAbTags.toList();
|
||||||
tags.sort();
|
tags.sort();
|
||||||
} else {
|
} else {
|
||||||
tags = gFFI.abModel.currentAbTags;
|
tags = gFFI.abModel.currentAbTags.toList();
|
||||||
}
|
}
|
||||||
|
tags = [kUntagged, ...tags].toList();
|
||||||
final editPermission = gFFI.abModel.current.canWrite();
|
final editPermission = gFFI.abModel.current.canWrite();
|
||||||
tagBuilder(String e) {
|
tagBuilder(String e) {
|
||||||
return AddressBookTag(
|
return AddressBookTag(
|
||||||
@@ -554,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'),
|
||||||
@@ -567,7 +569,7 @@ class _AddressBookState extends State<AddressBook> {
|
|||||||
? null
|
? null
|
||||||
: translate('Alias'),
|
: translate('Alias'),
|
||||||
),
|
),
|
||||||
)),
|
).workaroundFreezeLinuxMint()),
|
||||||
),
|
),
|
||||||
if (isCurrentAbShared)
|
if (isCurrentAbShared)
|
||||||
row(
|
row(
|
||||||
@@ -596,7 +598,7 @@ class _AddressBookState extends State<AddressBook> {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
).workaroundFreezeLinuxMint(),
|
||||||
)),
|
)),
|
||||||
if (gFFI.abModel.currentAbTags.isNotEmpty)
|
if (gFFI.abModel.currentAbTags.isNotEmpty)
|
||||||
Align(
|
Align(
|
||||||
@@ -669,6 +671,14 @@ class _AddressBookState extends State<AddressBook> {
|
|||||||
} else {
|
} else {
|
||||||
final tags = field.trim().split(RegExp(r"[\s,;\n]+"));
|
final tags = field.trim().split(RegExp(r"[\s,;\n]+"));
|
||||||
field = tags.join(',');
|
field = tags.join(',');
|
||||||
|
for (var t in [kUntagged, translate(kUntagged)]) {
|
||||||
|
if (tags.contains(t)) {
|
||||||
|
BotToast.showText(
|
||||||
|
contentColor: Colors.red, text: 'Tag name cannot be "$t"');
|
||||||
|
isInProgress = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
gFFI.abModel.addTags(tags);
|
gFFI.abModel.addTags(tags);
|
||||||
// final currentPeers
|
// final currentPeers
|
||||||
}
|
}
|
||||||
@@ -694,7 +704,7 @@ class _AddressBookState extends State<AddressBook> {
|
|||||||
),
|
),
|
||||||
controller: controller,
|
controller: controller,
|
||||||
autofocus: true,
|
autofocus: true,
|
||||||
),
|
).workaroundFreezeLinuxMint(),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -741,12 +751,14 @@ class AddressBookTag extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const double radius = 8;
|
const double radius = 8;
|
||||||
|
final isUnTagged = name == kUntagged;
|
||||||
|
final showAction = showActionMenu && !isUnTagged;
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
onTap: onTap,
|
onTap: onTap,
|
||||||
onTapDown: showActionMenu ? setPosition : null,
|
onTapDown: showAction ? setPosition : null,
|
||||||
onSecondaryTapDown: showActionMenu ? setPosition : null,
|
onSecondaryTapDown: showAction ? setPosition : null,
|
||||||
onSecondaryTap: showActionMenu ? () => _showMenu(context, pos) : null,
|
onSecondaryTap: showAction ? () => _showMenu(context, pos) : null,
|
||||||
onLongPress: showActionMenu ? () => _showMenu(context, pos) : null,
|
onLongPress: showAction ? () => _showMenu(context, pos) : null,
|
||||||
child: Obx(() => Container(
|
child: Obx(() => Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: tags.contains(name)
|
color: tags.contains(name)
|
||||||
@@ -758,17 +770,18 @@ class AddressBookTag extends StatelessWidget {
|
|||||||
child: IntrinsicWidth(
|
child: IntrinsicWidth(
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Container(
|
if (!isUnTagged)
|
||||||
width: radius,
|
Container(
|
||||||
height: radius,
|
width: radius,
|
||||||
decoration: BoxDecoration(
|
height: radius,
|
||||||
shape: BoxShape.circle,
|
decoration: BoxDecoration(
|
||||||
color: tags.contains(name)
|
shape: BoxShape.circle,
|
||||||
? Colors.white
|
color: tags.contains(name)
|
||||||
: gFFI.abModel.getCurrentAbTagColor(name)),
|
? Colors.white
|
||||||
).marginOnly(right: radius / 2),
|
: gFFI.abModel.getCurrentAbTagColor(name)),
|
||||||
|
).marginOnly(right: radius / 2),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Text(name,
|
child: Text(isUnTagged ? translate(name) : name,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
color: tags.contains(name) ? Colors.white : null)),
|
color: tags.contains(name) ? Colors.white : null)),
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ 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';
|
import 'package:flutter_hbb/models/platform_model.dart';
|
||||||
|
|
||||||
const _kWindowsSystemSound = 'System Sound';
|
const _kSystemSound = 'System Sound';
|
||||||
|
|
||||||
typedef AudioINputSetDevice = void Function(String device);
|
typedef AudioINputSetDevice = void Function(String device);
|
||||||
typedef AudioInputBuilder = Widget Function(
|
typedef AudioInputBuilder = Widget Function(
|
||||||
@@ -21,7 +21,7 @@ class AudioInput extends StatelessWidget {
|
|||||||
: super(key: key);
|
: super(key: key);
|
||||||
|
|
||||||
static String getDefault() {
|
static String getDefault() {
|
||||||
if (isWindows) return translate('System Sound');
|
if (bind.mainAudioSupportLoopback()) return translate(_kSystemSound);
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,8 +55,8 @@ class AudioInput extends StatelessWidget {
|
|||||||
static Future<Map<String, Object>> getDevicesInfo(
|
static Future<Map<String, Object>> getDevicesInfo(
|
||||||
bool isCm, bool isVoiceCall) async {
|
bool isCm, bool isVoiceCall) async {
|
||||||
List<String> devices = (await bind.mainGetSoundInputs()).toList();
|
List<String> devices = (await bind.mainGetSoundInputs()).toList();
|
||||||
if (isWindows) {
|
if (bind.mainAudioSupportLoopback()) {
|
||||||
devices.insert(0, translate(_kWindowsSystemSound));
|
devices.insert(0, translate(_kSystemSound));
|
||||||
}
|
}
|
||||||
String current = await getValue(isCm, isVoiceCall);
|
String current = await getValue(isCm, isVoiceCall);
|
||||||
return {'devices': devices, 'current': current};
|
return {'devices': devices, 'current': current};
|
||||||
|
|||||||
@@ -167,7 +167,7 @@ class ChatPage extends StatelessWidget implements PageShape {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
).workaroundFreezeLinuxMint();
|
||||||
return SelectionArea(child: chat);
|
return SelectionArea(child: chat);
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -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),
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ class CustomTouchGestureRecognizer extends ScaleGestureRecognizer {
|
|||||||
// end
|
// end
|
||||||
switch (_currentState) {
|
switch (_currentState) {
|
||||||
case GestureState.oneFingerPan:
|
case GestureState.oneFingerPan:
|
||||||
debugPrint("TwoFingerState.pan onEnd");
|
debugPrint("OneFingerState.pan onEnd");
|
||||||
if (onOneFingerPanEnd != null) {
|
if (onOneFingerPanEnd != null) {
|
||||||
onOneFingerPanEnd!(_getDragEndDetails(d));
|
onOneFingerPanEnd!(_getDragEndDetails(d));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
/*
|
/*
|
||||||
|
|||||||
@@ -145,7 +145,7 @@ class _MyGroupState extends State<MyGroup> {
|
|||||||
border: InputBorder.none,
|
border: InputBorder.none,
|
||||||
isDense: true,
|
isDense: true,
|
||||||
),
|
),
|
||||||
)),
|
).workaroundFreezeLinuxMint()),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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()),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
))
|
))
|
||||||
|
|||||||
@@ -743,7 +743,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(
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import 'package:dynamic_layouts/dynamic_layouts.dart';
|
|||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_hbb/consts.dart';
|
import 'package:flutter_hbb/consts.dart';
|
||||||
import 'package:flutter_hbb/desktop/widgets/scroll_wrapper.dart';
|
import 'package:flutter_hbb/models/ab_model.dart';
|
||||||
import 'package:flutter_hbb/models/peer_tab_model.dart';
|
import 'package:flutter_hbb/models/peer_tab_model.dart';
|
||||||
import 'package:flutter_hbb/models/state_model.dart';
|
import 'package:flutter_hbb/models/state_model.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
@@ -270,33 +270,24 @@ class _PeersViewState extends State<_PeersView>
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
: peerCardUiType.value == PeerUiType.list
|
: peerCardUiType.value == PeerUiType.list
|
||||||
? DesktopScrollWrapper(
|
? ListView.builder(
|
||||||
scrollController: _scrollController,
|
controller: _scrollController,
|
||||||
child: ListView.builder(
|
itemCount: peers.length,
|
||||||
controller: _scrollController,
|
itemBuilder: (BuildContext context, int index) {
|
||||||
physics: DraggableNeverScrollableScrollPhysics(),
|
return buildOnePeer(peers[index], false).marginOnly(
|
||||||
itemCount: peers.length,
|
right: space,
|
||||||
itemBuilder: (BuildContext context, int index) {
|
top: index == 0 ? 0 : space / 2,
|
||||||
return buildOnePeer(peers[index], false)
|
bottom: space / 2);
|
||||||
.marginOnly(
|
},
|
||||||
right: space,
|
|
||||||
top: index == 0 ? 0 : space / 2,
|
|
||||||
bottom: space / 2);
|
|
||||||
}),
|
|
||||||
)
|
)
|
||||||
: DesktopScrollWrapper(
|
: DynamicGridView.builder(
|
||||||
scrollController: _scrollController,
|
gridDelegate: SliverGridDelegateWithWrapping(
|
||||||
child: DynamicGridView.builder(
|
mainAxisSpacing: space / 2,
|
||||||
controller: _scrollController,
|
crossAxisSpacing: space),
|
||||||
physics: DraggableNeverScrollableScrollPhysics(),
|
itemCount: peers.length,
|
||||||
gridDelegate: SliverGridDelegateWithWrapping(
|
itemBuilder: (BuildContext context, int index) {
|
||||||
mainAxisSpacing: space / 2,
|
return buildOnePeer(peers[index], false);
|
||||||
crossAxisSpacing: space),
|
}));
|
||||||
itemCount: peers.length,
|
|
||||||
itemBuilder: (BuildContext context, int index) {
|
|
||||||
return buildOnePeer(peers[index], false);
|
|
||||||
}),
|
|
||||||
));
|
|
||||||
|
|
||||||
if (updateEvent == UpdateEvent.load) {
|
if (updateEvent == UpdateEvent.load) {
|
||||||
_curPeers.clear();
|
_curPeers.clear();
|
||||||
@@ -532,15 +523,22 @@ class AddressBookPeersView extends BasePeersView {
|
|||||||
if (selectedTags.isEmpty) {
|
if (selectedTags.isEmpty) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
// The result of a no-tag union with normal tags, still allows normal tags to perform union or intersection operations.
|
||||||
|
final selectedNormalTags =
|
||||||
|
selectedTags.where((tag) => tag != kUntagged).toList();
|
||||||
|
if (selectedTags.contains(kUntagged)) {
|
||||||
|
if (idents.isEmpty) return true;
|
||||||
|
if (selectedNormalTags.isEmpty) return false;
|
||||||
|
}
|
||||||
if (gFFI.abModel.filterByIntersection.value) {
|
if (gFFI.abModel.filterByIntersection.value) {
|
||||||
for (final tag in selectedTags) {
|
for (final tag in selectedNormalTags) {
|
||||||
if (!idents.contains(tag)) {
|
if (!idents.contains(tag)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
for (final tag in selectedTags) {
|
for (final tag in selectedNormalTags) {
|
||||||
if (idents.contains(tag)) {
|
if (idents.contains(tag)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -84,8 +84,17 @@ class _RawTouchGestureDetectorRegionState
|
|||||||
double _mouseScrollIntegral = 0; // mouse scroll speed controller
|
double _mouseScrollIntegral = 0; // mouse scroll speed controller
|
||||||
double _scale = 1;
|
double _scale = 1;
|
||||||
|
|
||||||
|
// Workaround tap down event when two fingers are used to scale(mobile)
|
||||||
|
TapDownDetails? _lastTapDownDetails;
|
||||||
|
|
||||||
PointerDeviceKind? lastDeviceKind;
|
PointerDeviceKind? lastDeviceKind;
|
||||||
|
|
||||||
|
// For touch mode, onDoubleTap
|
||||||
|
// `onDoubleTap()` does not provide the position of the tap event.
|
||||||
|
Offset _lastPosOfDoubleTapDown = Offset.zero;
|
||||||
|
bool _touchModePanStarted = false;
|
||||||
|
Offset _doubleFinerTapPosition = Offset.zero;
|
||||||
|
|
||||||
FFI get ffi => widget.ffi;
|
FFI get ffi => widget.ffi;
|
||||||
FfiModel get ffiModel => widget.ffiModel;
|
FfiModel get ffiModel => widget.ffiModel;
|
||||||
InputModel get inputModel => widget.inputModel;
|
InputModel get inputModel => widget.inputModel;
|
||||||
@@ -100,152 +109,190 @@ class _RawTouchGestureDetectorRegionState
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
onTapDown(TapDownDetails d) {
|
onTapDown(TapDownDetails d) async {
|
||||||
lastDeviceKind = d.kind;
|
lastDeviceKind = d.kind;
|
||||||
if (lastDeviceKind != PointerDeviceKind.touch) {
|
if (lastDeviceKind != PointerDeviceKind.touch) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (handleTouch) {
|
if (handleTouch) {
|
||||||
|
_lastPosOfDoubleTapDown = d.localPosition;
|
||||||
// Desktop or mobile "Touch mode"
|
// Desktop or mobile "Touch mode"
|
||||||
if (ffi.cursorModel.move(d.localPosition.dx, d.localPosition.dy)) {
|
_lastTapDownDetails = d;
|
||||||
inputModel.tapDown(MouseButtons.left);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onTapUp(TapUpDetails d) {
|
onTapUp(TapUpDetails d) async {
|
||||||
|
final TapDownDetails? lastTapDownDetails = _lastTapDownDetails;
|
||||||
|
_lastTapDownDetails = null;
|
||||||
if (lastDeviceKind != PointerDeviceKind.touch) {
|
if (lastDeviceKind != PointerDeviceKind.touch) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (handleTouch) {
|
if (handleTouch) {
|
||||||
if (ffi.cursorModel.move(d.localPosition.dx, d.localPosition.dy)) {
|
final isMoved =
|
||||||
inputModel.tapUp(MouseButtons.left);
|
await ffi.cursorModel.move(d.localPosition.dx, d.localPosition.dy);
|
||||||
|
if (isMoved) {
|
||||||
|
if (lastTapDownDetails != null) {
|
||||||
|
await inputModel.tapDown(MouseButtons.left);
|
||||||
|
}
|
||||||
|
await inputModel.tapUp(MouseButtons.left);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onTap() {
|
onTap() async {
|
||||||
if (lastDeviceKind != PointerDeviceKind.touch) {
|
if (lastDeviceKind != PointerDeviceKind.touch) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!handleTouch) {
|
if (!handleTouch) {
|
||||||
// Mobile, "Mouse mode"
|
// Mobile, "Mouse mode"
|
||||||
inputModel.tap(MouseButtons.left);
|
await inputModel.tap(MouseButtons.left);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onDoubleTapDown(TapDownDetails d) {
|
onDoubleTapDown(TapDownDetails d) async {
|
||||||
lastDeviceKind = d.kind;
|
lastDeviceKind = d.kind;
|
||||||
if (lastDeviceKind != PointerDeviceKind.touch) {
|
if (lastDeviceKind != PointerDeviceKind.touch) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (handleTouch) {
|
if (handleTouch) {
|
||||||
ffi.cursorModel.move(d.localPosition.dx, d.localPosition.dy);
|
_lastPosOfDoubleTapDown = d.localPosition;
|
||||||
|
await ffi.cursorModel.move(d.localPosition.dx, d.localPosition.dy);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onDoubleTap() {
|
onDoubleTap() async {
|
||||||
if (lastDeviceKind != PointerDeviceKind.touch) {
|
if (lastDeviceKind != PointerDeviceKind.touch) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (ffiModel.touchMode && ffi.cursorModel.lastIsBlocked) {
|
if (ffiModel.touchMode && ffi.cursorModel.lastIsBlocked) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
inputModel.tap(MouseButtons.left);
|
if (handleTouch &&
|
||||||
inputModel.tap(MouseButtons.left);
|
!ffi.cursorModel.isInRemoteRect(_lastPosOfDoubleTapDown)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await inputModel.tap(MouseButtons.left);
|
||||||
|
await inputModel.tap(MouseButtons.left);
|
||||||
}
|
}
|
||||||
|
|
||||||
onLongPressDown(LongPressDownDetails d) {
|
onLongPressDown(LongPressDownDetails d) async {
|
||||||
lastDeviceKind = d.kind;
|
lastDeviceKind = d.kind;
|
||||||
if (lastDeviceKind != PointerDeviceKind.touch) {
|
if (lastDeviceKind != PointerDeviceKind.touch) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (handleTouch) {
|
if (handleTouch) {
|
||||||
ffi.cursorModel.move(d.localPosition.dx, d.localPosition.dy);
|
_lastPosOfDoubleTapDown = d.localPosition;
|
||||||
_cacheLongPressPosition = d.localPosition;
|
_cacheLongPressPosition = d.localPosition;
|
||||||
|
if (!ffi.cursorModel.isInRemoteRect(d.localPosition)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
_cacheLongPressPositionTs = DateTime.now().millisecondsSinceEpoch;
|
_cacheLongPressPositionTs = DateTime.now().millisecondsSinceEpoch;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onLongPressUp() {
|
onLongPressUp() async {
|
||||||
if (lastDeviceKind != PointerDeviceKind.touch) {
|
if (lastDeviceKind != PointerDeviceKind.touch) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (handleTouch) {
|
if (handleTouch) {
|
||||||
inputModel.tapUp(MouseButtons.left);
|
await inputModel.tapUp(MouseButtons.left);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// for mobiles
|
// for mobiles
|
||||||
onLongPress() {
|
onLongPress() async {
|
||||||
if (lastDeviceKind != PointerDeviceKind.touch) {
|
if (lastDeviceKind != PointerDeviceKind.touch) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (handleTouch) {
|
if (handleTouch) {
|
||||||
ffi.cursorModel
|
final isMoved = await ffi.cursorModel
|
||||||
.move(_cacheLongPressPosition.dx, _cacheLongPressPosition.dy);
|
.move(_cacheLongPressPosition.dx, _cacheLongPressPosition.dy);
|
||||||
|
if (!isMoved) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!ffi.ffiModel.isPeerMobile) {
|
||||||
|
await inputModel.tap(MouseButtons.right);
|
||||||
}
|
}
|
||||||
inputModel.tap(MouseButtons.right);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onDoubleFinerTapDown(TapDownDetails d) {
|
onDoubleFinerTapDown(TapDownDetails d) async {
|
||||||
lastDeviceKind = d.kind;
|
lastDeviceKind = d.kind;
|
||||||
if (lastDeviceKind != PointerDeviceKind.touch) {
|
if (lastDeviceKind != PointerDeviceKind.touch) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
_doubleFinerTapPosition = d.localPosition;
|
||||||
// ignore for desktop and mobile
|
// ignore for desktop and mobile
|
||||||
}
|
}
|
||||||
|
|
||||||
onDoubleFinerTap(TapDownDetails d) {
|
onDoubleFinerTap(TapDownDetails d) async {
|
||||||
lastDeviceKind = d.kind;
|
lastDeviceKind = d.kind;
|
||||||
if (lastDeviceKind != PointerDeviceKind.touch) {
|
if (lastDeviceKind != PointerDeviceKind.touch) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if ((isDesktop || isWebDesktop) || !ffiModel.touchMode) {
|
|
||||||
inputModel.tap(MouseButtons.right);
|
// mobile mouse mode or desktop touch screen
|
||||||
|
final isMobileMouseMode = isMobile && !ffiModel.touchMode;
|
||||||
|
// We can't use `d.localPosition` here because it's always (0, 0) on desktop.
|
||||||
|
final isDesktopInRemoteRect = (isDesktop || isWebDesktop) &&
|
||||||
|
ffi.cursorModel.isInRemoteRect(_doubleFinerTapPosition);
|
||||||
|
if (isMobileMouseMode || isDesktopInRemoteRect) {
|
||||||
|
await inputModel.tap(MouseButtons.right);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onHoldDragStart(DragStartDetails d) {
|
onHoldDragStart(DragStartDetails d) async {
|
||||||
lastDeviceKind = d.kind;
|
lastDeviceKind = d.kind;
|
||||||
if (lastDeviceKind != PointerDeviceKind.touch) {
|
if (lastDeviceKind != PointerDeviceKind.touch) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!handleTouch) {
|
if (!handleTouch) {
|
||||||
inputModel.sendMouse('down', MouseButtons.left);
|
await inputModel.sendMouse('down', MouseButtons.left);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onHoldDragUpdate(DragUpdateDetails d) {
|
onHoldDragUpdate(DragUpdateDetails d) async {
|
||||||
if (lastDeviceKind != PointerDeviceKind.touch) {
|
if (lastDeviceKind != PointerDeviceKind.touch) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!handleTouch) {
|
if (!handleTouch) {
|
||||||
ffi.cursorModel.updatePan(d.delta, d.localPosition, handleTouch);
|
await ffi.cursorModel.updatePan(d.delta, d.localPosition, handleTouch);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onHoldDragEnd(DragEndDetails d) {
|
onHoldDragEnd(DragEndDetails d) async {
|
||||||
if (lastDeviceKind != PointerDeviceKind.touch) {
|
if (lastDeviceKind != PointerDeviceKind.touch) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!handleTouch) {
|
if (!handleTouch) {
|
||||||
inputModel.sendMouse('up', MouseButtons.left);
|
await inputModel.sendMouse('up', MouseButtons.left);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onOneFingerPanStart(BuildContext context, DragStartDetails d) {
|
onOneFingerPanStart(BuildContext context, DragStartDetails d) async {
|
||||||
|
final TapDownDetails? lastTapDownDetails = _lastTapDownDetails;
|
||||||
|
_lastTapDownDetails = null;
|
||||||
lastDeviceKind = d.kind ?? lastDeviceKind;
|
lastDeviceKind = d.kind ?? lastDeviceKind;
|
||||||
if (lastDeviceKind != PointerDeviceKind.touch) {
|
if (lastDeviceKind != PointerDeviceKind.touch) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (handleTouch) {
|
if (handleTouch) {
|
||||||
|
if (lastTapDownDetails != null) {
|
||||||
|
await ffi.cursorModel.move(lastTapDownDetails.localPosition.dx,
|
||||||
|
lastTapDownDetails.localPosition.dy);
|
||||||
|
}
|
||||||
if (ffi.cursorModel.shouldBlock(d.localPosition.dx, d.localPosition.dy)) {
|
if (ffi.cursorModel.shouldBlock(d.localPosition.dx, d.localPosition.dy)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (!ffi.cursorModel.isInRemoteRect(d.localPosition)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_touchModePanStarted = true;
|
||||||
if (isDesktop || isWebDesktop) {
|
if (isDesktop || isWebDesktop) {
|
||||||
ffi.cursorModel.trySetRemoteWindowCoords();
|
ffi.cursorModel.trySetRemoteWindowCoords();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Workaround for the issue that the first pan event is sent a long time after the start event.
|
// Workaround for the issue that the first pan event is sent a long time after the start event.
|
||||||
// If the time interval between the start event and the first pan event is less than 500ms,
|
// If the time interval between the start event and the first pan event is less than 500ms,
|
||||||
// we consider to use the long press position as the start position.
|
// we consider to use the long press position as the start position.
|
||||||
@@ -253,11 +300,11 @@ class _RawTouchGestureDetectorRegionState
|
|||||||
// TODO: We should find a better way to send the first pan event as soon as possible.
|
// TODO: We should find a better way to send the first pan event as soon as possible.
|
||||||
if (DateTime.now().millisecondsSinceEpoch - _cacheLongPressPositionTs <
|
if (DateTime.now().millisecondsSinceEpoch - _cacheLongPressPositionTs <
|
||||||
500) {
|
500) {
|
||||||
ffi.cursorModel
|
await ffi.cursorModel
|
||||||
.move(_cacheLongPressPosition.dx, _cacheLongPressPosition.dy);
|
.move(_cacheLongPressPosition.dx, _cacheLongPressPosition.dy);
|
||||||
}
|
}
|
||||||
inputModel.sendMouse('down', MouseButtons.left);
|
await inputModel.sendMouse('down', MouseButtons.left);
|
||||||
ffi.cursorModel.move(d.localPosition.dx, d.localPosition.dy);
|
await ffi.cursorModel.move(d.localPosition.dx, d.localPosition.dy);
|
||||||
} else {
|
} else {
|
||||||
final offset = ffi.cursorModel.offset;
|
final offset = ffi.cursorModel.offset;
|
||||||
final cursorX = offset.dx;
|
final cursorX = offset.dx;
|
||||||
@@ -266,39 +313,46 @@ class _RawTouchGestureDetectorRegionState
|
|||||||
ffi.cursorModel.getVisibleRect().inflate(1); // extend edges
|
ffi.cursorModel.getVisibleRect().inflate(1); // extend edges
|
||||||
final size = MediaQueryData.fromView(View.of(context)).size;
|
final size = MediaQueryData.fromView(View.of(context)).size;
|
||||||
if (!visible.contains(Offset(cursorX, cursorY))) {
|
if (!visible.contains(Offset(cursorX, cursorY))) {
|
||||||
ffi.cursorModel.move(size.width / 2, size.height / 2);
|
await ffi.cursorModel.move(size.width / 2, size.height / 2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onOneFingerPanUpdate(DragUpdateDetails d) {
|
onOneFingerPanUpdate(DragUpdateDetails d) async {
|
||||||
if (lastDeviceKind != PointerDeviceKind.touch) {
|
if (lastDeviceKind != PointerDeviceKind.touch) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (ffi.cursorModel.shouldBlock(d.localPosition.dx, d.localPosition.dy)) {
|
if (ffi.cursorModel.shouldBlock(d.localPosition.dx, d.localPosition.dy)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ffi.cursorModel.updatePan(d.delta, d.localPosition, handleTouch);
|
if (handleTouch && !_touchModePanStarted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await ffi.cursorModel.updatePan(d.delta, d.localPosition, handleTouch);
|
||||||
}
|
}
|
||||||
|
|
||||||
onOneFingerPanEnd(DragEndDetails d) {
|
onOneFingerPanEnd(DragEndDetails d) async {
|
||||||
|
_touchModePanStarted = false;
|
||||||
if (lastDeviceKind != PointerDeviceKind.touch) {
|
if (lastDeviceKind != PointerDeviceKind.touch) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (isDesktop || isWebDesktop) {
|
if (isDesktop || isWebDesktop) {
|
||||||
ffi.cursorModel.clearRemoteWindowCoords();
|
ffi.cursorModel.clearRemoteWindowCoords();
|
||||||
}
|
}
|
||||||
inputModel.sendMouse('up', MouseButtons.left);
|
if (handleTouch) {
|
||||||
|
await inputModel.sendMouse('up', MouseButtons.left);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// scale + pan event
|
// scale + pan event
|
||||||
onTwoFingerScaleStart(ScaleStartDetails d) {
|
onTwoFingerScaleStart(ScaleStartDetails d) {
|
||||||
|
_lastTapDownDetails = null;
|
||||||
if (lastDeviceKind != PointerDeviceKind.touch) {
|
if (lastDeviceKind != PointerDeviceKind.touch) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onTwoFingerScaleUpdate(ScaleUpdateDetails d) {
|
onTwoFingerScaleUpdate(ScaleUpdateDetails d) async {
|
||||||
if (lastDeviceKind != PointerDeviceKind.touch) {
|
if (lastDeviceKind != PointerDeviceKind.touch) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -307,7 +361,7 @@ class _RawTouchGestureDetectorRegionState
|
|||||||
_scale = d.scale;
|
_scale = d.scale;
|
||||||
|
|
||||||
if (scale != 0) {
|
if (scale != 0) {
|
||||||
bind.sessionSendPointer(
|
await bind.sessionSendPointer(
|
||||||
sessionId: sessionId,
|
sessionId: sessionId,
|
||||||
msg: json.encode(
|
msg: json.encode(
|
||||||
PointerEventToRust(kPointerEventKindTouch, 'scale', scale)
|
PointerEventToRust(kPointerEventKindTouch, 'scale', scale)
|
||||||
@@ -322,21 +376,22 @@ class _RawTouchGestureDetectorRegionState
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onTwoFingerScaleEnd(ScaleEndDetails d) {
|
onTwoFingerScaleEnd(ScaleEndDetails d) async {
|
||||||
if (lastDeviceKind != PointerDeviceKind.touch) {
|
if (lastDeviceKind != PointerDeviceKind.touch) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if ((isDesktop || isWebDesktop)) {
|
if ((isDesktop || isWebDesktop)) {
|
||||||
bind.sessionSendPointer(
|
await bind.sessionSendPointer(
|
||||||
sessionId: sessionId,
|
sessionId: sessionId,
|
||||||
msg: json.encode(
|
msg: json.encode(
|
||||||
PointerEventToRust(kPointerEventKindTouch, 'scale', 0).toJson()));
|
PointerEventToRust(kPointerEventKindTouch, 'scale', 0).toJson()));
|
||||||
} else {
|
} else {
|
||||||
// mobile
|
// mobile
|
||||||
_scale = 1;
|
_scale = 1;
|
||||||
bind.sessionSetViewStyle(sessionId: sessionId, value: "");
|
// No idea why we need to set the view style to "" here.
|
||||||
|
// bind.sessionSetViewStyle(sessionId: sessionId, value: "");
|
||||||
}
|
}
|
||||||
inputModel.sendMouse('up', MouseButtons.left);
|
await inputModel.sendMouse('up', MouseButtons.left);
|
||||||
}
|
}
|
||||||
|
|
||||||
get onHoldDragCancel => null;
|
get onHoldDragCancel => null;
|
||||||
|
|||||||
@@ -169,6 +169,13 @@ const int kWindowMainId = 0;
|
|||||||
const String kPointerEventKindTouch = "touch";
|
const String kPointerEventKindTouch = "touch";
|
||||||
const String kPointerEventKindMouse = "mouse";
|
const String kPointerEventKindMouse = "mouse";
|
||||||
|
|
||||||
|
const String kMouseEventTypeDefault = "";
|
||||||
|
const String kMouseEventTypePanStart = "pan_start";
|
||||||
|
const String kMouseEventTypePanUpdate = "pan_update";
|
||||||
|
const String kMouseEventTypePanEnd = "pan_end";
|
||||||
|
const String kMouseEventTypeDown = "down";
|
||||||
|
const String kMouseEventTypeUp = "up";
|
||||||
|
|
||||||
const String kKeyFlutterKey = "flutter_key";
|
const String kKeyFlutterKey = "flutter_key";
|
||||||
|
|
||||||
const String kKeyShowDisplaysAsIndividualWindows =
|
const String kKeyShowDisplaysAsIndividualWindows =
|
||||||
@@ -202,7 +209,7 @@ const double kMinFps = 5;
|
|||||||
const double kDefaultFps = 30;
|
const double kDefaultFps = 30;
|
||||||
const double kMaxFps = 120;
|
const double kMaxFps = 120;
|
||||||
|
|
||||||
const double kMinQuality = 5;
|
const double kMinQuality = 10;
|
||||||
const double kDefaultQuality = 50;
|
const double kDefaultQuality = 50;
|
||||||
const double kMaxQuality = 100;
|
const double kMaxQuality = 100;
|
||||||
const double kMaxMoreQuality = 2000;
|
const double kMaxMoreQuality = 2000;
|
||||||
@@ -237,10 +244,6 @@ const double kDesktopIconButtonSplashRadius = 20;
|
|||||||
/// [kMinCursorSize] indicates min cursor (w, h)
|
/// [kMinCursorSize] indicates min cursor (w, h)
|
||||||
const int kMinCursorSize = 12;
|
const int kMinCursorSize = 12;
|
||||||
|
|
||||||
/// [kDefaultScrollAmountMultiplier] indicates how many rows can be scrolled after a minimum scroll action of mouse
|
|
||||||
const kDefaultScrollAmountMultiplier = 5.0;
|
|
||||||
const kDefaultScrollDuration = Duration(milliseconds: 50);
|
|
||||||
const kDefaultMouseWheelThrottleDuration = Duration(milliseconds: 50);
|
|
||||||
const kFullScreenEdgeSize = 0.0;
|
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.
|
||||||
|
|||||||
@@ -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 (_) {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -203,6 +206,8 @@ class _ConnectionPageState extends State<ConnectionPage>
|
|||||||
|
|
||||||
bool isPeersLoading = false;
|
bool isPeersLoading = false;
|
||||||
bool isPeersLoaded = false;
|
bool isPeersLoaded = false;
|
||||||
|
// https://github.com/flutter/flutter/issues/157244
|
||||||
|
Iterable<Peer> _autocompleteOpts = [];
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
@@ -330,7 +335,7 @@ class _ConnectionPageState extends State<ConnectionPage>
|
|||||||
child: Autocomplete<Peer>(
|
child: Autocomplete<Peer>(
|
||||||
optionsBuilder: (TextEditingValue textEditingValue) {
|
optionsBuilder: (TextEditingValue textEditingValue) {
|
||||||
if (textEditingValue.text == '') {
|
if (textEditingValue.text == '') {
|
||||||
return const Iterable<Peer>.empty();
|
_autocompleteOpts = const Iterable<Peer>.empty();
|
||||||
} else if (peers.isEmpty && !isPeersLoaded) {
|
} else if (peers.isEmpty && !isPeersLoaded) {
|
||||||
Peer emptyPeer = Peer(
|
Peer emptyPeer = Peer(
|
||||||
id: '',
|
id: '',
|
||||||
@@ -346,7 +351,7 @@ class _ConnectionPageState extends State<ConnectionPage>
|
|||||||
rdpUsername: '',
|
rdpUsername: '',
|
||||||
loginName: '',
|
loginName: '',
|
||||||
);
|
);
|
||||||
return [emptyPeer];
|
_autocompleteOpts = [emptyPeer];
|
||||||
} else {
|
} else {
|
||||||
String textWithoutSpaces =
|
String textWithoutSpaces =
|
||||||
textEditingValue.text.replaceAll(" ", "");
|
textEditingValue.text.replaceAll(" ", "");
|
||||||
@@ -357,8 +362,7 @@ class _ConnectionPageState extends State<ConnectionPage>
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
String textToFind = textEditingValue.text.toLowerCase();
|
String textToFind = textEditingValue.text.toLowerCase();
|
||||||
|
_autocompleteOpts = peers
|
||||||
return peers
|
|
||||||
.where((peer) =>
|
.where((peer) =>
|
||||||
peer.id.toLowerCase().contains(textToFind) ||
|
peer.id.toLowerCase().contains(textToFind) ||
|
||||||
peer.username
|
peer.username
|
||||||
@@ -370,6 +374,7 @@ class _ConnectionPageState extends State<ConnectionPage>
|
|||||||
peer.alias.toLowerCase().contains(textToFind))
|
peer.alias.toLowerCase().contains(textToFind))
|
||||||
.toList();
|
.toList();
|
||||||
}
|
}
|
||||||
|
return _autocompleteOpts;
|
||||||
},
|
},
|
||||||
fieldViewBuilder: (
|
fieldViewBuilder: (
|
||||||
BuildContext context,
|
BuildContext context,
|
||||||
@@ -419,7 +424,7 @@ class _ConnectionPageState extends State<ConnectionPage>
|
|||||||
onSubmitted: (_) {
|
onSubmitted: (_) {
|
||||||
onConnect();
|
onConnect();
|
||||||
},
|
},
|
||||||
));
|
).workaroundFreezeLinuxMint());
|
||||||
},
|
},
|
||||||
onSelected: (option) {
|
onSelected: (option) {
|
||||||
setState(() {
|
setState(() {
|
||||||
@@ -430,6 +435,7 @@ class _ConnectionPageState extends State<ConnectionPage>
|
|||||||
optionsViewBuilder: (BuildContext context,
|
optionsViewBuilder: (BuildContext context,
|
||||||
AutocompleteOnSelected<Peer> onSelected,
|
AutocompleteOnSelected<Peer> onSelected,
|
||||||
Iterable<Peer> options) {
|
Iterable<Peer> options) {
|
||||||
|
options = _autocompleteOpts;
|
||||||
double maxHeight = options.length * 50;
|
double maxHeight = options.length * 50;
|
||||||
if (options.length == 1) {
|
if (options.length == 1) {
|
||||||
maxHeight = 52;
|
maxHeight = 52;
|
||||||
|
|||||||
@@ -12,9 +12,9 @@ import 'package:flutter_hbb/consts.dart';
|
|||||||
import 'package:flutter_hbb/desktop/pages/connection_page.dart';
|
import 'package:flutter_hbb/desktop/pages/connection_page.dart';
|
||||||
import 'package:flutter_hbb/desktop/pages/desktop_setting_page.dart';
|
import 'package:flutter_hbb/desktop/pages/desktop_setting_page.dart';
|
||||||
import 'package:flutter_hbb/desktop/pages/desktop_tab_page.dart';
|
import 'package:flutter_hbb/desktop/pages/desktop_tab_page.dart';
|
||||||
import 'package:flutter_hbb/desktop/widgets/scroll_wrapper.dart';
|
|
||||||
import 'package:flutter_hbb/models/platform_model.dart';
|
import 'package:flutter_hbb/models/platform_model.dart';
|
||||||
import 'package:flutter_hbb/models/server_model.dart';
|
import 'package:flutter_hbb/models/server_model.dart';
|
||||||
|
import 'package:flutter_hbb/models/state_model.dart';
|
||||||
import 'package:flutter_hbb/plugin/ui_manager.dart';
|
import 'package:flutter_hbb/plugin/ui_manager.dart';
|
||||||
import 'package:flutter_hbb/utils/multi_window_manager.dart';
|
import 'package:flutter_hbb/utils/multi_window_manager.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
@@ -35,12 +35,11 @@ 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
|
||||||
bool get wantKeepAlive => true;
|
bool get wantKeepAlive => true;
|
||||||
var updateUrl = '';
|
|
||||||
var systemError = '';
|
var systemError = '';
|
||||||
StreamSubscription? _uniLinksSubscription;
|
StreamSubscription? _uniLinksSubscription;
|
||||||
var svcStopped = false.obs;
|
var svcStopped = false.obs;
|
||||||
@@ -52,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();
|
||||||
|
|
||||||
@@ -59,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) {
|
||||||
@@ -87,7 +93,8 @@ class _DesktopHomePageState extends State<DesktopHomePage>
|
|||||||
if (!isOutgoingOnly) buildIDBoard(context),
|
if (!isOutgoingOnly) buildIDBoard(context),
|
||||||
if (!isOutgoingOnly) buildPasswordBoard(context),
|
if (!isOutgoingOnly) buildPasswordBoard(context),
|
||||||
FutureBuilder<Widget>(
|
FutureBuilder<Widget>(
|
||||||
future: buildHelpCards(),
|
future: Future.value(
|
||||||
|
Obx(() => buildHelpCards(stateGlobal.updateUrl.value))),
|
||||||
builder: (_, data) {
|
builder: (_, data) {
|
||||||
if (data.hasData) {
|
if (data.hasData) {
|
||||||
if (isIncomingOnly) {
|
if (isIncomingOnly) {
|
||||||
@@ -125,47 +132,43 @@ class _DesktopHomePageState extends State<DesktopHomePage>
|
|||||||
child: Container(
|
child: Container(
|
||||||
width: isIncomingOnly ? 280.0 : 200.0,
|
width: isIncomingOnly ? 280.0 : 200.0,
|
||||||
color: Theme.of(context).colorScheme.background,
|
color: Theme.of(context).colorScheme.background,
|
||||||
child: DesktopScrollWrapper(
|
child: Stack(
|
||||||
scrollController: _leftPaneScrollController,
|
children: [
|
||||||
child: Stack(
|
SingleChildScrollView(
|
||||||
children: [
|
controller: _leftPaneScrollController,
|
||||||
SingleChildScrollView(
|
child: Column(
|
||||||
controller: _leftPaneScrollController,
|
key: _childKey,
|
||||||
physics: DraggableNeverScrollableScrollPhysics(),
|
children: children,
|
||||||
child: Column(
|
|
||||||
key: _childKey,
|
|
||||||
children: children,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
if (isOutgoingOnly)
|
),
|
||||||
Positioned(
|
if (isOutgoingOnly)
|
||||||
bottom: 6,
|
Positioned(
|
||||||
left: 12,
|
bottom: 6,
|
||||||
child: Align(
|
left: 12,
|
||||||
alignment: Alignment.centerLeft,
|
child: Align(
|
||||||
child: InkWell(
|
alignment: Alignment.centerLeft,
|
||||||
child: Obx(
|
child: InkWell(
|
||||||
() => Icon(
|
child: Obx(
|
||||||
Icons.settings,
|
() => Icon(
|
||||||
color: _editHover.value
|
Icons.settings,
|
||||||
? textColor
|
color: _editHover.value
|
||||||
: Colors.grey.withOpacity(0.5),
|
? textColor
|
||||||
size: 22,
|
: Colors.grey.withOpacity(0.5),
|
||||||
),
|
size: 22,
|
||||||
),
|
),
|
||||||
onTap: () => {
|
|
||||||
if (DesktopSettingPage.tabKeys.isNotEmpty)
|
|
||||||
{
|
|
||||||
DesktopSettingPage.switch2page(
|
|
||||||
DesktopSettingPage.tabKeys[0])
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onHover: (value) => _editHover.value = value,
|
|
||||||
),
|
),
|
||||||
|
onTap: () => {
|
||||||
|
if (DesktopSettingPage.tabKeys.isNotEmpty)
|
||||||
|
{
|
||||||
|
DesktopSettingPage.switch2page(
|
||||||
|
DesktopSettingPage.tabKeys[0])
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onHover: (value) => _editHover.value = value,
|
||||||
),
|
),
|
||||||
)
|
),
|
||||||
],
|
)
|
||||||
),
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -234,7 +237,7 @@ class _DesktopHomePageState extends State<DesktopHomePage>
|
|||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 22,
|
fontSize: 22,
|
||||||
),
|
),
|
||||||
),
|
).workaroundFreezeLinuxMint(),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
@@ -272,10 +275,21 @@ class _DesktopHomePageState extends State<DesktopHomePage>
|
|||||||
}
|
}
|
||||||
|
|
||||||
buildPasswordBoard(BuildContext context) {
|
buildPasswordBoard(BuildContext context) {
|
||||||
final model = gFFI.serverModel;
|
return ChangeNotifierProvider.value(
|
||||||
|
value: gFFI.serverModel,
|
||||||
|
child: Consumer<ServerModel>(
|
||||||
|
builder: (context, model, child) {
|
||||||
|
return buildPasswordBoard2(context, model);
|
||||||
|
},
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
buildPasswordBoard2(BuildContext context, ServerModel model) {
|
||||||
RxBool refreshHover = false.obs;
|
RxBool refreshHover = false.obs;
|
||||||
RxBool editHover = false.obs;
|
RxBool editHover = false.obs;
|
||||||
final textColor = Theme.of(context).textTheme.titleLarge?.color;
|
final textColor = Theme.of(context).textTheme.titleLarge?.color;
|
||||||
|
final showOneTime = model.approveMode != 'click' &&
|
||||||
|
model.verificationMethod != kUsePermanentPassword;
|
||||||
return Container(
|
return Container(
|
||||||
margin: EdgeInsets.only(left: 20.0, right: 16, top: 13, bottom: 13),
|
margin: EdgeInsets.only(left: 20.0, right: 16, top: 13, bottom: 13),
|
||||||
child: Row(
|
child: Row(
|
||||||
@@ -304,8 +318,7 @@ class _DesktopHomePageState extends State<DesktopHomePage>
|
|||||||
Expanded(
|
Expanded(
|
||||||
child: GestureDetector(
|
child: GestureDetector(
|
||||||
onDoubleTap: () {
|
onDoubleTap: () {
|
||||||
if (model.verificationMethod !=
|
if (showOneTime) {
|
||||||
kUsePermanentPassword) {
|
|
||||||
Clipboard.setData(
|
Clipboard.setData(
|
||||||
ClipboardData(text: model.serverPasswd.text));
|
ClipboardData(text: model.serverPasswd.text));
|
||||||
showToast(translate("Copied"));
|
showToast(translate("Copied"));
|
||||||
@@ -320,25 +333,26 @@ 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(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
AnimatedRotationWidget(
|
if (showOneTime)
|
||||||
onPressed: () => bind.mainUpdateTemporaryPassword(),
|
AnimatedRotationWidget(
|
||||||
child: Tooltip(
|
onPressed: () => bind.mainUpdateTemporaryPassword(),
|
||||||
message: translate('Refresh Password'),
|
child: Tooltip(
|
||||||
child: Obx(() => RotatedBox(
|
message: translate('Refresh Password'),
|
||||||
quarterTurns: 2,
|
child: Obx(() => RotatedBox(
|
||||||
child: Icon(
|
quarterTurns: 2,
|
||||||
Icons.refresh,
|
child: Icon(
|
||||||
color: refreshHover.value
|
Icons.refresh,
|
||||||
? textColor
|
color: refreshHover.value
|
||||||
: Color(0xFFDDDDDD),
|
? textColor
|
||||||
size: 22,
|
: Color(0xFFDDDDDD),
|
||||||
))),
|
size: 22,
|
||||||
),
|
))),
|
||||||
onHover: (value) => refreshHover.value = value,
|
),
|
||||||
).marginOnly(right: 8, top: 4),
|
onHover: (value) => refreshHover.value = value,
|
||||||
|
).marginOnly(right: 8, top: 4),
|
||||||
if (!bind.isDisableSettings())
|
if (!bind.isDisableSettings())
|
||||||
InkWell(
|
InkWell(
|
||||||
child: Tooltip(
|
child: Tooltip(
|
||||||
@@ -409,14 +423,14 @@ class _DesktopHomePageState extends State<DesktopHomePage>
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Widget> buildHelpCards() async {
|
Widget buildHelpCards(String updateUrl) {
|
||||||
if (!bind.isCustomClient() &&
|
if (!bind.isCustomClient() &&
|
||||||
updateUrl.isNotEmpty &&
|
updateUrl.isNotEmpty &&
|
||||||
!isCardClosed &&
|
!isCardClosed &&
|
||||||
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);
|
||||||
@@ -663,20 +677,6 @@ class _DesktopHomePageState extends State<DesktopHomePage>
|
|||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
if (!bind.isCustomClient()) {
|
|
||||||
platformFFI.registerEventHandler(
|
|
||||||
kCheckSoftwareUpdateFinish, kCheckSoftwareUpdateFinish,
|
|
||||||
(Map<String, dynamic> evt) async {
|
|
||||||
if (evt['url'] is String) {
|
|
||||||
setState(() {
|
|
||||||
updateUrl = evt['url'];
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
Timer(const Duration(seconds: 1), () async {
|
|
||||||
bind.mainGetSoftwareUpdateUrl();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
_updateTimer = periodic_immediate(const Duration(seconds: 1), () async {
|
_updateTimer = periodic_immediate(const Duration(seconds: 1), () async {
|
||||||
await gFFI.serverModel.fetchID();
|
await gFFI.serverModel.fetchID();
|
||||||
final error = await bind.mainGetError();
|
final error = await bind.mainGetError();
|
||||||
@@ -812,6 +812,7 @@ class _DesktopHomePageState extends State<DesktopHomePage>
|
|||||||
_updateWindowSize();
|
_updateWindowSize();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
WidgetsBinding.instance.addObserver(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
_updateWindowSize() {
|
_updateWindowSize() {
|
||||||
@@ -833,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(
|
||||||
@@ -930,7 +936,7 @@ void setPasswordDialog({VoidCallback? notEmptyCallback}) async {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
maxLength: maxLength,
|
maxLength: maxLength,
|
||||||
),
|
).workaroundFreezeLinuxMint(),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -957,7 +963,7 @@ void setPasswordDialog({VoidCallback? notEmptyCallback}) async {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
maxLength: maxLength,
|
maxLength: maxLength,
|
||||||
),
|
).workaroundFreezeLinuxMint(),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -11,15 +11,16 @@ import 'package:flutter_hbb/common/widgets/setting_widgets.dart';
|
|||||||
import 'package:flutter_hbb/consts.dart';
|
import 'package:flutter_hbb/consts.dart';
|
||||||
import 'package:flutter_hbb/desktop/pages/desktop_home_page.dart';
|
import 'package:flutter_hbb/desktop/pages/desktop_home_page.dart';
|
||||||
import 'package:flutter_hbb/desktop/pages/desktop_tab_page.dart';
|
import 'package:flutter_hbb/desktop/pages/desktop_tab_page.dart';
|
||||||
|
import 'package:flutter_hbb/mobile/widgets/dialog.dart';
|
||||||
import 'package:flutter_hbb/models/platform_model.dart';
|
import 'package:flutter_hbb/models/platform_model.dart';
|
||||||
import 'package:flutter_hbb/models/server_model.dart';
|
import 'package:flutter_hbb/models/server_model.dart';
|
||||||
|
import 'package:flutter_hbb/models/state_model.dart';
|
||||||
import 'package:flutter_hbb/plugin/manager.dart';
|
import 'package:flutter_hbb/plugin/manager.dart';
|
||||||
import 'package:flutter_hbb/plugin/widgets/desktop_settings.dart';
|
import 'package:flutter_hbb/plugin/widgets/desktop_settings.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:url_launcher/url_launcher.dart';
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
import 'package:url_launcher/url_launcher_string.dart';
|
import 'package:url_launcher/url_launcher_string.dart';
|
||||||
import 'package:flutter_hbb/desktop/widgets/scroll_wrapper.dart';
|
|
||||||
|
|
||||||
import '../../common/widgets/dialog.dart';
|
import '../../common/widgets/dialog.dart';
|
||||||
import '../../common/widgets/login.dart';
|
import '../../common/widgets/login.dart';
|
||||||
@@ -106,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) {
|
||||||
@@ -132,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() {
|
||||||
@@ -206,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,
|
||||||
@@ -226,13 +280,11 @@ class _DesktopSettingPageState extends State<DesktopSettingPage>
|
|||||||
Expanded(
|
Expanded(
|
||||||
child: Container(
|
child: Container(
|
||||||
color: Theme.of(context).scaffoldBackgroundColor,
|
color: Theme.of(context).scaffoldBackgroundColor,
|
||||||
child: DesktopScrollWrapper(
|
child: PageView(
|
||||||
scrollController: controller,
|
controller: controller,
|
||||||
child: PageView(
|
physics: NeverScrollableScrollPhysics(),
|
||||||
controller: controller,
|
children: _children(),
|
||||||
physics: NeverScrollableScrollPhysics(),
|
),
|
||||||
children: _children(),
|
|
||||||
)),
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
@@ -281,13 +333,10 @@ class _DesktopSettingPageState extends State<DesktopSettingPage>
|
|||||||
|
|
||||||
Widget _listView({required List<_TabInfo> tabs}) {
|
Widget _listView({required List<_TabInfo> tabs}) {
|
||||||
final scrollController = ScrollController();
|
final scrollController = ScrollController();
|
||||||
return DesktopScrollWrapper(
|
return ListView(
|
||||||
scrollController: scrollController,
|
controller: scrollController,
|
||||||
child: ListView(
|
children: tabs.map((tab) => _listItem(tab: tab)).toList(),
|
||||||
physics: DraggableNeverScrollableScrollPhysics(),
|
);
|
||||||
controller: scrollController,
|
|
||||||
children: tabs.map((tab) => _listItem(tab: tab)).toList(),
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _listItem({required _TabInfo tab}) {
|
Widget _listItem({required _TabInfo tab}) {
|
||||||
@@ -349,22 +398,19 @@ class _GeneralState extends State<_General> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final scrollController = ScrollController();
|
final scrollController = ScrollController();
|
||||||
return DesktopScrollWrapper(
|
return ListView(
|
||||||
scrollController: scrollController,
|
controller: scrollController,
|
||||||
child: ListView(
|
children: [
|
||||||
physics: DraggableNeverScrollableScrollPhysics(),
|
if (!isWeb) service(),
|
||||||
controller: scrollController,
|
theme(),
|
||||||
children: [
|
_Card(title: 'Language', children: [language()]),
|
||||||
if (!isWeb) service(),
|
if (!isWeb) hwcodec(),
|
||||||
theme(),
|
if (!isWeb) audio(context),
|
||||||
_Card(title: 'Language', children: [language()]),
|
if (!isWeb) record(context),
|
||||||
if (!isWeb) hwcodec(),
|
if (!isWeb) WaylandCard(),
|
||||||
if (!isWeb) audio(context),
|
other()
|
||||||
if (!isWeb) record(context),
|
],
|
||||||
if (!isWeb) WaylandCard(),
|
).marginOnly(bottom: _kListViewBottomMargin);
|
||||||
other()
|
|
||||||
],
|
|
||||||
).marginOnly(bottom: _kListViewBottomMargin));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget theme() {
|
Widget theme() {
|
||||||
@@ -561,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,
|
||||||
@@ -705,29 +750,26 @@ class _SafetyState extends State<_Safety> with AutomaticKeepAliveClientMixin {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
super.build(context);
|
super.build(context);
|
||||||
return DesktopScrollWrapper(
|
return SingleChildScrollView(
|
||||||
scrollController: scrollController,
|
controller: scrollController,
|
||||||
child: SingleChildScrollView(
|
child: Column(
|
||||||
physics: DraggableNeverScrollableScrollPhysics(),
|
children: [
|
||||||
controller: scrollController,
|
_lock(locked, 'Unlock Security Settings', () {
|
||||||
child: Column(
|
locked = false;
|
||||||
children: [
|
setState(() => {});
|
||||||
_lock(locked, 'Unlock Security Settings', () {
|
}),
|
||||||
locked = false;
|
preventMouseKeyBuilder(
|
||||||
setState(() => {});
|
block: locked,
|
||||||
}),
|
child: Column(children: [
|
||||||
AbsorbPointer(
|
permissions(context),
|
||||||
absorbing: locked,
|
password(context),
|
||||||
child: Column(children: [
|
_Card(title: '2FA', children: [tfa()]),
|
||||||
permissions(context),
|
_Card(title: 'ID', children: [changeId()]),
|
||||||
password(context),
|
more(context),
|
||||||
_Card(title: '2FA', children: [tfa()]),
|
]),
|
||||||
_Card(title: 'ID', children: [changeId()]),
|
),
|
||||||
more(context),
|
],
|
||||||
]),
|
)).marginOnly(bottom: _kListViewBottomMargin);
|
||||||
),
|
|
||||||
],
|
|
||||||
)).marginOnly(bottom: _kListViewBottomMargin));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget tfa() {
|
Widget tfa() {
|
||||||
@@ -1029,7 +1071,9 @@ class _SafetyState extends State<_Safety> with AutomaticKeepAliveClientMixin {
|
|||||||
translate('Accept sessions via both'),
|
translate('Accept sessions via both'),
|
||||||
];
|
];
|
||||||
var modeInitialKey = model.approveMode;
|
var modeInitialKey = model.approveMode;
|
||||||
if (!modeKeys.contains(modeInitialKey)) modeInitialKey = '';
|
if (!modeKeys.contains(modeInitialKey)) {
|
||||||
|
modeInitialKey = defaultOptionApproveMode;
|
||||||
|
}
|
||||||
final usePassword = model.approveMode != 'click';
|
final usePassword = model.approveMode != 'click';
|
||||||
|
|
||||||
final isApproveModeFixed = isOptionFixed(kOptionApproveMode);
|
final isApproveModeFixed = isOptionFixed(kOptionApproveMode);
|
||||||
@@ -1144,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 &&
|
||||||
@@ -1301,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:
|
||||||
@@ -1372,112 +1416,82 @@ class _NetworkState extends State<_Network> with AutomaticKeepAliveClientMixin {
|
|||||||
bool get wantKeepAlive => true;
|
bool get wantKeepAlive => true;
|
||||||
bool locked = !isWeb && bind.mainIsInstalled();
|
bool locked = !isWeb && bind.mainIsInstalled();
|
||||||
|
|
||||||
|
final scrollController = ScrollController();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
super.build(context);
|
super.build(context);
|
||||||
bool enabled = !locked;
|
return ListView(controller: scrollController, children: [
|
||||||
final scrollController = ScrollController();
|
_lock(locked, 'Unlock Network Settings', () {
|
||||||
final hideServer =
|
locked = false;
|
||||||
bind.mainGetBuildinOption(key: kOptionHideServerSetting) == 'Y';
|
setState(() => {});
|
||||||
// TODO: support web proxy
|
}),
|
||||||
final hideProxy =
|
preventMouseKeyBuilder(
|
||||||
isWeb || bind.mainGetBuildinOption(key: kOptionHideProxySetting) == 'Y';
|
block: locked,
|
||||||
return DesktopScrollWrapper(
|
child: Column(children: [
|
||||||
scrollController: scrollController,
|
network(context),
|
||||||
child: ListView(
|
]),
|
||||||
controller: scrollController,
|
),
|
||||||
physics: DraggableNeverScrollableScrollPhysics(),
|
]).marginOnly(bottom: _kListViewBottomMargin);
|
||||||
children: [
|
|
||||||
_lock(locked, 'Unlock Network Settings', () {
|
|
||||||
locked = false;
|
|
||||||
setState(() => {});
|
|
||||||
}),
|
|
||||||
AbsorbPointer(
|
|
||||||
absorbing: locked,
|
|
||||||
child: Column(children: [
|
|
||||||
if (!hideServer) server(enabled),
|
|
||||||
if (!hideProxy)
|
|
||||||
_Card(title: 'Proxy', children: [
|
|
||||||
_Button('Socks5/Http(s) Proxy', changeSocks5Proxy,
|
|
||||||
enabled: enabled),
|
|
||||||
]),
|
|
||||||
]),
|
|
||||||
),
|
|
||||||
]).marginOnly(bottom: _kListViewBottomMargin));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
server(bool enabled) {
|
Widget network(BuildContext context) {
|
||||||
// Simple temp wrapper for PR check
|
final hideServer =
|
||||||
tmpWrapper() {
|
bind.mainGetBuildinOption(key: kOptionHideServerSetting) == 'Y';
|
||||||
// Setting page is not modal, oldOptions should only be used when getting options, never when setting.
|
final hideProxy =
|
||||||
Map<String, dynamic> oldOptions = jsonDecode(bind.mainGetOptionsSync());
|
isWeb || bind.mainGetBuildinOption(key: kOptionHideProxySetting) == 'Y';
|
||||||
old(String key) {
|
|
||||||
return (oldOptions[key] ?? '').trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
RxString idErrMsg = ''.obs;
|
if (hideServer && hideProxy) {
|
||||||
RxString relayErrMsg = ''.obs;
|
return Offstage();
|
||||||
RxString apiErrMsg = ''.obs;
|
|
||||||
var idController =
|
|
||||||
TextEditingController(text: old('custom-rendezvous-server'));
|
|
||||||
var relayController = TextEditingController(text: old('relay-server'));
|
|
||||||
var apiController = TextEditingController(text: old('api-server'));
|
|
||||||
var keyController = TextEditingController(text: old('key'));
|
|
||||||
final controllers = [
|
|
||||||
idController,
|
|
||||||
relayController,
|
|
||||||
apiController,
|
|
||||||
keyController,
|
|
||||||
];
|
|
||||||
final errMsgs = [
|
|
||||||
idErrMsg,
|
|
||||||
relayErrMsg,
|
|
||||||
apiErrMsg,
|
|
||||||
];
|
|
||||||
|
|
||||||
submit() async {
|
|
||||||
bool result = await setServerConfig(
|
|
||||||
null,
|
|
||||||
errMsgs,
|
|
||||||
ServerConfig(
|
|
||||||
idServer: idController.text,
|
|
||||||
relayServer: relayController.text,
|
|
||||||
apiServer: apiController.text,
|
|
||||||
key: keyController.text));
|
|
||||||
if (result) {
|
|
||||||
setState(() {});
|
|
||||||
showToast(translate('Successful'));
|
|
||||||
} else {
|
|
||||||
showToast(translate('Failed'));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool secure = !enabled;
|
|
||||||
return _Card(
|
|
||||||
title: 'ID/Relay Server',
|
|
||||||
title_suffix: ServerConfigImportExportWidgets(controllers, errMsgs),
|
|
||||||
children: [
|
|
||||||
Column(
|
|
||||||
children: [
|
|
||||||
Obx(() => _LabeledTextField(context, 'ID Server', idController,
|
|
||||||
idErrMsg.value, enabled, secure)),
|
|
||||||
if (!isWeb)
|
|
||||||
Obx(() => _LabeledTextField(context, 'Relay Server',
|
|
||||||
relayController, relayErrMsg.value, enabled, secure)),
|
|
||||||
Obx(() => _LabeledTextField(context, 'API Server',
|
|
||||||
apiController, apiErrMsg.value, enabled, secure)),
|
|
||||||
_LabeledTextField(
|
|
||||||
context, 'Key', keyController, '', enabled, secure),
|
|
||||||
Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.end,
|
|
||||||
children: [_Button('Apply', submit, enabled: enabled)],
|
|
||||||
).marginOnly(top: 10),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return tmpWrapper();
|
return _Card(
|
||||||
|
title: 'Network',
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
if (!hideServer)
|
||||||
|
ListTile(
|
||||||
|
leading: Icon(Icons.dns_outlined, color: _accentColor),
|
||||||
|
title: Text(
|
||||||
|
translate('ID/Relay Server'),
|
||||||
|
style: TextStyle(fontSize: _kContentFontSize),
|
||||||
|
),
|
||||||
|
enabled: !locked,
|
||||||
|
onTap: () => showServerSettings(gFFI.dialogManager),
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(10),
|
||||||
|
),
|
||||||
|
contentPadding: EdgeInsets.symmetric(horizontal: 16),
|
||||||
|
minLeadingWidth: 0,
|
||||||
|
horizontalTitleGap: 10,
|
||||||
|
),
|
||||||
|
if (!hideServer && !hideProxy)
|
||||||
|
Divider(height: 1, indent: 16, endIndent: 16),
|
||||||
|
if (!hideProxy)
|
||||||
|
ListTile(
|
||||||
|
leading:
|
||||||
|
Icon(Icons.network_ping_outlined, color: _accentColor),
|
||||||
|
title: Text(
|
||||||
|
translate('Socks5/Http(s) Proxy'),
|
||||||
|
style: TextStyle(fontSize: _kContentFontSize),
|
||||||
|
),
|
||||||
|
enabled: !locked,
|
||||||
|
onTap: changeSocks5Proxy,
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(10),
|
||||||
|
),
|
||||||
|
contentPadding: EdgeInsets.symmetric(horizontal: 16),
|
||||||
|
minLeadingWidth: 0,
|
||||||
|
horizontalTitleGap: 10,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1492,19 +1506,14 @@ class _DisplayState extends State<_Display> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final scrollController = ScrollController();
|
final scrollController = ScrollController();
|
||||||
return DesktopScrollWrapper(
|
return ListView(controller: scrollController, children: [
|
||||||
scrollController: scrollController,
|
viewStyle(context),
|
||||||
child: ListView(
|
scrollStyle(context),
|
||||||
controller: scrollController,
|
imageQuality(context),
|
||||||
physics: DraggableNeverScrollableScrollPhysics(),
|
codec(context),
|
||||||
children: [
|
if (!isWeb) privacyModeImpl(context),
|
||||||
viewStyle(context),
|
other(context),
|
||||||
scrollStyle(context),
|
]).marginOnly(bottom: _kListViewBottomMargin);
|
||||||
imageQuality(context),
|
|
||||||
codec(context),
|
|
||||||
if (!isWeb) privacyModeImpl(context),
|
|
||||||
other(context),
|
|
||||||
]).marginOnly(bottom: _kListViewBottomMargin));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget viewStyle(BuildContext context) {
|
Widget viewStyle(BuildContext context) {
|
||||||
@@ -1727,15 +1736,12 @@ class _AccountState extends State<_Account> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final scrollController = ScrollController();
|
final scrollController = ScrollController();
|
||||||
return DesktopScrollWrapper(
|
return ListView(
|
||||||
scrollController: scrollController,
|
controller: scrollController,
|
||||||
child: ListView(
|
children: [
|
||||||
physics: DraggableNeverScrollableScrollPhysics(),
|
_Card(title: 'Account', children: [accountAction(), useInfo()]),
|
||||||
controller: scrollController,
|
],
|
||||||
children: [
|
).marginOnly(bottom: _kListViewBottomMargin);
|
||||||
_Card(title: 'Account', children: [accountAction(), useInfo()]),
|
|
||||||
],
|
|
||||||
).marginOnly(bottom: _kListViewBottomMargin));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget accountAction() {
|
Widget accountAction() {
|
||||||
@@ -1832,18 +1838,14 @@ class _PluginState extends State<_Plugin> {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
bind.pluginListReload();
|
bind.pluginListReload();
|
||||||
final scrollController = ScrollController();
|
final scrollController = ScrollController();
|
||||||
return DesktopScrollWrapper(
|
return ChangeNotifierProvider.value(
|
||||||
scrollController: scrollController,
|
value: pluginManager,
|
||||||
child: ChangeNotifierProvider.value(
|
child: Consumer<PluginManager>(builder: (context, model, child) {
|
||||||
value: pluginManager,
|
return ListView(
|
||||||
child: Consumer<PluginManager>(builder: (context, model, child) {
|
controller: scrollController,
|
||||||
return ListView(
|
children: model.plugins.map((entry) => pluginCard(entry)).toList(),
|
||||||
physics: DraggableNeverScrollableScrollPhysics(),
|
).marginOnly(bottom: _kListViewBottomMargin);
|
||||||
controller: scrollController,
|
}),
|
||||||
children: model.plugins.map((entry) => pluginCard(entry)).toList(),
|
|
||||||
).marginOnly(bottom: _kListViewBottomMargin);
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1895,75 +1897,72 @@ class _AboutState extends State<_About> {
|
|||||||
final fingerprint = data['fingerprint'].toString();
|
final fingerprint = data['fingerprint'].toString();
|
||||||
const linkStyle = TextStyle(decoration: TextDecoration.underline);
|
const linkStyle = TextStyle(decoration: TextDecoration.underline);
|
||||||
final scrollController = ScrollController();
|
final scrollController = ScrollController();
|
||||||
return DesktopScrollWrapper(
|
return SingleChildScrollView(
|
||||||
scrollController: scrollController,
|
controller: scrollController,
|
||||||
child: SingleChildScrollView(
|
child: _Card(title: translate('About RustDesk'), children: [
|
||||||
controller: scrollController,
|
Column(
|
||||||
physics: DraggableNeverScrollableScrollPhysics(),
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
child: _Card(title: translate('About RustDesk'), children: [
|
children: [
|
||||||
Column(
|
const SizedBox(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
height: 8.0,
|
||||||
children: [
|
),
|
||||||
const SizedBox(
|
SelectionArea(
|
||||||
height: 8.0,
|
child: Text('${translate('Version')}: $version')
|
||||||
),
|
.marginSymmetric(vertical: 4.0)),
|
||||||
SelectionArea(
|
SelectionArea(
|
||||||
child: Text('${translate('Version')}: $version')
|
child: Text('${translate('Build Date')}: $buildDate')
|
||||||
.marginSymmetric(vertical: 4.0)),
|
.marginSymmetric(vertical: 4.0)),
|
||||||
SelectionArea(
|
if (!isWeb)
|
||||||
child: Text('${translate('Build Date')}: $buildDate')
|
SelectionArea(
|
||||||
.marginSymmetric(vertical: 4.0)),
|
child: Text('${translate('Fingerprint')}: $fingerprint')
|
||||||
if (!isWeb)
|
.marginSymmetric(vertical: 4.0)),
|
||||||
SelectionArea(
|
InkWell(
|
||||||
child: Text('${translate('Fingerprint')}: $fingerprint')
|
onTap: () {
|
||||||
.marginSymmetric(vertical: 4.0)),
|
launchUrlString('https://rustdesk.com/privacy.html');
|
||||||
InkWell(
|
},
|
||||||
onTap: () {
|
child: Text(
|
||||||
launchUrlString('https://rustdesk.com/privacy.html');
|
translate('Privacy Statement'),
|
||||||
},
|
style: linkStyle,
|
||||||
child: Text(
|
).marginSymmetric(vertical: 4.0)),
|
||||||
translate('Privacy Statement'),
|
InkWell(
|
||||||
style: linkStyle,
|
onTap: () {
|
||||||
).marginSymmetric(vertical: 4.0)),
|
launchUrlString('https://rustdesk.com');
|
||||||
InkWell(
|
},
|
||||||
onTap: () {
|
child: Text(
|
||||||
launchUrlString('https://rustdesk.com');
|
translate('Website'),
|
||||||
},
|
style: linkStyle,
|
||||||
child: Text(
|
).marginSymmetric(vertical: 4.0)),
|
||||||
translate('Website'),
|
Container(
|
||||||
style: linkStyle,
|
decoration: const BoxDecoration(color: Color(0xFF2c8cff)),
|
||||||
).marginSymmetric(vertical: 4.0)),
|
padding:
|
||||||
Container(
|
const EdgeInsets.symmetric(vertical: 24, horizontal: 8),
|
||||||
decoration: const BoxDecoration(color: Color(0xFF2c8cff)),
|
child: SelectionArea(
|
||||||
padding:
|
child: Row(
|
||||||
const EdgeInsets.symmetric(vertical: 24, horizontal: 8),
|
children: [
|
||||||
child: SelectionArea(
|
Expanded(
|
||||||
child: Row(
|
child: Column(
|
||||||
children: [
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
Expanded(
|
children: [
|
||||||
child: Column(
|
Text(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
'Copyright © ${DateTime.now().toString().substring(0, 4)} Purslane Ltd.\n$license',
|
||||||
children: [
|
style: const TextStyle(color: Colors.white),
|
||||||
Text(
|
|
||||||
'Copyright © ${DateTime.now().toString().substring(0, 4)} Purslane Ltd.\n$license',
|
|
||||||
style: const TextStyle(color: Colors.white),
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
translate('Slogan_tip'),
|
|
||||||
style: TextStyle(
|
|
||||||
fontWeight: FontWeight.w800,
|
|
||||||
color: Colors.white),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
Text(
|
||||||
],
|
translate('Slogan_tip'),
|
||||||
)),
|
style: TextStyle(
|
||||||
).marginSymmetric(vertical: 4.0)
|
fontWeight: FontWeight.w800,
|
||||||
],
|
color: Colors.white),
|
||||||
).marginOnly(left: _kContentHMargin)
|
)
|
||||||
]),
|
],
|
||||||
));
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)),
|
||||||
|
).marginSymmetric(vertical: 4.0)
|
||||||
|
],
|
||||||
|
).marginOnly(left: _kContentHMargin)
|
||||||
|
]),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2281,26 +2280,39 @@ _LabeledTextField(
|
|||||||
String errorText,
|
String errorText,
|
||||||
bool enabled,
|
bool enabled,
|
||||||
bool secure) {
|
bool secure) {
|
||||||
return Row(
|
return Table(
|
||||||
|
columnWidths: const {
|
||||||
|
0: FixedColumnWidth(150),
|
||||||
|
1: FlexColumnWidth(),
|
||||||
|
},
|
||||||
|
defaultVerticalAlignment: TableCellVerticalAlignment.middle,
|
||||||
children: [
|
children: [
|
||||||
ConstrainedBox(
|
TableRow(
|
||||||
constraints: const BoxConstraints(minWidth: 140),
|
children: [
|
||||||
child: Text(
|
Padding(
|
||||||
'${translate(label)}:',
|
padding: const EdgeInsets.only(right: 10),
|
||||||
textAlign: TextAlign.right,
|
child: Text(
|
||||||
style: TextStyle(
|
'${translate(label)}:',
|
||||||
fontSize: 16, color: disabledTextColor(context, enabled)),
|
textAlign: TextAlign.right,
|
||||||
).marginOnly(right: 10)),
|
style: TextStyle(
|
||||||
Expanded(
|
fontSize: 16,
|
||||||
child: TextField(
|
color: disabledTextColor(context, enabled),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
TextField(
|
||||||
controller: controller,
|
controller: controller,
|
||||||
enabled: enabled,
|
enabled: enabled,
|
||||||
obscureText: secure,
|
obscureText: secure,
|
||||||
|
autocorrect: false,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
errorText: errorText.isNotEmpty ? errorText : null),
|
errorText: errorText.isNotEmpty ? errorText : null,
|
||||||
|
),
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: disabledTextColor(context, enabled),
|
color: disabledTextColor(context, enabled),
|
||||||
)),
|
),
|
||||||
|
).workaroundFreezeLinuxMint(),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
).marginOnly(bottom: 8);
|
).marginOnly(bottom: 8);
|
||||||
@@ -2478,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),
|
||||||
@@ -2498,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),
|
||||||
@@ -2524,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(),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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()),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import 'package:flutter/services.dart';
|
|||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:wakelock_plus/wakelock_plus.dart';
|
import 'package:wakelock_plus/wakelock_plus.dart';
|
||||||
import 'package:flutter_improved_scrolling/flutter_improved_scrolling.dart';
|
|
||||||
import 'package:flutter_hbb/models/state_model.dart';
|
import 'package:flutter_hbb/models/state_model.dart';
|
||||||
|
|
||||||
import '../../consts.dart';
|
import '../../consts.dart';
|
||||||
@@ -742,12 +741,6 @@ class _ImagePaintState extends State<ImagePaint> {
|
|||||||
ScrollController horizontal,
|
ScrollController horizontal,
|
||||||
ScrollController vertical,
|
ScrollController vertical,
|
||||||
) {
|
) {
|
||||||
final scrollConfig = CustomMouseWheelScrollConfig(
|
|
||||||
scrollDuration: kDefaultScrollDuration,
|
|
||||||
scrollCurve: Curves.linearToEaseOut,
|
|
||||||
mouseWheelTurnsThrottleTimeMs:
|
|
||||||
kDefaultMouseWheelThrottleDuration.inMilliseconds,
|
|
||||||
scrollAmountMultiplier: kDefaultScrollAmountMultiplier);
|
|
||||||
var widget = child;
|
var widget = child;
|
||||||
if (layoutSize.width < size.width) {
|
if (layoutSize.width < size.width) {
|
||||||
widget = ScrollConfiguration(
|
widget = ScrollConfiguration(
|
||||||
@@ -793,36 +786,26 @@ class _ImagePaintState extends State<ImagePaint> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (layoutSize.width < size.width) {
|
if (layoutSize.width < size.width) {
|
||||||
widget = ImprovedScrolling(
|
widget = RawScrollbar(
|
||||||
scrollController: horizontal,
|
thickness: kScrollbarThickness,
|
||||||
enableCustomMouseWheelScrolling: cursorOverImage.isFalse,
|
thumbColor: Colors.grey,
|
||||||
customMouseWheelScrollConfig: scrollConfig,
|
controller: horizontal,
|
||||||
child: RawScrollbar(
|
thumbVisibility: false,
|
||||||
thickness: kScrollbarThickness,
|
trackVisibility: false,
|
||||||
thumbColor: Colors.grey,
|
notificationPredicate: layoutSize.height < size.height
|
||||||
controller: horizontal,
|
? (notification) => notification.depth == 1
|
||||||
thumbVisibility: false,
|
: defaultScrollNotificationPredicate,
|
||||||
trackVisibility: false,
|
child: widget,
|
||||||
notificationPredicate: layoutSize.height < size.height
|
|
||||||
? (notification) => notification.depth == 1
|
|
||||||
: defaultScrollNotificationPredicate,
|
|
||||||
child: widget,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (layoutSize.height < size.height) {
|
if (layoutSize.height < size.height) {
|
||||||
widget = ImprovedScrolling(
|
widget = RawScrollbar(
|
||||||
scrollController: vertical,
|
thickness: kScrollbarThickness,
|
||||||
enableCustomMouseWheelScrolling: cursorOverImage.isFalse,
|
thumbColor: Colors.grey,
|
||||||
customMouseWheelScrollConfig: scrollConfig,
|
controller: vertical,
|
||||||
child: RawScrollbar(
|
thumbVisibility: false,
|
||||||
thickness: kScrollbarThickness,
|
trackVisibility: false,
|
||||||
thumbColor: Colors.grey,
|
child: widget,
|
||||||
controller: vertical,
|
|
||||||
thumbVisibility: false,
|
|
||||||
trackVisibility: false,
|
|
||||||
child: widget,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -110,7 +110,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 +140,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 +194,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 +238,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 +276,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
|
||||||
|
|||||||
@@ -1,27 +0,0 @@
|
|||||||
import 'package:flutter/widgets.dart';
|
|
||||||
import 'package:flutter_hbb/consts.dart';
|
|
||||||
import 'package:flutter_improved_scrolling/flutter_improved_scrolling.dart';
|
|
||||||
|
|
||||||
class DesktopScrollWrapper extends StatelessWidget {
|
|
||||||
final ScrollController scrollController;
|
|
||||||
final Widget child;
|
|
||||||
const DesktopScrollWrapper(
|
|
||||||
{Key? key, required this.scrollController, required this.child})
|
|
||||||
: super(key: key);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return ImprovedScrolling(
|
|
||||||
scrollController: scrollController,
|
|
||||||
enableCustomMouseWheelScrolling: true,
|
|
||||||
// enableKeyboardScrolling: true, // strange behavior on mac
|
|
||||||
customMouseWheelScrollConfig: CustomMouseWheelScrollConfig(
|
|
||||||
scrollDuration: kDefaultScrollDuration,
|
|
||||||
scrollCurve: Curves.linearToEaseOut,
|
|
||||||
mouseWheelTurnsThrottleTimeMs:
|
|
||||||
kDefaultMouseWheelThrottleDuration.inMilliseconds,
|
|
||||||
scrollAmountMultiplier: kDefaultScrollAmountMultiplier),
|
|
||||||
child: child,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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(),
|
||||||
|
|||||||
@@ -120,6 +120,7 @@ Future<void> initEnv(String appType) async {
|
|||||||
void runMainApp(bool startService) async {
|
void runMainApp(bool startService) async {
|
||||||
// register uni links
|
// register uni links
|
||||||
await initEnv(kAppTypeMain);
|
await initEnv(kAppTypeMain);
|
||||||
|
checkUpdate();
|
||||||
// trigger connection status updater
|
// trigger connection status updater
|
||||||
await bind.mainCheckConnectStatus();
|
await bind.mainCheckConnectStatus();
|
||||||
if (startService) {
|
if (startService) {
|
||||||
@@ -156,6 +157,7 @@ void runMainApp(bool startService) async {
|
|||||||
|
|
||||||
void runMobileApp() async {
|
void runMobileApp() async {
|
||||||
await initEnv(kAppTypeMain);
|
await initEnv(kAppTypeMain);
|
||||||
|
checkUpdate();
|
||||||
if (isAndroid) androidChannelInit();
|
if (isAndroid) androidChannelInit();
|
||||||
if (isAndroid) platformFFI.syncAndroidServiceAppDirConfigPath();
|
if (isAndroid) platformFFI.syncAndroidServiceAppDirConfigPath();
|
||||||
draggablePositions.load();
|
draggablePositions.load();
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import 'package:auto_size_text_field/auto_size_text_field.dart';
|
|||||||
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 'package:flutter_hbb/common/widgets/connection_page_title.dart';
|
import 'package:flutter_hbb/common/widgets/connection_page_title.dart';
|
||||||
|
import 'package:flutter_hbb/models/state_model.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:url_launcher/url_launcher.dart';
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
@@ -40,14 +41,15 @@ class _ConnectionPageState extends State<ConnectionPage> {
|
|||||||
final _idController = IDTextEditingController();
|
final _idController = IDTextEditingController();
|
||||||
final RxBool _idEmpty = true.obs;
|
final RxBool _idEmpty = true.obs;
|
||||||
|
|
||||||
/// Update url. If it's not null, means an update is available.
|
|
||||||
var _updateUrl = '';
|
|
||||||
List<Peer> peers = [];
|
List<Peer> peers = [];
|
||||||
|
|
||||||
bool isPeersLoading = false;
|
bool isPeersLoading = false;
|
||||||
bool isPeersLoaded = false;
|
bool isPeersLoaded = false;
|
||||||
StreamSubscription? _uniLinksSubscription;
|
StreamSubscription? _uniLinksSubscription;
|
||||||
|
|
||||||
|
// https://github.com/flutter/flutter/issues/157244
|
||||||
|
Iterable<Peer> _autocompleteOpts = [];
|
||||||
|
|
||||||
_ConnectionPageState() {
|
_ConnectionPageState() {
|
||||||
if (!isWeb) _uniLinksSubscription = listenUniLinks();
|
if (!isWeb) _uniLinksSubscription = listenUniLinks();
|
||||||
_idController.addListener(() {
|
_idController.addListener(() {
|
||||||
@@ -69,22 +71,6 @@ class _ConnectionPageState extends State<ConnectionPage> {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (isAndroid) {
|
|
||||||
if (!bind.isCustomClient()) {
|
|
||||||
platformFFI.registerEventHandler(
|
|
||||||
kCheckSoftwareUpdateFinish, kCheckSoftwareUpdateFinish,
|
|
||||||
(Map<String, dynamic> evt) async {
|
|
||||||
if (evt['url'] is String) {
|
|
||||||
setState(() {
|
|
||||||
_updateUrl = evt['url'];
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
Timer(const Duration(seconds: 1), () async {
|
|
||||||
bind.mainGetSoftwareUpdateUrl();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -94,7 +80,8 @@ class _ConnectionPageState extends State<ConnectionPage> {
|
|||||||
slivers: [
|
slivers: [
|
||||||
SliverList(
|
SliverList(
|
||||||
delegate: SliverChildListDelegate([
|
delegate: SliverChildListDelegate([
|
||||||
if (!bind.isCustomClient()) _buildUpdateUI(),
|
if (!bind.isCustomClient() && !isIOS)
|
||||||
|
Obx(() => _buildUpdateUI(stateGlobal.updateUrl.value)),
|
||||||
_buildRemoteIDTextField(),
|
_buildRemoteIDTextField(),
|
||||||
])),
|
])),
|
||||||
SliverFillRemaining(
|
SliverFillRemaining(
|
||||||
@@ -113,16 +100,22 @@ class _ConnectionPageState extends State<ConnectionPage> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// 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() {
|
Widget _buildUpdateUI(String updateUrl) {
|
||||||
return _updateUrl.isEmpty
|
return updateUrl.isEmpty
|
||||||
? const SizedBox(height: 0)
|
? const SizedBox(height: 0)
|
||||||
: InkWell(
|
: InkWell(
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
final url = 'https://rustdesk.com/download';
|
final url = 'https://rustdesk.com/download';
|
||||||
if (await canLaunchUrl(Uri.parse(url))) {
|
// https://pub.dev/packages/url_launcher#configuration
|
||||||
await launchUrl(Uri.parse(url));
|
// https://developer.android.com/training/package-visibility/use-cases#open-urls-custom-tabs
|
||||||
}
|
//
|
||||||
|
// `await launchUrl(Uri.parse(url))` can also run if skip
|
||||||
|
// 1. The following check
|
||||||
|
// 2. `<action android:name="android.support.customtabs.action.CustomTabsService" />` in AndroidManifest.xml
|
||||||
|
//
|
||||||
|
// But it is better to add the check.
|
||||||
|
await launchUrl(Uri.parse(url));
|
||||||
},
|
},
|
||||||
child: Container(
|
child: Container(
|
||||||
alignment: AlignmentDirectional.center,
|
alignment: AlignmentDirectional.center,
|
||||||
@@ -166,7 +159,7 @@ class _ConnectionPageState extends State<ConnectionPage> {
|
|||||||
child: Autocomplete<Peer>(
|
child: Autocomplete<Peer>(
|
||||||
optionsBuilder: (TextEditingValue textEditingValue) {
|
optionsBuilder: (TextEditingValue textEditingValue) {
|
||||||
if (textEditingValue.text == '') {
|
if (textEditingValue.text == '') {
|
||||||
return const Iterable<Peer>.empty();
|
_autocompleteOpts = const Iterable<Peer>.empty();
|
||||||
} else if (peers.isEmpty && !isPeersLoaded) {
|
} else if (peers.isEmpty && !isPeersLoaded) {
|
||||||
Peer emptyPeer = Peer(
|
Peer emptyPeer = Peer(
|
||||||
id: '',
|
id: '',
|
||||||
@@ -182,7 +175,7 @@ class _ConnectionPageState extends State<ConnectionPage> {
|
|||||||
rdpUsername: '',
|
rdpUsername: '',
|
||||||
loginName: '',
|
loginName: '',
|
||||||
);
|
);
|
||||||
return [emptyPeer];
|
_autocompleteOpts = [emptyPeer];
|
||||||
} else {
|
} else {
|
||||||
String textWithoutSpaces =
|
String textWithoutSpaces =
|
||||||
textEditingValue.text.replaceAll(" ", "");
|
textEditingValue.text.replaceAll(" ", "");
|
||||||
@@ -194,7 +187,7 @@ class _ConnectionPageState extends State<ConnectionPage> {
|
|||||||
}
|
}
|
||||||
String textToFind = textEditingValue.text.toLowerCase();
|
String textToFind = textEditingValue.text.toLowerCase();
|
||||||
|
|
||||||
return peers
|
_autocompleteOpts = peers
|
||||||
.where((peer) =>
|
.where((peer) =>
|
||||||
peer.id.toLowerCase().contains(textToFind) ||
|
peer.id.toLowerCase().contains(textToFind) ||
|
||||||
peer.username
|
peer.username
|
||||||
@@ -206,6 +199,7 @@ class _ConnectionPageState extends State<ConnectionPage> {
|
|||||||
peer.alias.toLowerCase().contains(textToFind))
|
peer.alias.toLowerCase().contains(textToFind))
|
||||||
.toList();
|
.toList();
|
||||||
}
|
}
|
||||||
|
return _autocompleteOpts;
|
||||||
},
|
},
|
||||||
fieldViewBuilder: (BuildContext context,
|
fieldViewBuilder: (BuildContext context,
|
||||||
TextEditingController fieldTextEditingController,
|
TextEditingController fieldTextEditingController,
|
||||||
@@ -274,6 +268,7 @@ class _ConnectionPageState extends State<ConnectionPage> {
|
|||||||
optionsViewBuilder: (BuildContext context,
|
optionsViewBuilder: (BuildContext context,
|
||||||
AutocompleteOnSelected<Peer> onSelected,
|
AutocompleteOnSelected<Peer> onSelected,
|
||||||
Iterable<Peer> options) {
|
Iterable<Peer> options) {
|
||||||
|
options = _autocompleteOpts;
|
||||||
double maxHeight = options.length * 50;
|
double maxHeight = options.length * 50;
|
||||||
if (options.length == 1) {
|
if (options.length == 1) {
|
||||||
maxHeight = 52;
|
maxHeight = 52;
|
||||||
@@ -373,10 +368,6 @@ class _ConnectionPageState extends State<ConnectionPage> {
|
|||||||
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: [
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
import 'dart:ui' as ui;
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
@@ -25,6 +26,19 @@ import '../widgets/dialog.dart';
|
|||||||
|
|
||||||
final initText = '1' * 1024;
|
final initText = '1' * 1024;
|
||||||
|
|
||||||
|
// Workaround for Android (default input method, Microsoft SwiftKey keyboard) when using physical keyboard.
|
||||||
|
// When connecting a physical keyboard, `KeyEvent.physicalKey.usbHidUsage` are wrong is using Microsoft SwiftKey keyboard.
|
||||||
|
// https://github.com/flutter/flutter/issues/159384
|
||||||
|
// https://github.com/flutter/flutter/issues/159383
|
||||||
|
void _disableAndroidSoftKeyboard({bool? isKeyboardVisible}) {
|
||||||
|
if (isAndroid) {
|
||||||
|
if (isKeyboardVisible != true) {
|
||||||
|
// `enable_soft_keyboard` will be set to `true` when clicking the keyboard icon, in `openKeyboard()`.
|
||||||
|
gFFI.invokeMethod("enable_soft_keyboard", false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class RemotePage extends StatefulWidget {
|
class RemotePage extends StatefulWidget {
|
||||||
RemotePage({Key? key, required this.id, this.password, this.isSharedPassword})
|
RemotePage({Key? key, required this.id, this.password, this.isSharedPassword})
|
||||||
: super(key: key);
|
: super(key: key);
|
||||||
@@ -37,12 +51,15 @@ class RemotePage extends StatefulWidget {
|
|||||||
State<RemotePage> createState() => _RemotePageState(id);
|
State<RemotePage> createState() => _RemotePageState(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
class _RemotePageState extends State<RemotePage> {
|
class _RemotePageState extends State<RemotePage> with WidgetsBindingObserver {
|
||||||
Timer? _timer;
|
Timer? _timer;
|
||||||
bool _showBar = !isWebDesktop;
|
bool _showBar = !isWebDesktop;
|
||||||
bool _showGestureHelp = false;
|
bool _showGestureHelp = false;
|
||||||
String _value = '';
|
String _value = '';
|
||||||
Orientation? _currentOrientation;
|
Orientation? _currentOrientation;
|
||||||
|
double _viewInsetsBottom = 0;
|
||||||
|
|
||||||
|
Timer? _timerDidChangeMetrics;
|
||||||
|
|
||||||
final _blockableOverlayState = BlockableOverlayState();
|
final _blockableOverlayState = BlockableOverlayState();
|
||||||
|
|
||||||
@@ -57,9 +74,6 @@ class _RemotePageState extends State<RemotePage> {
|
|||||||
|
|
||||||
final TextEditingController _textController =
|
final TextEditingController _textController =
|
||||||
TextEditingController(text: initText);
|
TextEditingController(text: initText);
|
||||||
// This timer is used to check the composing status of the soft keyboard.
|
|
||||||
// It is used for Android, Korean(and other similar) input method.
|
|
||||||
Timer? _composingTimer;
|
|
||||||
|
|
||||||
_RemotePageState(String id) {
|
_RemotePageState(String id) {
|
||||||
initSharedStates(id);
|
initSharedStates(id);
|
||||||
@@ -98,11 +112,15 @@ class _RemotePageState extends State<RemotePage> {
|
|||||||
if (gFFI.recordingModel.start) {
|
if (gFFI.recordingModel.start) {
|
||||||
showToast(translate('Automatically record outgoing sessions'));
|
showToast(translate('Automatically record outgoing sessions'));
|
||||||
}
|
}
|
||||||
|
_disableAndroidSoftKeyboard(
|
||||||
|
isKeyboardVisible: keyboardVisibilityController.isVisible);
|
||||||
});
|
});
|
||||||
|
WidgetsBinding.instance.addObserver(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> dispose() async {
|
Future<void> dispose() async {
|
||||||
|
WidgetsBinding.instance.removeObserver(this);
|
||||||
// https://github.com/flutter/flutter/issues/64935
|
// https://github.com/flutter/flutter/issues/64935
|
||||||
super.dispose();
|
super.dispose();
|
||||||
gFFI.dialogManager.hideMobileActionsOverlay(store: false);
|
gFFI.dialogManager.hideMobileActionsOverlay(store: false);
|
||||||
@@ -114,7 +132,7 @@ class _RemotePageState extends State<RemotePage> {
|
|||||||
_physicalFocusNode.dispose();
|
_physicalFocusNode.dispose();
|
||||||
await gFFI.close();
|
await gFFI.close();
|
||||||
_timer?.cancel();
|
_timer?.cancel();
|
||||||
_composingTimer?.cancel();
|
_timerDidChangeMetrics?.cancel();
|
||||||
gFFI.dialogManager.dismissAll();
|
gFFI.dialogManager.dismissAll();
|
||||||
await SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual,
|
await SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual,
|
||||||
overlays: SystemUiOverlay.values);
|
overlays: SystemUiOverlay.values);
|
||||||
@@ -129,6 +147,39 @@ class _RemotePageState extends State<RemotePage> {
|
|||||||
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
|
||||||
|
void didChangeMetrics() {
|
||||||
|
// If the soft keyboard is visible and the canvas has been changed(panned or scaled)
|
||||||
|
// Don't try reset the view style and focus the cursor.
|
||||||
|
if (gFFI.cursorModel.lastKeyboardIsVisible &&
|
||||||
|
gFFI.canvasModel.isMobileCanvasChanged) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final newBottom = MediaQueryData.fromView(ui.window).viewInsets.bottom;
|
||||||
|
_timerDidChangeMetrics?.cancel();
|
||||||
|
_timerDidChangeMetrics = Timer(Duration(milliseconds: 100), () async {
|
||||||
|
// We need this comparation because poping up the floating action will also trigger `didChangeMetrics()`.
|
||||||
|
if (newBottom != _viewInsetsBottom) {
|
||||||
|
gFFI.canvasModel.mobileFocusCanvasCursor();
|
||||||
|
_viewInsetsBottom = newBottom;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// to-do: It should be better to use transparent color instead of the bgColor.
|
// to-do: It should be better to use transparent color instead of the bgColor.
|
||||||
// But for now, the transparent color will cause the canvas to be white.
|
// But for now, the transparent color will cause the canvas to be white.
|
||||||
// I'm sure that the white color is caused by the Overlay widget in BlockableOverlay.
|
// I'm sure that the white color is caused by the Overlay widget in BlockableOverlay.
|
||||||
@@ -150,7 +201,6 @@ class _RemotePageState extends State<RemotePage> {
|
|||||||
gFFI.ffiModel.pi.version.isNotEmpty) {
|
gFFI.ffiModel.pi.version.isNotEmpty) {
|
||||||
gFFI.invokeMethod("enable_soft_keyboard", false);
|
gFFI.invokeMethod("enable_soft_keyboard", false);
|
||||||
}
|
}
|
||||||
_composingTimer?.cancel();
|
|
||||||
} else {
|
} else {
|
||||||
_timer?.cancel();
|
_timer?.cancel();
|
||||||
_timer = Timer(kMobileDelaySoftKeyboardFocus, () {
|
_timer = Timer(kMobileDelaySoftKeyboardFocus, () {
|
||||||
@@ -214,13 +264,6 @@ class _RemotePageState extends State<RemotePage> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _handleNonIOSSoftKeyboardInput(String newValue) {
|
void _handleNonIOSSoftKeyboardInput(String newValue) {
|
||||||
_composingTimer?.cancel();
|
|
||||||
if (_textController.value.isComposingRangeValid) {
|
|
||||||
_composingTimer = Timer(Duration(milliseconds: 25), () {
|
|
||||||
_handleNonIOSSoftKeyboardInput(_textController.value.text);
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var oldValue = _value;
|
var oldValue = _value;
|
||||||
_value = newValue;
|
_value = newValue;
|
||||||
if (oldValue.isNotEmpty &&
|
if (oldValue.isNotEmpty &&
|
||||||
@@ -261,14 +304,10 @@ class _RemotePageState extends State<RemotePage> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> handleSoftKeyboardInput(String newValue) async {
|
// handle mobile virtual keyboard
|
||||||
|
void handleSoftKeyboardInput(String newValue) {
|
||||||
if (isIOS) {
|
if (isIOS) {
|
||||||
// fix: TextFormField onChanged event triggered multiple times when Korean input
|
_handleIOSSoftKeyboardInput(newValue);
|
||||||
// https://github.com/rustdesk/rustdesk/pull/9644
|
|
||||||
await Future.delayed(const Duration(milliseconds: 10));
|
|
||||||
|
|
||||||
if (newValue != _textController.text) return;
|
|
||||||
_handleIOSSoftKeyboardInput(_textController.text);
|
|
||||||
} else {
|
} else {
|
||||||
_handleNonIOSSoftKeyboardInput(newValue);
|
_handleNonIOSSoftKeyboardInput(newValue);
|
||||||
}
|
}
|
||||||
@@ -534,7 +573,9 @@ class _RemotePageState extends State<RemotePage> {
|
|||||||
right: 10,
|
right: 10,
|
||||||
child: QualityMonitor(gFFI.qualityMonitorModel),
|
child: QualityMonitor(gFFI.qualityMonitorModel),
|
||||||
),
|
),
|
||||||
KeyHelpTools(requestShow: (keyboardIsVisible || _showGestureHelp)),
|
KeyHelpTools(
|
||||||
|
keyboardIsVisible: keyboardIsVisible,
|
||||||
|
showGestureHelp: _showGestureHelp),
|
||||||
SizedBox(
|
SizedBox(
|
||||||
width: 0,
|
width: 0,
|
||||||
height: 0,
|
height: 0,
|
||||||
@@ -554,8 +595,16 @@ class _RemotePageState extends State<RemotePage> {
|
|||||||
controller: _textController,
|
controller: _textController,
|
||||||
// trick way to make backspace work always
|
// trick way to make backspace work always
|
||||||
keyboardType: TextInputType.multiline,
|
keyboardType: TextInputType.multiline,
|
||||||
|
// `onChanged` may be called depending on the input method if this widget is wrapped in
|
||||||
|
// `Focus(onKeyEvent: ..., child: ...)`
|
||||||
|
// For `Backspace` button in the soft keyboard:
|
||||||
|
// en/fr input method:
|
||||||
|
// 1. The button will not trigger `onKeyEvent` if the text field is not empty.
|
||||||
|
// 2. The button will trigger `onKeyEvent` if the text field is empty.
|
||||||
|
// ko/zh/ja input method: the button will trigger `onKeyEvent`
|
||||||
|
// and the event will not popup if `KeyEventResult.handled` is returned.
|
||||||
onChanged: handleSoftKeyboardInput,
|
onChanged: handleSoftKeyboardInput,
|
||||||
),
|
).workaroundFreezeLinuxMint(),
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
if (showCursorPaint) {
|
if (showCursorPaint) {
|
||||||
@@ -763,10 +812,14 @@ class _RemotePageState extends State<RemotePage> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class KeyHelpTools extends StatefulWidget {
|
class KeyHelpTools extends StatefulWidget {
|
||||||
/// need to show by external request, etc [keyboardIsVisible] or [changeTouchMode]
|
final bool keyboardIsVisible;
|
||||||
final bool requestShow;
|
final bool showGestureHelp;
|
||||||
|
|
||||||
KeyHelpTools({required this.requestShow});
|
/// need to show by external request, etc [keyboardIsVisible] or [changeTouchMode]
|
||||||
|
bool get requestShow => keyboardIsVisible || showGestureHelp;
|
||||||
|
|
||||||
|
KeyHelpTools(
|
||||||
|
{required this.keyboardIsVisible, required this.showGestureHelp});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<KeyHelpTools> createState() => _KeyHelpToolsState();
|
State<KeyHelpTools> createState() => _KeyHelpToolsState();
|
||||||
@@ -811,7 +864,8 @@ class _KeyHelpToolsState extends State<KeyHelpTools> {
|
|||||||
final size = renderObject.size;
|
final size = renderObject.size;
|
||||||
Offset pos = renderObject.localToGlobal(Offset.zero);
|
Offset pos = renderObject.localToGlobal(Offset.zero);
|
||||||
gFFI.cursorModel.keyHelpToolsVisibilityChanged(
|
gFFI.cursorModel.keyHelpToolsVisibilityChanged(
|
||||||
Rect.fromLTWH(pos.dx, pos.dy, size.width, size.height));
|
Rect.fromLTWH(pos.dx, pos.dy, size.width, size.height),
|
||||||
|
widget.keyboardIsVisible);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -823,13 +877,16 @@ class _KeyHelpToolsState extends State<KeyHelpTools> {
|
|||||||
inputModel.command;
|
inputModel.command;
|
||||||
|
|
||||||
if (!_pin && !hasModifierOn && !widget.requestShow) {
|
if (!_pin && !hasModifierOn && !widget.requestShow) {
|
||||||
gFFI.cursorModel.keyHelpToolsVisibilityChanged(null);
|
gFFI.cursorModel
|
||||||
|
.keyHelpToolsVisibilityChanged(null, widget.keyboardIsVisible);
|
||||||
return Offstage();
|
return Offstage();
|
||||||
}
|
}
|
||||||
final size = MediaQuery.of(context).size;
|
final size = MediaQuery.of(context).size;
|
||||||
|
|
||||||
final pi = gFFI.ffiModel.pi;
|
final pi = gFFI.ffiModel.pi;
|
||||||
final isMac = pi.platform == kPeerPlatformMacOS;
|
final isMac = pi.platform == kPeerPlatformMacOS;
|
||||||
|
final isWin = pi.platform == kPeerPlatformWindows;
|
||||||
|
final isLinux = pi.platform == kPeerPlatformLinux;
|
||||||
final modifiers = <Widget>[
|
final modifiers = <Widget>[
|
||||||
wrap('Ctrl ', () {
|
wrap('Ctrl ', () {
|
||||||
setState(() => inputModel.ctrl = !inputModel.ctrl);
|
setState(() => inputModel.ctrl = !inputModel.ctrl);
|
||||||
@@ -910,6 +967,28 @@ class _KeyHelpToolsState extends State<KeyHelpTools> {
|
|||||||
wrap('PgDn', () {
|
wrap('PgDn', () {
|
||||||
inputModel.inputKey('VK_NEXT');
|
inputModel.inputKey('VK_NEXT');
|
||||||
}),
|
}),
|
||||||
|
// to-do: support PrtScr on Mac
|
||||||
|
if (isWin || isLinux)
|
||||||
|
wrap('PrtScr', () {
|
||||||
|
inputModel.inputKey('VK_SNAPSHOT');
|
||||||
|
}),
|
||||||
|
if (isWin || isLinux)
|
||||||
|
wrap('ScrollLock', () {
|
||||||
|
inputModel.inputKey('VK_SCROLL');
|
||||||
|
}),
|
||||||
|
if (isWin || isLinux)
|
||||||
|
wrap('Pause', () {
|
||||||
|
inputModel.inputKey('VK_PAUSE');
|
||||||
|
}),
|
||||||
|
if (isWin || isLinux)
|
||||||
|
// Maybe it's better to call it "Menu"
|
||||||
|
// https://en.wikipedia.org/wiki/Menu_key
|
||||||
|
wrap('Menu', () {
|
||||||
|
inputModel.inputKey('Apps');
|
||||||
|
}),
|
||||||
|
wrap('Enter', () {
|
||||||
|
inputModel.inputKey('VK_ENTER');
|
||||||
|
}),
|
||||||
SizedBox(width: 9999),
|
SizedBox(width: 9999),
|
||||||
wrap('', () {
|
wrap('', () {
|
||||||
inputModel.inputKey('VK_LEFT');
|
inputModel.inputKey('VK_LEFT');
|
||||||
@@ -960,11 +1039,11 @@ class ImagePaint extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final m = Provider.of<ImageModel>(context);
|
final m = Provider.of<ImageModel>(context);
|
||||||
final c = Provider.of<CanvasModel>(context);
|
final c = Provider.of<CanvasModel>(context);
|
||||||
final adjust = gFFI.cursorModel.adjustForKeyboard();
|
|
||||||
var s = c.scale;
|
var s = c.scale;
|
||||||
|
final adjust = c.getAdjustY();
|
||||||
return CustomPaint(
|
return CustomPaint(
|
||||||
painter: ImagePainter(
|
painter: ImagePainter(
|
||||||
image: m.image, x: c.x / s, y: (c.y - adjust) / s, scale: s),
|
image: m.image, x: c.x / s, y: (c.y + adjust) / s, scale: s),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -978,7 +1057,6 @@ class CursorPaint extends StatelessWidget {
|
|||||||
final m = Provider.of<CursorModel>(context);
|
final m = Provider.of<CursorModel>(context);
|
||||||
final c = Provider.of<CanvasModel>(context);
|
final c = Provider.of<CanvasModel>(context);
|
||||||
final ffiModel = Provider.of<FfiModel>(context);
|
final ffiModel = Provider.of<FfiModel>(context);
|
||||||
final adjust = gFFI.cursorModel.adjustForKeyboard();
|
|
||||||
final s = c.scale;
|
final s = c.scale;
|
||||||
double hotx = m.hotx;
|
double hotx = m.hotx;
|
||||||
double hoty = m.hoty;
|
double hoty = m.hoty;
|
||||||
@@ -1010,11 +1088,12 @@ class CursorPaint extends StatelessWidget {
|
|||||||
factor = s / mins;
|
factor = s / mins;
|
||||||
}
|
}
|
||||||
final s2 = s < mins ? mins : s;
|
final s2 = s < mins ? mins : s;
|
||||||
|
final adjust = c.getAdjustY();
|
||||||
return CustomPaint(
|
return CustomPaint(
|
||||||
painter: ImagePainter(
|
painter: ImagePainter(
|
||||||
image: image,
|
image: image,
|
||||||
x: (m.x - hotx) * factor + c.x / s2,
|
x: (m.x - hotx) * factor + c.x / s2,
|
||||||
y: (m.y - hoty) * factor + (c.y - adjust) / s2,
|
y: (m.y - hoty) * factor + (c.y + adjust) / s2,
|
||||||
scale: s2),
|
scale: s2),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1217,7 +1296,9 @@ void showOptions(
|
|||||||
toggles +
|
toggles +
|
||||||
[privacyModeWidget]),
|
[privacyModeWidget]),
|
||||||
);
|
);
|
||||||
}, clickMaskDismiss: true, backDismiss: true);
|
}, clickMaskDismiss: true, backDismiss: true).then((value) {
|
||||||
|
_disableAndroidSoftKeyboard();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
TTextMenu? getVirtualDisplayMenu(FFI ffi, String id) {
|
TTextMenu? getVirtualDisplayMenu(FFI ffi, String id) {
|
||||||
@@ -1236,7 +1317,9 @@ TTextMenu? getVirtualDisplayMenu(FFI ffi, String id) {
|
|||||||
children: children,
|
children: children,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}, clickMaskDismiss: true, backDismiss: true);
|
}, clickMaskDismiss: true, backDismiss: true).then((value) {
|
||||||
|
_disableAndroidSoftKeyboard();
|
||||||
|
});
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1278,7 +1361,9 @@ TTextMenu? getResolutionMenu(FFI ffi, String id) {
|
|||||||
children: children,
|
children: children,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}, clickMaskDismiss: true, backDismiss: true);
|
}, clickMaskDismiss: true, backDismiss: true).then((value) {
|
||||||
|
_disableAndroidSoftKeyboard();
|
||||||
|
});
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -446,7 +446,6 @@ class ServerInfo extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final isPermanent = model.verificationMethod == kUsePermanentPassword;
|
|
||||||
final serverModel = Provider.of<ServerModel>(context);
|
final serverModel = Provider.of<ServerModel>(context);
|
||||||
|
|
||||||
const Color colorPositive = Colors.green;
|
const Color colorPositive = Colors.green;
|
||||||
@@ -486,6 +485,8 @@ class ServerInfo extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final showOneTime = serverModel.approveMode != 'click' &&
|
||||||
|
serverModel.verificationMethod != kUsePermanentPassword;
|
||||||
return PaddingCard(
|
return PaddingCard(
|
||||||
title: translate('Your Device'),
|
title: translate('Your Device'),
|
||||||
child: Column(
|
child: Column(
|
||||||
@@ -523,10 +524,10 @@ class ServerInfo extends StatelessWidget {
|
|||||||
]),
|
]),
|
||||||
Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [
|
Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [
|
||||||
Text(
|
Text(
|
||||||
isPermanent ? '-' : model.serverPasswd.value.text,
|
!showOneTime ? '-' : model.serverPasswd.value.text,
|
||||||
style: textStyleValue,
|
style: textStyleValue,
|
||||||
),
|
),
|
||||||
isPermanent
|
!showOneTime
|
||||||
? SizedBox.shrink()
|
? SizedBox.shrink()
|
||||||
: Row(children: [
|
: Row(children: [
|
||||||
IconButton(
|
IconButton(
|
||||||
@@ -595,7 +596,9 @@ class _PermissionCheckerState extends State<PermissionChecker> {
|
|||||||
translate("android_version_audio_tip"),
|
translate("android_version_audio_tip"),
|
||||||
style: const TextStyle(color: MyTheme.darkGray),
|
style: const TextStyle(color: MyTheme.darkGray),
|
||||||
))
|
))
|
||||||
])
|
]),
|
||||||
|
PermissionRow(translate("Enable clipboard"), serverModel.clipboardOk,
|
||||||
|
serverModel.toggleClipboard),
|
||||||
]));
|
]));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import 'dart:typed_data';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_hbb/common/widgets/setting_widgets.dart';
|
import 'package:flutter_hbb/common/widgets/setting_widgets.dart';
|
||||||
import 'package:flutter_hbb/desktop/pages/desktop_setting_page.dart';
|
import 'package:flutter_hbb/desktop/pages/desktop_setting_page.dart';
|
||||||
|
import 'package:flutter_hbb/models/state_model.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:settings_ui/settings_ui.dart';
|
import 'package:settings_ui/settings_ui.dart';
|
||||||
@@ -70,6 +71,7 @@ class _SettingsState extends State<SettingsPage> with WidgetsBindingObserver {
|
|||||||
false; //androidVersion >= 26; // remove because not work on every device
|
false; //androidVersion >= 26; // remove because not work on every device
|
||||||
var _ignoreBatteryOpt = false;
|
var _ignoreBatteryOpt = false;
|
||||||
var _enableStartOnBoot = false;
|
var _enableStartOnBoot = false;
|
||||||
|
var _checkUpdateOnStartup = false;
|
||||||
var _floatingWindowDisabled = false;
|
var _floatingWindowDisabled = false;
|
||||||
var _keepScreenOn = KeepScreenOn.duringControlled; // relay on floating window
|
var _keepScreenOn = KeepScreenOn.duringControlled; // relay on floating window
|
||||||
var _enableAbr = false;
|
var _enableAbr = false;
|
||||||
@@ -154,6 +156,13 @@ class _SettingsState extends State<SettingsPage> with WidgetsBindingObserver {
|
|||||||
_enableStartOnBoot = enableStartOnBoot;
|
_enableStartOnBoot = enableStartOnBoot;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var checkUpdateOnStartup =
|
||||||
|
mainGetLocalBoolOptionSync(kOptionEnableCheckUpdate);
|
||||||
|
if (checkUpdateOnStartup != _checkUpdateOnStartup) {
|
||||||
|
update = true;
|
||||||
|
_checkUpdateOnStartup = checkUpdateOnStartup;
|
||||||
|
}
|
||||||
|
|
||||||
var floatingWindowDisabled =
|
var floatingWindowDisabled =
|
||||||
bind.mainGetLocalOption(key: kOptionDisableFloatingWindow) == "Y" ||
|
bind.mainGetLocalOption(key: kOptionDisableFloatingWindow) == "Y" ||
|
||||||
!await AndroidPermissionManager.check(kSystemAlertWindow);
|
!await AndroidPermissionManager.check(kSystemAlertWindow);
|
||||||
@@ -552,6 +561,22 @@ class _SettingsState extends State<SettingsPage> with WidgetsBindingObserver {
|
|||||||
gFFI.invokeMethod(AndroidChannel.kSetStartOnBootOpt, toValue);
|
gFFI.invokeMethod(AndroidChannel.kSetStartOnBootOpt, toValue);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
if (!bind.isCustomClient()) {
|
||||||
|
enhancementsTiles.add(
|
||||||
|
SettingsTile.switchTile(
|
||||||
|
initialValue: _checkUpdateOnStartup,
|
||||||
|
title:
|
||||||
|
Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||||||
|
Text(translate('Check for software update on startup')),
|
||||||
|
]),
|
||||||
|
onToggle: (bool toValue) async {
|
||||||
|
await mainSetLocalBoolOption(kOptionEnableCheckUpdate, toValue);
|
||||||
|
setState(() => _checkUpdateOnStartup = toValue);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
onFloatingWindowChanged(bool toValue) async {
|
onFloatingWindowChanged(bool toValue) async {
|
||||||
if (toValue) {
|
if (toValue) {
|
||||||
if (!await AndroidPermissionManager.check(kSystemAlertWindow)) {
|
if (!await AndroidPermissionManager.check(kSystemAlertWindow)) {
|
||||||
@@ -757,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(
|
||||||
@@ -828,11 +851,6 @@ class _SettingsState extends State<SettingsPage> with WidgetsBindingObserver {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void showServerSettings(OverlayDialogManager dialogManager) async {
|
|
||||||
Map<String, dynamic> options = jsonDecode(await bind.mainGetOptions());
|
|
||||||
showServerSettingsWithValue(ServerConfig.fromOptions(options), dialogManager);
|
|
||||||
}
|
|
||||||
|
|
||||||
void showLanguageSettings(OverlayDialogManager dialogManager) async {
|
void showLanguageSettings(OverlayDialogManager dialogManager) async {
|
||||||
try {
|
try {
|
||||||
final langs = json.decode(await bind.mainGetLangs()) as List<dynamic>;
|
final langs = json.decode(await bind.mainGetLangs()) as List<dynamic>;
|
||||||
@@ -908,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),
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
import 'dart:convert';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_hbb/common/widgets/setting_widgets.dart';
|
import 'package:flutter_hbb/common/widgets/setting_widgets.dart';
|
||||||
import 'package:flutter_hbb/common/widgets/toolbar.dart';
|
import 'package:flutter_hbb/common/widgets/toolbar.dart';
|
||||||
@@ -65,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,
|
||||||
@@ -84,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,
|
||||||
@@ -146,6 +147,16 @@ void setTemporaryPasswordLengthDialog(
|
|||||||
}, backDismiss: true, clickMaskDismiss: true);
|
}, backDismiss: true, clickMaskDismiss: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void showServerSettings(OverlayDialogManager dialogManager) async {
|
||||||
|
Map<String, dynamic> options = {};
|
||||||
|
try {
|
||||||
|
options = jsonDecode(await bind.mainGetOptions());
|
||||||
|
} catch (e) {
|
||||||
|
print("Invalid server config: $e");
|
||||||
|
}
|
||||||
|
showServerSettingsWithValue(ServerConfig.fromOptions(options), dialogManager);
|
||||||
|
}
|
||||||
|
|
||||||
void showServerSettingsWithValue(
|
void showServerSettingsWithValue(
|
||||||
ServerConfig serverConfig, OverlayDialogManager dialogManager) async {
|
ServerConfig serverConfig, OverlayDialogManager dialogManager) async {
|
||||||
var isInProgress = false;
|
var isInProgress = false;
|
||||||
@@ -184,6 +195,43 @@ void showServerSettingsWithValue(
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget buildField(
|
||||||
|
String label, TextEditingController controller, String errorMsg,
|
||||||
|
{String? Function(String?)? validator, bool autofocus = false}) {
|
||||||
|
if (isDesktop || isWeb) {
|
||||||
|
return Row(
|
||||||
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
width: 120,
|
||||||
|
child: Text(label),
|
||||||
|
),
|
||||||
|
SizedBox(width: 8),
|
||||||
|
Expanded(
|
||||||
|
child: TextFormField(
|
||||||
|
controller: controller,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
errorText: errorMsg.isEmpty ? null : errorMsg,
|
||||||
|
contentPadding:
|
||||||
|
EdgeInsets.symmetric(horizontal: 8, vertical: 12),
|
||||||
|
),
|
||||||
|
validator: validator,
|
||||||
|
autofocus: autofocus,
|
||||||
|
).workaroundFreezeLinuxMint(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return TextFormField(
|
||||||
|
controller: controller,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: label,
|
||||||
|
errorText: errorMsg.isEmpty ? null : errorMsg,
|
||||||
|
),
|
||||||
|
validator: validator,
|
||||||
|
).workaroundFreezeLinuxMint();
|
||||||
|
}
|
||||||
|
|
||||||
return CustomAlertDialog(
|
return CustomAlertDialog(
|
||||||
title: Row(
|
title: Row(
|
||||||
children: [
|
children: [
|
||||||
@@ -191,56 +239,45 @@ void showServerSettingsWithValue(
|
|||||||
...ServerConfigImportExportWidgets(controllers, errMsgs),
|
...ServerConfigImportExportWidgets(controllers, errMsgs),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
content: Form(
|
content: ConstrainedBox(
|
||||||
|
constraints: const BoxConstraints(minWidth: 500),
|
||||||
|
child: Form(
|
||||||
child: Obx(() => Column(
|
child: Obx(() => Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: <Widget>[
|
children: [
|
||||||
TextFormField(
|
buildField(translate('ID Server'), idCtrl, idServerMsg.value,
|
||||||
controller: idCtrl,
|
autofocus: true),
|
||||||
decoration: InputDecoration(
|
SizedBox(height: 8),
|
||||||
labelText: translate('ID Server'),
|
if (!isIOS && !isWeb) ...[
|
||||||
errorText: idServerMsg.value.isEmpty
|
buildField(translate('Relay Server'), relayCtrl,
|
||||||
? null
|
relayServerMsg.value),
|
||||||
: idServerMsg.value),
|
SizedBox(height: 8),
|
||||||
)
|
],
|
||||||
] +
|
buildField(
|
||||||
[
|
translate('API Server'),
|
||||||
if (isAndroid)
|
apiCtrl,
|
||||||
TextFormField(
|
apiServerMsg.value,
|
||||||
controller: relayCtrl,
|
validator: (v) {
|
||||||
decoration: InputDecoration(
|
if (v != null && v.isNotEmpty) {
|
||||||
labelText: translate('Relay Server'),
|
if (!(v.startsWith('http://') ||
|
||||||
errorText: relayServerMsg.value.isEmpty
|
v.startsWith("https://"))) {
|
||||||
? null
|
return translate("invalid_http");
|
||||||
: relayServerMsg.value),
|
|
||||||
)
|
|
||||||
] +
|
|
||||||
[
|
|
||||||
TextFormField(
|
|
||||||
controller: apiCtrl,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: translate('API Server'),
|
|
||||||
),
|
|
||||||
autovalidateMode: AutovalidateMode.onUserInteraction,
|
|
||||||
validator: (v) {
|
|
||||||
if (v != null && v.isNotEmpty) {
|
|
||||||
if (!(v.startsWith('http://') ||
|
|
||||||
v.startsWith("https://"))) {
|
|
||||||
return translate("invalid_http");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return null;
|
}
|
||||||
},
|
return null;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
SizedBox(height: 8),
|
||||||
|
buildField('Key', keyCtrl, ''),
|
||||||
|
if (isInProgress)
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsets.only(top: 8),
|
||||||
|
child: LinearProgressIndicator(),
|
||||||
),
|
),
|
||||||
TextFormField(
|
],
|
||||||
controller: keyCtrl,
|
)),
|
||||||
decoration: InputDecoration(
|
),
|
||||||
labelText: 'Key',
|
),
|
||||||
),
|
|
||||||
),
|
|
||||||
// NOT use Offstage to wrap LinearProgressIndicator
|
|
||||||
if (isInProgress) const LinearProgressIndicator(),
|
|
||||||
]))),
|
|
||||||
actions: [
|
actions: [
|
||||||
dialogButton('Cancel', onPressed: () {
|
dialogButton('Cancel', onPressed: () {
|
||||||
close();
|
close();
|
||||||
|
|||||||
@@ -33,6 +33,8 @@ bool filterAbTagByIntersection() {
|
|||||||
const _personalAddressBookName = "My address book";
|
const _personalAddressBookName = "My address book";
|
||||||
const _legacyAddressBookName = "Legacy address book";
|
const _legacyAddressBookName = "Legacy address book";
|
||||||
|
|
||||||
|
const kUntagged = "Untagged";
|
||||||
|
|
||||||
enum ForcePullAb {
|
enum ForcePullAb {
|
||||||
listAndCurrent,
|
listAndCurrent,
|
||||||
current,
|
current,
|
||||||
@@ -424,6 +426,7 @@ class AbModel {
|
|||||||
|
|
||||||
// #region tags
|
// #region tags
|
||||||
Future<bool> addTags(List<String> tagList) async {
|
Future<bool> addTags(List<String> tagList) async {
|
||||||
|
tagList.removeWhere((e) => e == kUntagged);
|
||||||
final ret = await current.addTags(tagList, {});
|
final ret = await current.addTags(tagList, {});
|
||||||
await pullNonLegacyAfterChange();
|
await pullNonLegacyAfterChange();
|
||||||
_saveCache();
|
_saveCache();
|
||||||
@@ -645,6 +648,9 @@ class AbModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Color getCurrentAbTagColor(String tag) {
|
Color getCurrentAbTagColor(String tag) {
|
||||||
|
if (tag == kUntagged) {
|
||||||
|
return MyTheme.accent;
|
||||||
|
}
|
||||||
int? colorValue = current.tagColors[tag];
|
int? colorValue = current.tagColors[tag];
|
||||||
if (colorValue != null) {
|
if (colorValue != null) {
|
||||||
return Color(colorValue);
|
return Color(colorValue);
|
||||||
|
|||||||
@@ -100,6 +100,10 @@ class FileModel {
|
|||||||
fileFetcher.tryCompleteTask(evt['value'], evt['is_local']);
|
fileFetcher.tryCompleteTask(evt['value'], evt['is_local']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void receiveEmptyDirs(Map<String, dynamic> evt) {
|
||||||
|
fileFetcher.tryCompleteEmptyDirsTask(evt['value'], evt['is_local']);
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> postOverrideFileConfirm(Map<String, dynamic> evt) async {
|
Future<void> postOverrideFileConfirm(Map<String, dynamic> evt) async {
|
||||||
evtLoop.pushEvent(
|
evtLoop.pushEvent(
|
||||||
_FileDialogEvent(WeakReference(this), FileDialogType.overwrite, evt));
|
_FileDialogEvent(WeakReference(this), FileDialogType.overwrite, evt));
|
||||||
@@ -257,6 +261,27 @@ class FileModel {
|
|||||||
debugPrint("Failed to decode onSelectedFiles: $e");
|
debugPrint("Failed to decode onSelectedFiles: $e");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void sendEmptyDirs(dynamic obj) {
|
||||||
|
late final List<dynamic> emptyDirs;
|
||||||
|
try {
|
||||||
|
emptyDirs = jsonDecode(obj['dirs'] as String);
|
||||||
|
} catch (e) {
|
||||||
|
debugPrint("Failed to decode sendEmptyDirs: $e");
|
||||||
|
}
|
||||||
|
final otherSideData = remoteController.directoryData();
|
||||||
|
final toPath = otherSideData.directory.path;
|
||||||
|
final isPeerWindows = otherSideData.options.isWindows;
|
||||||
|
|
||||||
|
final isLocalWindows = isWindows || isWebOnWindows;
|
||||||
|
for (var dir in emptyDirs) {
|
||||||
|
if (isLocalWindows != isPeerWindows) {
|
||||||
|
dir = PathUtil.convert(dir, isLocalWindows, isPeerWindows);
|
||||||
|
}
|
||||||
|
var peerPath = PathUtil.join(toPath, dir, isPeerWindows);
|
||||||
|
remoteController.createDirWithRemote(peerPath, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class DirectoryData {
|
class DirectoryData {
|
||||||
@@ -470,7 +495,8 @@ class FileController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// sendFiles from current side (FileController.isLocal) to other side (SelectedItems).
|
/// sendFiles from current side (FileController.isLocal) to other side (SelectedItems).
|
||||||
void sendFiles(SelectedItems items, DirectoryData otherSideData) {
|
Future<void> sendFiles(
|
||||||
|
SelectedItems items, DirectoryData otherSideData) async {
|
||||||
/// ignore wrong items side status
|
/// ignore wrong items side status
|
||||||
if (items.isLocal != isLocal) {
|
if (items.isLocal != isLocal) {
|
||||||
return;
|
return;
|
||||||
@@ -496,6 +522,43 @@ class FileController {
|
|||||||
debugPrint(
|
debugPrint(
|
||||||
"path: ${from.path}, toPath: $toPath, to: ${PathUtil.join(toPath, from.name, isWindows)}");
|
"path: ${from.path}, toPath: $toPath, to: ${PathUtil.join(toPath, from.name, isWindows)}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isWeb ||
|
||||||
|
(!isLocal &&
|
||||||
|
versionCmp(rootState.target!.ffiModel.pi.version, '1.3.3') < 0)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final List<Entry> entrys = items.items.toList();
|
||||||
|
var isRemote = isLocal == true ? true : false;
|
||||||
|
|
||||||
|
await Future.forEach(entrys, (Entry item) async {
|
||||||
|
if (!item.isDirectory) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final List<String> paths = [];
|
||||||
|
|
||||||
|
final emptyDirs =
|
||||||
|
await fileFetcher.readEmptyDirs(item.path, isLocal, showHidden);
|
||||||
|
|
||||||
|
if (emptyDirs.isEmpty) {
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
for (var dir in emptyDirs) {
|
||||||
|
paths.add(dir.path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final dirs = paths.map((path) {
|
||||||
|
return PathUtil.getOtherSidePath(directory.value.path, path,
|
||||||
|
options.value.isWindows, toPath, isWindows);
|
||||||
|
});
|
||||||
|
|
||||||
|
for (var dir in dirs) {
|
||||||
|
createDirWithRemote(dir, isRemote);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
bool _removeCheckboxRemember = false;
|
bool _removeCheckboxRemember = false;
|
||||||
@@ -689,12 +752,16 @@ class FileController {
|
|||||||
sessionId: sessionId, actId: actId, path: path, isRemote: !isLocal);
|
sessionId: sessionId, actId: actId, path: path, isRemote: !isLocal);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> createDir(String path) async {
|
Future<void> createDirWithRemote(String path, bool isRemote) async {
|
||||||
bind.sessionCreateDir(
|
bind.sessionCreateDir(
|
||||||
sessionId: sessionId,
|
sessionId: sessionId,
|
||||||
actId: JobController.jobID.next(),
|
actId: JobController.jobID.next(),
|
||||||
path: path,
|
path: path,
|
||||||
isRemote: !isLocal);
|
isRemote: isRemote);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> createDir(String path) async {
|
||||||
|
await createDirWithRemote(path, !isLocal);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> renameAction(Entry item, bool isLocal) async {
|
Future<void> renameAction(Entry item, bool isLocal) async {
|
||||||
@@ -1064,6 +1131,7 @@ class JobResultListener<T> {
|
|||||||
class FileFetcher {
|
class FileFetcher {
|
||||||
// Map<String,Completer<FileDirectory>> localTasks = {}; // now we only use read local dir sync
|
// Map<String,Completer<FileDirectory>> localTasks = {}; // now we only use read local dir sync
|
||||||
Map<String, Completer<FileDirectory>> remoteTasks = {};
|
Map<String, Completer<FileDirectory>> remoteTasks = {};
|
||||||
|
Map<String, Completer<List<FileDirectory>>> remoteEmptyDirsTasks = {};
|
||||||
Map<int, Completer<FileDirectory>> readRecursiveTasks = {};
|
Map<int, Completer<FileDirectory>> readRecursiveTasks = {};
|
||||||
|
|
||||||
final GetSessionID getSessionID;
|
final GetSessionID getSessionID;
|
||||||
@@ -1071,6 +1139,24 @@ class FileFetcher {
|
|||||||
|
|
||||||
FileFetcher(this.getSessionID);
|
FileFetcher(this.getSessionID);
|
||||||
|
|
||||||
|
Future<List<FileDirectory>> registerReadEmptyDirsTask(
|
||||||
|
bool isLocal, String path) {
|
||||||
|
// final jobs = isLocal?localJobs:remoteJobs; // maybe we will use read local dir async later
|
||||||
|
final tasks = remoteEmptyDirsTasks; // bypass now
|
||||||
|
if (tasks.containsKey(path)) {
|
||||||
|
throw "Failed to registerReadEmptyDirsTask, already have same read job";
|
||||||
|
}
|
||||||
|
final c = Completer<List<FileDirectory>>();
|
||||||
|
tasks[path] = c;
|
||||||
|
|
||||||
|
Timer(Duration(seconds: 2), () {
|
||||||
|
tasks.remove(path);
|
||||||
|
if (c.isCompleted) return;
|
||||||
|
c.completeError("Failed to read empty dirs, timeout");
|
||||||
|
});
|
||||||
|
return c.future;
|
||||||
|
}
|
||||||
|
|
||||||
Future<FileDirectory> registerReadTask(bool isLocal, String path) {
|
Future<FileDirectory> registerReadTask(bool isLocal, String path) {
|
||||||
// final jobs = isLocal?localJobs:remoteJobs; // maybe we will use read local dir async later
|
// final jobs = isLocal?localJobs:remoteJobs; // maybe we will use read local dir async later
|
||||||
final tasks = remoteTasks; // bypass now
|
final tasks = remoteTasks; // bypass now
|
||||||
@@ -1104,6 +1190,25 @@ class FileFetcher {
|
|||||||
return c.future;
|
return c.future;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tryCompleteEmptyDirsTask(String? msg, String? isLocalStr) {
|
||||||
|
if (msg == null || isLocalStr == null) return;
|
||||||
|
late final Map<String, Completer<List<FileDirectory>>> tasks;
|
||||||
|
try {
|
||||||
|
final map = jsonDecode(msg);
|
||||||
|
final String path = map["path"];
|
||||||
|
final List<dynamic> fdJsons = map["empty_dirs"];
|
||||||
|
final List<FileDirectory> fds =
|
||||||
|
fdJsons.map((fdJson) => FileDirectory.fromJson(fdJson)).toList();
|
||||||
|
|
||||||
|
tasks = remoteEmptyDirsTasks;
|
||||||
|
final completer = tasks.remove(path);
|
||||||
|
|
||||||
|
completer?.complete(fds);
|
||||||
|
} catch (e) {
|
||||||
|
debugPrint("tryCompleteJob err: $e");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
tryCompleteTask(String? msg, String? isLocalStr) {
|
tryCompleteTask(String? msg, String? isLocalStr) {
|
||||||
if (msg == null || isLocalStr == null) return;
|
if (msg == null || isLocalStr == null) return;
|
||||||
late final Map<Object, Completer<FileDirectory>> tasks;
|
late final Map<Object, Completer<FileDirectory>> tasks;
|
||||||
@@ -1127,6 +1232,28 @@ class FileFetcher {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<List<FileDirectory>> readEmptyDirs(
|
||||||
|
String path, bool isLocal, bool showHidden) async {
|
||||||
|
try {
|
||||||
|
if (isLocal) {
|
||||||
|
final res = await bind.sessionReadLocalEmptyDirsRecursiveSync(
|
||||||
|
sessionId: sessionId, path: path, includeHidden: showHidden);
|
||||||
|
|
||||||
|
final List<dynamic> fdJsons = jsonDecode(res);
|
||||||
|
|
||||||
|
final List<FileDirectory> fds =
|
||||||
|
fdJsons.map((fdJson) => FileDirectory.fromJson(fdJson)).toList();
|
||||||
|
return fds;
|
||||||
|
} else {
|
||||||
|
await bind.sessionReadRemoteEmptyDirsRecursiveSync(
|
||||||
|
sessionId: sessionId, path: path, includeHidden: showHidden);
|
||||||
|
return registerReadEmptyDirsTask(isLocal, path);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
return Future.error(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<FileDirectory> fetchDirectory(
|
Future<FileDirectory> fetchDirectory(
|
||||||
String path, bool isLocal, bool showHidden) async {
|
String path, bool isLocal, bool showHidden) async {
|
||||||
try {
|
try {
|
||||||
@@ -1373,6 +1500,24 @@ class PathUtil {
|
|||||||
static final windowsContext = path.Context(style: path.Style.windows);
|
static final windowsContext = path.Context(style: path.Style.windows);
|
||||||
static final posixContext = path.Context(style: path.Style.posix);
|
static final posixContext = path.Context(style: path.Style.posix);
|
||||||
|
|
||||||
|
static String getOtherSidePath(String mainRootPath, String mainPath,
|
||||||
|
bool isMainWindows, String otherRootPath, bool isOtherWindows) {
|
||||||
|
final mainPathUtil = isMainWindows ? windowsContext : posixContext;
|
||||||
|
final relativePath = mainPathUtil.relative(mainPath, from: mainRootPath);
|
||||||
|
|
||||||
|
final names = mainPathUtil.split(relativePath);
|
||||||
|
|
||||||
|
final otherPathUtil = isOtherWindows ? windowsContext : posixContext;
|
||||||
|
|
||||||
|
String path = otherRootPath;
|
||||||
|
|
||||||
|
for (var name in names) {
|
||||||
|
path = otherPathUtil.join(path, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
static String join(String path1, String path2, bool isWindows) {
|
static String join(String path1, String path2, bool isWindows) {
|
||||||
final pathUtil = isWindows ? windowsContext : posixContext;
|
final pathUtil = isWindows ? windowsContext : posixContext;
|
||||||
return pathUtil.join(path1, path2);
|
return pathUtil.join(path1, path2);
|
||||||
@@ -1383,6 +1528,12 @@ class PathUtil {
|
|||||||
return pathUtil.split(path);
|
return pathUtil.split(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static String convert(String path, bool isMainWindows, bool isOtherWindows) {
|
||||||
|
final mainPathUtil = isMainWindows ? windowsContext : posixContext;
|
||||||
|
final otherPathUtil = isOtherWindows ? windowsContext : posixContext;
|
||||||
|
return otherPathUtil.joinAll(mainPathUtil.split(path));
|
||||||
|
}
|
||||||
|
|
||||||
static String dirname(String path, bool isWindows) {
|
static String dirname(String path, bool isWindows) {
|
||||||
final pathUtil = isWindows ? windowsContext : posixContext;
|
final pathUtil = isWindows ? windowsContext : posixContext;
|
||||||
return pathUtil.dirname(path);
|
return pathUtil.dirname(path);
|
||||||
|
|||||||
@@ -544,7 +544,40 @@ class InputModel {
|
|||||||
handleKeyDownEventModifiers(e);
|
handleKeyDownEventModifiers(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isMobile || (isDesktop || isWebDesktop) && keyboardMode == kKeyMapMode) {
|
bool isMobileAndMapMode = false;
|
||||||
|
if (isMobile) {
|
||||||
|
// Do not use map mode if mobile -> Android. Android does not support map mode for now.
|
||||||
|
// Because simulating the physical key events(uhid) which requires root permission is not supported.
|
||||||
|
if (peerPlatform != kPeerPlatformAndroid) {
|
||||||
|
if (isIOS) {
|
||||||
|
isMobileAndMapMode = true;
|
||||||
|
} else {
|
||||||
|
// The physicalKey.usbHidUsage may be not correct for soft keyboard on Android.
|
||||||
|
// iOS does not have this issue.
|
||||||
|
// 1. Open the soft keyboard on Android
|
||||||
|
// 2. Switch to input method like zh/ko/ja
|
||||||
|
// 3. Click Backspace and Enter on the soft keyboard or physical keyboard
|
||||||
|
// 4. The physicalKey.usbHidUsage is not correct.
|
||||||
|
// PhysicalKeyboardKey#8ac83(usbHidUsage: "0x1100000042", debugName: "Key with ID 0x1100000042")
|
||||||
|
// LogicalKeyboardKey#2604c(keyId: "0x10000000d", keyLabel: "Enter", debugName: "Enter")
|
||||||
|
//
|
||||||
|
// The correct PhysicalKeyboardKey should be
|
||||||
|
// PhysicalKeyboardKey#e14a9(usbHidUsage: "0x00070028", debugName: "Enter")
|
||||||
|
// https://github.com/flutter/flutter/issues/157771
|
||||||
|
// We cannot use the debugName to determine the key is correct or not, because it's null in release mode.
|
||||||
|
// The normal `usbHidUsage` for keyboard shoud be between [0x00000010, 0x000c029f]
|
||||||
|
// https://github.com/flutter/flutter/blob/c051b69e2a2224300e20d93dbd15f4b91e8844d1/packages/flutter/lib/src/services/keyboard_key.g.dart#L5332 - 5600
|
||||||
|
final isNormalHsbHidUsage = (e.physicalKey.usbHidUsage >> 20) == 0;
|
||||||
|
isMobileAndMapMode = isNormalHsbHidUsage &&
|
||||||
|
// No need to check `!['Backspace', 'Enter'].contains(e.logicalKey.keyLabel)`
|
||||||
|
// But we still add it for more reliability.
|
||||||
|
!['Backspace', 'Enter'].contains(e.logicalKey.keyLabel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
final isDesktopAndMapMode =
|
||||||
|
isDesktop || (isWebDesktop && keyboardMode == kKeyMapMode);
|
||||||
|
if (isMobileAndMapMode || isDesktopAndMapMode) {
|
||||||
// FIXME: e.character is wrong for dead keys, eg: ^ in de
|
// FIXME: e.character is wrong for dead keys, eg: ^ in de
|
||||||
newKeyboardMode(
|
newKeyboardMode(
|
||||||
e.character ?? '',
|
e.character ?? '',
|
||||||
@@ -735,22 +768,22 @@ class InputModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Send a mouse tap event(down and up).
|
/// Send a mouse tap event(down and up).
|
||||||
void tap(MouseButtons button) {
|
Future<void> tap(MouseButtons button) async {
|
||||||
sendMouse('down', button);
|
await sendMouse('down', button);
|
||||||
sendMouse('up', button);
|
await sendMouse('up', button);
|
||||||
}
|
}
|
||||||
|
|
||||||
void tapDown(MouseButtons button) {
|
Future<void> tapDown(MouseButtons button) async {
|
||||||
sendMouse('down', button);
|
await sendMouse('down', button);
|
||||||
}
|
}
|
||||||
|
|
||||||
void tapUp(MouseButtons button) {
|
Future<void> tapUp(MouseButtons button) async {
|
||||||
sendMouse('up', button);
|
await sendMouse('up', button);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Send scroll event with scroll distance [y].
|
/// Send scroll event with scroll distance [y].
|
||||||
void scroll(int y) {
|
Future<void> scroll(int y) async {
|
||||||
bind.sessionSendMouse(
|
await bind.sessionSendMouse(
|
||||||
sessionId: sessionId,
|
sessionId: sessionId,
|
||||||
msg: json
|
msg: json
|
||||||
.encode(modify({'id': id, 'type': 'wheel', 'y': y.toString()})));
|
.encode(modify({'id': id, 'type': 'wheel', 'y': y.toString()})));
|
||||||
@@ -771,9 +804,9 @@ class InputModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Send mouse press event.
|
/// Send mouse press event.
|
||||||
void sendMouse(String type, MouseButtons button) {
|
Future<void> sendMouse(String type, MouseButtons button) async {
|
||||||
if (!keyboardPerm) return;
|
if (!keyboardPerm) return;
|
||||||
bind.sessionSendMouse(
|
await bind.sessionSendMouse(
|
||||||
sessionId: sessionId,
|
sessionId: sessionId,
|
||||||
msg: json.encode(modify({'type': type, 'buttons': button.value})));
|
msg: json.encode(modify({'type': type, 'buttons': button.value})));
|
||||||
}
|
}
|
||||||
@@ -797,11 +830,11 @@ class InputModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Send mouse movement event with distance in [x] and [y].
|
/// Send mouse movement event with distance in [x] and [y].
|
||||||
void moveMouse(double x, double y) {
|
Future<void> moveMouse(double x, double y) async {
|
||||||
if (!keyboardPerm) return;
|
if (!keyboardPerm) return;
|
||||||
var x2 = x.toInt();
|
var x2 = x.toInt();
|
||||||
var y2 = y.toInt();
|
var y2 = y.toInt();
|
||||||
bind.sessionSendMouse(
|
await bind.sessionSendMouse(
|
||||||
sessionId: sessionId,
|
sessionId: sessionId,
|
||||||
msg: json.encode(modify({'x': '$x2', 'y': '$y2'})));
|
msg: json.encode(modify({'x': '$x2', 'y': '$y2'})));
|
||||||
}
|
}
|
||||||
@@ -823,7 +856,7 @@ class InputModel {
|
|||||||
_stopFling = true;
|
_stopFling = true;
|
||||||
if (isViewOnly) return;
|
if (isViewOnly) return;
|
||||||
if (peerPlatform == kPeerPlatformAndroid) {
|
if (peerPlatform == kPeerPlatformAndroid) {
|
||||||
handlePointerEvent('touch', 'pan_start', e.position);
|
handlePointerEvent('touch', kMouseEventTypePanStart, e.position);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -866,8 +899,8 @@ class InputModel {
|
|||||||
}
|
}
|
||||||
if (x != 0 || y != 0) {
|
if (x != 0 || y != 0) {
|
||||||
if (peerPlatform == kPeerPlatformAndroid) {
|
if (peerPlatform == kPeerPlatformAndroid) {
|
||||||
handlePointerEvent(
|
handlePointerEvent('touch', kMouseEventTypePanUpdate,
|
||||||
'touch', 'pan_update', Offset(x.toDouble(), y.toDouble()));
|
Offset(x.toDouble(), y.toDouble()));
|
||||||
} else {
|
} else {
|
||||||
bind.sessionSendMouse(
|
bind.sessionSendMouse(
|
||||||
sessionId: sessionId,
|
sessionId: sessionId,
|
||||||
@@ -929,7 +962,7 @@ class InputModel {
|
|||||||
|
|
||||||
void onPointerPanZoomEnd(PointerPanZoomEndEvent e) {
|
void onPointerPanZoomEnd(PointerPanZoomEndEvent e) {
|
||||||
if (peerPlatform == kPeerPlatformAndroid) {
|
if (peerPlatform == kPeerPlatformAndroid) {
|
||||||
handlePointerEvent('touch', 'pan_end', e.position);
|
handlePointerEvent('touch', kMouseEventTypePanEnd, e.position);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1047,7 +1080,7 @@ class InputModel {
|
|||||||
onExit: true,
|
onExit: true,
|
||||||
);
|
);
|
||||||
|
|
||||||
int trySetNearestRange(int v, int min, int max, int n) {
|
static double tryGetNearestRange(double v, double min, double max, double n) {
|
||||||
if (v < min && v >= min - n) {
|
if (v < min && v >= min - n) {
|
||||||
v = min;
|
v = min;
|
||||||
}
|
}
|
||||||
@@ -1087,13 +1120,13 @@ class InputModel {
|
|||||||
// to-do: handle mouse events
|
// to-do: handle mouse events
|
||||||
|
|
||||||
late final dynamic evtValue;
|
late final dynamic evtValue;
|
||||||
if (type == 'pan_update') {
|
if (type == kMouseEventTypePanUpdate) {
|
||||||
evtValue = {
|
evtValue = {
|
||||||
'x': x.toInt(),
|
'x': x.toInt(),
|
||||||
'y': y.toInt(),
|
'y': y.toInt(),
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
final isMoveTypes = ['pan_start', 'pan_end'];
|
final isMoveTypes = [kMouseEventTypePanStart, kMouseEventTypePanEnd];
|
||||||
final pos = handlePointerDevicePos(
|
final pos = handlePointerDevicePos(
|
||||||
kPointerEventKindTouch,
|
kPointerEventKindTouch,
|
||||||
x,
|
x,
|
||||||
@@ -1105,8 +1138,8 @@ class InputModel {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
evtValue = {
|
evtValue = {
|
||||||
'x': pos.x,
|
'x': pos.x.toInt(),
|
||||||
'y': pos.y,
|
'y': pos.y.toInt(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1148,14 +1181,14 @@ class InputModel {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var type = '';
|
var type = kMouseEventTypeDefault;
|
||||||
var isMove = false;
|
var isMove = false;
|
||||||
switch (evt['type']) {
|
switch (evt['type']) {
|
||||||
case _kMouseEventDown:
|
case _kMouseEventDown:
|
||||||
type = 'down';
|
type = kMouseEventTypeDown;
|
||||||
break;
|
break;
|
||||||
case _kMouseEventUp:
|
case _kMouseEventUp:
|
||||||
type = 'up';
|
type = kMouseEventTypeUp;
|
||||||
break;
|
break;
|
||||||
case _kMouseEventMove:
|
case _kMouseEventMove:
|
||||||
_pointerMovedAfterEnter = true;
|
_pointerMovedAfterEnter = true;
|
||||||
@@ -1166,7 +1199,7 @@ class InputModel {
|
|||||||
}
|
}
|
||||||
evt['type'] = type;
|
evt['type'] = type;
|
||||||
|
|
||||||
if (type == 'down' && !_pointerMovedAfterEnter) {
|
if (type == kMouseEventTypeDown && !_pointerMovedAfterEnter) {
|
||||||
// Move mouse to the position of the down event first.
|
// Move mouse to the position of the down event first.
|
||||||
lastMousePos = ui.Offset(x, y);
|
lastMousePos = ui.Offset(x, y);
|
||||||
refreshMousePos();
|
refreshMousePos();
|
||||||
@@ -1188,8 +1221,8 @@ class InputModel {
|
|||||||
evt['x'] = '0';
|
evt['x'] = '0';
|
||||||
evt['y'] = '0';
|
evt['y'] = '0';
|
||||||
} else {
|
} else {
|
||||||
evt['x'] = '${pos.x}';
|
evt['x'] = '${pos.x.toInt()}';
|
||||||
evt['y'] = '${pos.y}';
|
evt['y'] = '${pos.y.toInt()}';
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<int, String> mapButtons = {
|
Map<int, String> mapButtons = {
|
||||||
@@ -1329,33 +1362,56 @@ class InputModel {
|
|||||||
y = pos.dy;
|
y = pos.dy;
|
||||||
}
|
}
|
||||||
|
|
||||||
var evtX = 0;
|
return InputModel.getPointInRemoteRect(
|
||||||
var evtY = 0;
|
true, peerPlatform, kind, evtType, x, y, rect,
|
||||||
try {
|
buttons: buttons);
|
||||||
evtX = x.round();
|
}
|
||||||
evtY = y.round();
|
|
||||||
} catch (e) {
|
|
||||||
debugPrintStack(label: 'canvas.scale value ${canvas.scale}, $e');
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
int minX = rect.left.toInt();
|
static Point<double>? getPointInRemoteRect(
|
||||||
|
bool isLocalDesktop,
|
||||||
|
String? peerPlatform,
|
||||||
|
String kind,
|
||||||
|
String evtType,
|
||||||
|
double evtX,
|
||||||
|
double evtY,
|
||||||
|
Rect rect,
|
||||||
|
{int buttons = kPrimaryMouseButton}) {
|
||||||
|
double minX = rect.left;
|
||||||
// https://github.com/rustdesk/rustdesk/issues/6678
|
// https://github.com/rustdesk/rustdesk/issues/6678
|
||||||
// For Windows, [0,maxX], [0,maxY] should be set to enable window snapping.
|
// For Windows, [0,maxX], [0,maxY] should be set to enable window snapping.
|
||||||
int maxX = (rect.left + rect.width).toInt() -
|
double maxX = (rect.left + rect.width) -
|
||||||
(peerPlatform == kPeerPlatformWindows ? 0 : 1);
|
(peerPlatform == kPeerPlatformWindows ? 0 : 1);
|
||||||
int minY = rect.top.toInt();
|
double minY = rect.top;
|
||||||
int maxY = (rect.top + rect.height).toInt() -
|
double maxY = (rect.top + rect.height) -
|
||||||
(peerPlatform == kPeerPlatformWindows ? 0 : 1);
|
(peerPlatform == kPeerPlatformWindows ? 0 : 1);
|
||||||
evtX = trySetNearestRange(evtX, minX, maxX, 5);
|
evtX = InputModel.tryGetNearestRange(evtX, minX, maxX, 5);
|
||||||
evtY = trySetNearestRange(evtY, minY, maxY, 5);
|
evtY = InputModel.tryGetNearestRange(evtY, minY, maxY, 5);
|
||||||
if (kind == kPointerEventKindMouse) {
|
if (isLocalDesktop) {
|
||||||
if (evtX < minX || evtY < minY || evtX > maxX || evtY > maxY) {
|
if (kind == kPointerEventKindMouse) {
|
||||||
// If left mouse up, no early return.
|
if (evtX < minX || evtY < minY || evtX > maxX || evtY > maxY) {
|
||||||
if (!(buttons == kPrimaryMouseButton && evtType == 'up')) {
|
// If left mouse up, no early return.
|
||||||
return null;
|
if (!(buttons == kPrimaryMouseButton &&
|
||||||
|
evtType == kMouseEventTypeUp)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
bool evtXInRange = evtX >= minX && evtX <= maxX;
|
||||||
|
bool evtYInRange = evtY >= minY && evtY <= maxY;
|
||||||
|
if (!(evtXInRange || evtYInRange)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (evtX < minX) {
|
||||||
|
evtX = minX;
|
||||||
|
} else if (evtX > maxX) {
|
||||||
|
evtX = maxX;
|
||||||
|
}
|
||||||
|
if (evtY < minY) {
|
||||||
|
evtY = minY;
|
||||||
|
} else if (evtY > maxY) {
|
||||||
|
evtY = maxY;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Point(evtX, evtY);
|
return Point(evtX, evtY);
|
||||||
|
|||||||
@@ -6,8 +6,10 @@ import 'dart:ui' as ui;
|
|||||||
|
|
||||||
import 'package:bot_toast/bot_toast.dart';
|
import 'package:bot_toast/bot_toast.dart';
|
||||||
import 'package:desktop_multi_window/desktop_multi_window.dart';
|
import 'package:desktop_multi_window/desktop_multi_window.dart';
|
||||||
|
import 'package:flutter/gestures.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
import 'package:flutter_hbb/common/widgets/peers_view.dart';
|
import 'package:flutter_hbb/common/widgets/peers_view.dart';
|
||||||
import 'package:flutter_hbb/consts.dart';
|
import 'package:flutter_hbb/consts.dart';
|
||||||
import 'package:flutter_hbb/models/ab_model.dart';
|
import 'package:flutter_hbb/models/ab_model.dart';
|
||||||
@@ -142,6 +144,7 @@ class FfiModel with ChangeNotifier {
|
|||||||
bool get touchMode => _touchMode;
|
bool get touchMode => _touchMode;
|
||||||
|
|
||||||
bool get isPeerAndroid => _pi.platform == kPeerPlatformAndroid;
|
bool get isPeerAndroid => _pi.platform == kPeerPlatformAndroid;
|
||||||
|
bool get isPeerMobile => isPeerAndroid;
|
||||||
|
|
||||||
bool get viewOnly => _viewOnly;
|
bool get viewOnly => _viewOnly;
|
||||||
|
|
||||||
@@ -306,6 +309,8 @@ class FfiModel with ChangeNotifier {
|
|||||||
.receive(int.parse(evt['id'] as String), evt['text'] ?? '');
|
.receive(int.parse(evt['id'] as String), evt['text'] ?? '');
|
||||||
} else if (name == 'file_dir') {
|
} else if (name == 'file_dir') {
|
||||||
parent.target?.fileModel.receiveFileDir(evt);
|
parent.target?.fileModel.receiveFileDir(evt);
|
||||||
|
} else if (name == 'empty_dirs') {
|
||||||
|
parent.target?.fileModel.receiveEmptyDirs(evt);
|
||||||
} else if (name == 'job_progress') {
|
} else if (name == 'job_progress') {
|
||||||
parent.target?.fileModel.jobController.tryUpdateJobProgress(evt);
|
parent.target?.fileModel.jobController.tryUpdateJobProgress(evt);
|
||||||
} else if (name == 'job_done') {
|
} else if (name == 'job_done') {
|
||||||
@@ -397,6 +402,10 @@ class FfiModel with ChangeNotifier {
|
|||||||
if (isWeb) {
|
if (isWeb) {
|
||||||
parent.target?.fileModel.onSelectedFiles(evt);
|
parent.target?.fileModel.onSelectedFiles(evt);
|
||||||
}
|
}
|
||||||
|
} else if (name == "send_emptry_dirs") {
|
||||||
|
if (isWeb) {
|
||||||
|
parent.target?.fileModel.sendEmptyDirs(evt);
|
||||||
|
}
|
||||||
} else if (name == "record_status") {
|
} else if (name == "record_status") {
|
||||||
if (desktopType == DesktopType.remote || isMobile) {
|
if (desktopType == DesktopType.remote || isMobile) {
|
||||||
parent.target?.recordingModel.updateStatus(evt['start'] == 'true');
|
parent.target?.recordingModel.updateStatus(evt['start'] == 'true');
|
||||||
@@ -1263,7 +1272,9 @@ class ImageModel with ChangeNotifier {
|
|||||||
rgba,
|
rgba,
|
||||||
rect?.width.toInt() ?? 0,
|
rect?.width.toInt() ?? 0,
|
||||||
rect?.height.toInt() ?? 0,
|
rect?.height.toInt() ?? 0,
|
||||||
isWeb ? ui.PixelFormat.rgba8888 : ui.PixelFormat.bgra8888,
|
isWeb | isWindows | isLinux
|
||||||
|
? ui.PixelFormat.rgba8888
|
||||||
|
: ui.PixelFormat.bgra8888,
|
||||||
);
|
);
|
||||||
if (parent.target?.id != pid) return;
|
if (parent.target?.id != pid) return;
|
||||||
await update(image);
|
await update(image);
|
||||||
@@ -1274,13 +1285,6 @@ class ImageModel with ChangeNotifier {
|
|||||||
if (isDesktop || isWebDesktop) {
|
if (isDesktop || isWebDesktop) {
|
||||||
await parent.target?.canvasModel.updateViewStyle();
|
await parent.target?.canvasModel.updateViewStyle();
|
||||||
await parent.target?.canvasModel.updateScrollStyle();
|
await parent.target?.canvasModel.updateScrollStyle();
|
||||||
} else {
|
|
||||||
final size = MediaQueryData.fromWindow(ui.window).size;
|
|
||||||
final canvasWidth = size.width;
|
|
||||||
final canvasHeight = size.height;
|
|
||||||
final xscale = canvasWidth / image.width;
|
|
||||||
final yscale = canvasHeight / image.height;
|
|
||||||
parent.target?.canvasModel.scale = min(xscale, yscale);
|
|
||||||
}
|
}
|
||||||
if (parent.target != null) {
|
if (parent.target != null) {
|
||||||
await initializeCursorAndCanvas(parent.target!);
|
await initializeCursorAndCanvas(parent.target!);
|
||||||
@@ -1292,20 +1296,18 @@ class ImageModel with ChangeNotifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// mobile only
|
// mobile only
|
||||||
// for desktop, height should minus tabbar height
|
|
||||||
double get maxScale {
|
double get maxScale {
|
||||||
if (_image == null) return 1.5;
|
if (_image == null) return 1.5;
|
||||||
final size = MediaQueryData.fromWindow(ui.window).size;
|
final size = parent.target!.canvasModel.getSize();
|
||||||
final xscale = size.width / _image!.width;
|
final xscale = size.width / _image!.width;
|
||||||
final yscale = size.height / _image!.height;
|
final yscale = size.height / _image!.height;
|
||||||
return max(1.5, max(xscale, yscale));
|
return max(1.5, max(xscale, yscale));
|
||||||
}
|
}
|
||||||
|
|
||||||
// mobile only
|
// mobile only
|
||||||
// for desktop, height should minus tabbar height
|
|
||||||
double get minScale {
|
double get minScale {
|
||||||
if (_image == null) return 1.5;
|
if (_image == null) return 1.5;
|
||||||
final size = MediaQueryData.fromWindow(ui.window).size;
|
final size = parent.target!.canvasModel.getSize();
|
||||||
final xscale = size.width / _image!.width;
|
final xscale = size.width / _image!.width;
|
||||||
final yscale = size.height / _image!.height;
|
final yscale = size.height / _image!.height;
|
||||||
return min(xscale, yscale) / 1.5;
|
return min(xscale, yscale) / 1.5;
|
||||||
@@ -1428,6 +1430,12 @@ class CanvasModel with ChangeNotifier {
|
|||||||
ScrollStyle _scrollStyle = ScrollStyle.scrollauto;
|
ScrollStyle _scrollStyle = ScrollStyle.scrollauto;
|
||||||
ViewStyle _lastViewStyle = ViewStyle.defaultViewStyle();
|
ViewStyle _lastViewStyle = ViewStyle.defaultViewStyle();
|
||||||
|
|
||||||
|
Timer? _timerMobileFocusCanvasCursor;
|
||||||
|
|
||||||
|
// `isMobileCanvasChanged` is used to avoid canvas reset when changing the input method
|
||||||
|
// after showing the soft keyboard.
|
||||||
|
bool isMobileCanvasChanged = false;
|
||||||
|
|
||||||
final ScrollController _horizontal = ScrollController();
|
final ScrollController _horizontal = ScrollController();
|
||||||
final ScrollController _vertical = ScrollController();
|
final ScrollController _vertical = ScrollController();
|
||||||
|
|
||||||
@@ -1470,21 +1478,37 @@ class CanvasModel with ChangeNotifier {
|
|||||||
static double get bottomToEdge =>
|
static double get bottomToEdge =>
|
||||||
isDesktop ? windowBorderWidth + kDragToResizeAreaPadding.bottom : 0;
|
isDesktop ? windowBorderWidth + kDragToResizeAreaPadding.bottom : 0;
|
||||||
|
|
||||||
updateViewStyle({refreshMousePos = true}) async {
|
Size getSize() {
|
||||||
Size getSize() {
|
final mediaData = MediaQueryData.fromView(ui.window);
|
||||||
final size = MediaQueryData.fromWindow(ui.window).size;
|
final size = mediaData.size;
|
||||||
// If minimized, w or h may be negative here.
|
// If minimized, w or h may be negative here.
|
||||||
double w = size.width - leftToEdge - rightToEdge;
|
double w = size.width - leftToEdge - rightToEdge;
|
||||||
double h = size.height - topToEdge - bottomToEdge;
|
double h = size.height - topToEdge - bottomToEdge;
|
||||||
return Size(w < 0 ? 0 : w, h < 0 ? 0 : h);
|
if (isMobile) {
|
||||||
|
h = h -
|
||||||
|
mediaData.viewInsets.bottom -
|
||||||
|
(parent.target?.cursorModel.keyHelpToolsRectToAdjustCanvas?.bottom ??
|
||||||
|
0);
|
||||||
}
|
}
|
||||||
|
return Size(w < 0 ? 0 : w, h < 0 ? 0 : h);
|
||||||
|
}
|
||||||
|
|
||||||
|
// mobile only
|
||||||
|
double getAdjustY() {
|
||||||
|
final bottom =
|
||||||
|
parent.target?.cursorModel.keyHelpToolsRectToAdjustCanvas?.bottom ?? 0;
|
||||||
|
return max(bottom - MediaQueryData.fromView(ui.window).padding.top, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateSize() => _size = getSize();
|
||||||
|
|
||||||
|
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(
|
||||||
@@ -1507,16 +1531,25 @@ class CanvasModel with ChangeNotifier {
|
|||||||
if (kIgnoreDpi && style == kRemoteViewStyleOriginal) {
|
if (kIgnoreDpi && style == kRemoteViewStyleOriginal) {
|
||||||
_scale = 1.0 / _devicePixelRatio;
|
_scale = 1.0 / _devicePixelRatio;
|
||||||
}
|
}
|
||||||
_x = (size.width - displayWidth * _scale) / 2;
|
_resetCanvasOffset(displayWidth, displayHeight);
|
||||||
_y = (size.height - displayHeight * _scale) / 2;
|
|
||||||
_imageOverflow.value = _x < 0 || y < 0;
|
_imageOverflow.value = _x < 0 || y < 0;
|
||||||
notifyListeners();
|
if (notify) {
|
||||||
if (refreshMousePos) {
|
notifyListeners();
|
||||||
|
}
|
||||||
|
if (!isMobile && refreshMousePos) {
|
||||||
parent.target?.inputModel.refreshMousePos();
|
parent.target?.inputModel.refreshMousePos();
|
||||||
}
|
}
|
||||||
tryUpdateScrollStyle(Duration.zero, style);
|
tryUpdateScrollStyle(Duration.zero, style);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_resetCanvasOffset(int displayWidth, int displayHeight) {
|
||||||
|
_x = (size.width - displayWidth * _scale) / 2;
|
||||||
|
_y = (size.height - displayHeight * _scale) / 2;
|
||||||
|
if (isMobile) {
|
||||||
|
_moveToCenterCursor();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
tryUpdateScrollStyle(Duration duration, String? style) async {
|
tryUpdateScrollStyle(Duration duration, String? style) async {
|
||||||
if (_scrollStyle != ScrollStyle.scrollbar) return;
|
if (_scrollStyle != ScrollStyle.scrollbar) return;
|
||||||
style ??= await bind.sessionGetViewStyle(sessionId: sessionId);
|
style ??= await bind.sessionGetViewStyle(sessionId: sessionId);
|
||||||
@@ -1620,6 +1653,9 @@ class CanvasModel with ChangeNotifier {
|
|||||||
|
|
||||||
panX(double dx) {
|
panX(double dx) {
|
||||||
_x += dx;
|
_x += dx;
|
||||||
|
if (isMobile) {
|
||||||
|
isMobileCanvasChanged = true;
|
||||||
|
}
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1627,17 +1663,20 @@ class CanvasModel with ChangeNotifier {
|
|||||||
if (isWebDesktop) {
|
if (isWebDesktop) {
|
||||||
updateViewStyle();
|
updateViewStyle();
|
||||||
} else {
|
} else {
|
||||||
_x = (size.width - getDisplayWidth() * _scale) / 2;
|
_resetCanvasOffset(getDisplayWidth(), getDisplayHeight());
|
||||||
_y = (size.height - getDisplayHeight() * _scale) / 2;
|
|
||||||
}
|
}
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
panY(double dy) {
|
panY(double dy) {
|
||||||
_y += dy;
|
_y += dy;
|
||||||
|
if (isMobile) {
|
||||||
|
isMobileCanvasChanged = true;
|
||||||
|
}
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// mobile only
|
||||||
updateScale(double v, Offset focalPoint) {
|
updateScale(double v, Offset focalPoint) {
|
||||||
if (parent.target?.imageModel.image == null) return;
|
if (parent.target?.imageModel.image == null) return;
|
||||||
final s = _scale;
|
final s = _scale;
|
||||||
@@ -1649,13 +1688,13 @@ class CanvasModel with ChangeNotifier {
|
|||||||
// (focalPoint.dx - _x_1) / s1 + displayOriginX = (focalPoint.dx - _x_2) / s2 + displayOriginX
|
// (focalPoint.dx - _x_1) / s1 + displayOriginX = (focalPoint.dx - _x_2) / s2 + displayOriginX
|
||||||
// _x_2 = focalPoint.dx - (focalPoint.dx - _x_1) / s1 * s2
|
// _x_2 = focalPoint.dx - (focalPoint.dx - _x_1) / s1 * s2
|
||||||
_x = focalPoint.dx - (focalPoint.dx - _x) / s * _scale;
|
_x = focalPoint.dx - (focalPoint.dx - _x) / s * _scale;
|
||||||
final adjustForKeyboard =
|
final adjust = getAdjustY();
|
||||||
parent.target?.cursorModel.adjustForKeyboard() ?? 0.0;
|
// (focalPoint.dy - _y_1 - adjust) / s1 + displayOriginY = (focalPoint.dy - _y_2 - adjust) / s2 + displayOriginY
|
||||||
// (focalPoint.dy - _y_1 + adjust) / s1 + displayOriginY = (focalPoint.dy - _y_2 + adjust) / s2 + displayOriginY
|
// _y_2 = focalPoint.dy - adjust - (focalPoint.dy - _y_1 - adjust) / s1 * s2
|
||||||
// _y_2 = focalPoint.dy + adjust - (focalPoint.dy - _y_1 + adjust) / s1 * s2
|
_y = focalPoint.dy - adjust - (focalPoint.dy - _y - adjust) / s * _scale;
|
||||||
_y = focalPoint.dy +
|
if (isMobile) {
|
||||||
adjustForKeyboard -
|
isMobileCanvasChanged = true;
|
||||||
(focalPoint.dy - _y + adjustForKeyboard) / s * _scale;
|
}
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1666,10 +1705,7 @@ class CanvasModel with ChangeNotifier {
|
|||||||
if (kIgnoreDpi && _lastViewStyle.style == kRemoteViewStyleOriginal) {
|
if (kIgnoreDpi && _lastViewStyle.style == kRemoteViewStyleOriginal) {
|
||||||
_scale = 1.0 / _devicePixelRatio;
|
_scale = 1.0 / _devicePixelRatio;
|
||||||
}
|
}
|
||||||
final displayWidth = getDisplayWidth();
|
_resetCanvasOffset(getDisplayWidth(), getDisplayHeight());
|
||||||
final displayHeight = getDisplayHeight();
|
|
||||||
_x = (size.width - displayWidth * _scale) / 2;
|
|
||||||
_y = (size.height - displayHeight * _scale) / 2;
|
|
||||||
bind.sessionSetViewStyle(sessionId: sessionId, value: _lastViewStyle.style);
|
bind.sessionSetViewStyle(sessionId: sessionId, value: _lastViewStyle.style);
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
@@ -1678,6 +1714,8 @@ class CanvasModel with ChangeNotifier {
|
|||||||
_x = 0;
|
_x = 0;
|
||||||
_y = 0;
|
_y = 0;
|
||||||
_scale = 1.0;
|
_scale = 1.0;
|
||||||
|
_lastViewStyle = ViewStyle.defaultViewStyle();
|
||||||
|
_timerMobileFocusCanvasCursor?.cancel();
|
||||||
}
|
}
|
||||||
|
|
||||||
updateScrollPercent() {
|
updateScrollPercent() {
|
||||||
@@ -1695,6 +1733,43 @@ class CanvasModel with ChangeNotifier {
|
|||||||
: 0.0;
|
: 0.0;
|
||||||
setScrollPercent(percentX, percentY);
|
setScrollPercent(percentX, percentY);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void mobileFocusCanvasCursor() {
|
||||||
|
_timerMobileFocusCanvasCursor?.cancel();
|
||||||
|
_timerMobileFocusCanvasCursor =
|
||||||
|
Timer(Duration(milliseconds: 100), () async {
|
||||||
|
updateSize();
|
||||||
|
_resetCanvasOffset(getDisplayWidth(), getDisplayHeight());
|
||||||
|
notifyListeners();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// mobile only
|
||||||
|
// Move the canvas to make the cursor visible(center) on the screen.
|
||||||
|
void _moveToCenterCursor() {
|
||||||
|
Rect? imageRect = parent.target?.ffiModel.rect;
|
||||||
|
if (imageRect == null) {
|
||||||
|
// unreachable
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final maxX = 0.0;
|
||||||
|
final minX = _size.width + (imageRect.left - imageRect.right) * _scale;
|
||||||
|
final maxY = 0.0;
|
||||||
|
final minY = _size.height + (imageRect.top - imageRect.bottom) * _scale;
|
||||||
|
Offset offsetToCenter =
|
||||||
|
parent.target?.cursorModel.getCanvasOffsetToCenterCursor() ??
|
||||||
|
Offset.zero;
|
||||||
|
if (minX < 0) {
|
||||||
|
_x = min(max(offsetToCenter.dx, minX), maxX);
|
||||||
|
} else {
|
||||||
|
// _size.width > (imageRect.right, imageRect.left) * _scale, we should not change _x
|
||||||
|
}
|
||||||
|
if (minY < 0) {
|
||||||
|
_y = min(max(offsetToCenter.dy, minY), maxY);
|
||||||
|
} else {
|
||||||
|
// _size.height > (imageRect.bottom - imageRect.top) * _scale, , we should not change _y
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// data for cursor
|
// data for cursor
|
||||||
@@ -1888,9 +1963,13 @@ class CursorModel with ChangeNotifier {
|
|||||||
// `lastIsBlocked` is only used in common/widgets/remote_input.dart -> _RawTouchGestureDetectorRegionState -> onDoubleTap()
|
// `lastIsBlocked` is only used in common/widgets/remote_input.dart -> _RawTouchGestureDetectorRegionState -> onDoubleTap()
|
||||||
// Because onDoubleTap() doesn't have the `event` parameter, we can't get the touch event's position.
|
// Because onDoubleTap() doesn't have the `event` parameter, we can't get the touch event's position.
|
||||||
bool _lastIsBlocked = false;
|
bool _lastIsBlocked = false;
|
||||||
double _yForKeyboardAdjust = 0;
|
bool _lastKeyboardIsVisible = false;
|
||||||
|
|
||||||
keyHelpToolsVisibilityChanged(Rect? r) {
|
bool get lastKeyboardIsVisible => _lastKeyboardIsVisible;
|
||||||
|
|
||||||
|
Rect? get keyHelpToolsRectToAdjustCanvas =>
|
||||||
|
_lastKeyboardIsVisible ? _keyHelpToolsRect : null;
|
||||||
|
keyHelpToolsVisibilityChanged(Rect? r, bool keyboardIsVisible) {
|
||||||
_keyHelpToolsRect = r;
|
_keyHelpToolsRect = r;
|
||||||
if (r == null) {
|
if (r == null) {
|
||||||
_lastIsBlocked = false;
|
_lastIsBlocked = false;
|
||||||
@@ -1900,7 +1979,11 @@ class CursorModel with ChangeNotifier {
|
|||||||
// `lastIsBlocked` will be set when the cursor is moving or touch somewhere else.
|
// `lastIsBlocked` will be set when the cursor is moving or touch somewhere else.
|
||||||
_lastIsBlocked = true;
|
_lastIsBlocked = true;
|
||||||
}
|
}
|
||||||
_yForKeyboardAdjust = _y;
|
if (isMobile && _lastKeyboardIsVisible != keyboardIsVisible) {
|
||||||
|
parent.target?.canvasModel.mobileFocusCanvasCursor();
|
||||||
|
parent.target?.canvasModel.isMobileCanvasChanged = false;
|
||||||
|
}
|
||||||
|
_lastKeyboardIsVisible = keyboardIsVisible;
|
||||||
}
|
}
|
||||||
|
|
||||||
get lastIsBlocked => _lastIsBlocked;
|
get lastIsBlocked => _lastIsBlocked;
|
||||||
@@ -1939,8 +2022,10 @@ class CursorModel with ChangeNotifier {
|
|||||||
addKey(String key) => _cacheKeys.add(key);
|
addKey(String key) => _cacheKeys.add(key);
|
||||||
|
|
||||||
// remote physical display coordinate
|
// remote physical display coordinate
|
||||||
|
// For update pan (mobile), onOneFingerPanStart, onOneFingerPanUpdate, onHoldDragUpdate
|
||||||
Rect getVisibleRect() {
|
Rect getVisibleRect() {
|
||||||
final size = MediaQueryData.fromWindow(ui.window).size;
|
final size = parent.target?.canvasModel.getSize() ??
|
||||||
|
MediaQueryData.fromView(ui.window).size;
|
||||||
final xoffset = parent.target?.canvasModel.x ?? 0;
|
final xoffset = parent.target?.canvasModel.x ?? 0;
|
||||||
final yoffset = parent.target?.canvasModel.y ?? 0;
|
final yoffset = parent.target?.canvasModel.y ?? 0;
|
||||||
final scale = parent.target?.canvasModel.scale ?? 1;
|
final scale = parent.target?.canvasModel.scale ?? 1;
|
||||||
@@ -1949,22 +2034,22 @@ class CursorModel with ChangeNotifier {
|
|||||||
return Rect.fromLTWH(x0, y0, size.width / scale, size.height / scale);
|
return Rect.fromLTWH(x0, y0, size.width / scale, size.height / scale);
|
||||||
}
|
}
|
||||||
|
|
||||||
get keyboardHeight => MediaQueryData.fromWindow(ui.window).viewInsets.bottom;
|
Offset getCanvasOffsetToCenterCursor() {
|
||||||
get scale => parent.target?.canvasModel.scale ?? 1.0;
|
// Cursor should be at the center of the visible rect.
|
||||||
|
// _x = rect.left + rect.width / 2
|
||||||
double adjustForKeyboard() {
|
// _y = rect.right + rect.height / 2
|
||||||
if (keyboardHeight < 100) {
|
// See `getVisibleRect()`
|
||||||
return 0.0;
|
// _x = _displayOriginX - xoffset / scale + size.width / scale * 0.5;
|
||||||
}
|
// _y = _displayOriginY - yoffset / scale + size.height / scale * 0.5;
|
||||||
|
final size = parent.target?.canvasModel.getSize() ??
|
||||||
final m = MediaQueryData.fromWindow(ui.window);
|
MediaQueryData.fromView(ui.window).size;
|
||||||
final size = m.size;
|
final xoffset = (_displayOriginX - _x) * scale + size.width * 0.5;
|
||||||
final thresh = (size.height - keyboardHeight) / 2;
|
final yoffset = (_displayOriginY - _y) * scale + size.height * 0.5;
|
||||||
final h = (_yForKeyboardAdjust - getVisibleRect().top) *
|
return Offset(xoffset, yoffset);
|
||||||
scale; // local physical display height
|
|
||||||
return h - thresh;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get scale => parent.target?.canvasModel.scale ?? 1.0;
|
||||||
|
|
||||||
// mobile Soft keyboard, block touch event from the KeyHelpTools
|
// mobile Soft keyboard, block touch event from the KeyHelpTools
|
||||||
shouldBlock(double x, double y) {
|
shouldBlock(double x, double y) {
|
||||||
if (!(parent.target?.ffiModel.touchMode ?? false)) {
|
if (!(parent.target?.ffiModel.touchMode ?? false)) {
|
||||||
@@ -1979,22 +2064,63 @@ class CursorModel with ChangeNotifier {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
move(double x, double y) {
|
// For touch mode
|
||||||
|
Future<bool> move(double x, double y) async {
|
||||||
if (shouldBlock(x, y)) {
|
if (shouldBlock(x, y)) {
|
||||||
_lastIsBlocked = true;
|
_lastIsBlocked = true;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
_lastIsBlocked = false;
|
_lastIsBlocked = false;
|
||||||
moveLocal(x, y, adjust: adjustForKeyboard());
|
if (!_moveLocalIfInRemoteRect(x, y)) {
|
||||||
parent.target?.inputModel.moveMouse(_x, _y);
|
return false;
|
||||||
|
}
|
||||||
|
await parent.target?.inputModel.moveMouse(_x, _y);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isInRemoteRect(Offset offset) {
|
||||||
|
return getRemotePosInRect(offset) != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Offset? getRemotePosInRect(Offset offset) {
|
||||||
|
final adjust = parent.target?.canvasModel.getAdjustY() ?? 0;
|
||||||
|
final newPos = _getNewPos(offset.dx, offset.dy, adjust);
|
||||||
|
final visibleRect = getVisibleRect();
|
||||||
|
if (!isPointInRect(newPos, visibleRect)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
final rect = parent.target?.ffiModel.rect;
|
||||||
|
if (rect != null) {
|
||||||
|
if (!isPointInRect(newPos, rect)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return newPos;
|
||||||
|
}
|
||||||
|
|
||||||
|
Offset _getNewPos(double x, double y, double adjust) {
|
||||||
|
final xoffset = parent.target?.canvasModel.x ?? 0;
|
||||||
|
final yoffset = parent.target?.canvasModel.y ?? 0;
|
||||||
|
final newX = (x - xoffset) / scale + _displayOriginX;
|
||||||
|
final newY = (y - yoffset - adjust) / scale + _displayOriginY;
|
||||||
|
return Offset(newX, newY);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool _moveLocalIfInRemoteRect(double x, double y) {
|
||||||
|
final newPos = getRemotePosInRect(Offset(x, y));
|
||||||
|
if (newPos == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
_x = newPos.dx;
|
||||||
|
_y = newPos.dy;
|
||||||
|
notifyListeners();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
moveLocal(double x, double y, {double adjust = 0}) {
|
moveLocal(double x, double y, {double adjust = 0}) {
|
||||||
final xoffset = parent.target?.canvasModel.x ?? 0;
|
final newPos = _getNewPos(x, y, adjust);
|
||||||
final yoffset = parent.target?.canvasModel.y ?? 0;
|
_x = newPos.dx;
|
||||||
_x = (x - xoffset) / scale + _displayOriginX;
|
_y = newPos.dy;
|
||||||
_y = (y - yoffset + adjust) / scale + _displayOriginY;
|
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2006,9 +2132,9 @@ class CursorModel with ChangeNotifier {
|
|||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
updatePan(Offset delta, Offset localPosition, bool touchMode) {
|
updatePan(Offset delta, Offset localPosition, bool touchMode) async {
|
||||||
if (touchMode) {
|
if (touchMode) {
|
||||||
_handleTouchMode(delta, localPosition);
|
await _handleTouchMode(delta, localPosition);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
double dx = delta.dx;
|
double dx = delta.dx;
|
||||||
@@ -2066,13 +2192,34 @@ class CursorModel with ChangeNotifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (dx == 0 && dy == 0) return;
|
if (dx == 0 && dy == 0) return;
|
||||||
_x += dx;
|
|
||||||
_y += dy;
|
Point<double>? newPos;
|
||||||
|
final rect = parent.target?.ffiModel.rect;
|
||||||
|
if (rect == null) {
|
||||||
|
// unreachable
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
newPos = InputModel.getPointInRemoteRect(
|
||||||
|
false,
|
||||||
|
parent.target?.ffiModel.pi.platform,
|
||||||
|
kPointerEventKindMouse,
|
||||||
|
kMouseEventTypeDefault,
|
||||||
|
_x + dx,
|
||||||
|
_y + dy,
|
||||||
|
rect,
|
||||||
|
buttons: kPrimaryButton);
|
||||||
|
if (newPos == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
dx = newPos.x - _x;
|
||||||
|
dy = newPos.y - _y;
|
||||||
|
_x = newPos.x;
|
||||||
|
_y = newPos.y;
|
||||||
if (tryMoveCanvasX && dx != 0) {
|
if (tryMoveCanvasX && dx != 0) {
|
||||||
parent.target?.canvasModel.panX(-dx);
|
parent.target?.canvasModel.panX(-dx * scale);
|
||||||
}
|
}
|
||||||
if (tryMoveCanvasY && dy != 0) {
|
if (tryMoveCanvasY && dy != 0) {
|
||||||
parent.target?.canvasModel.panY(-dy);
|
parent.target?.canvasModel.panY(-dy * scale);
|
||||||
}
|
}
|
||||||
|
|
||||||
parent.target?.inputModel.moveMouse(_x, _y);
|
parent.target?.inputModel.moveMouse(_x, _y);
|
||||||
@@ -2085,7 +2232,7 @@ class CursorModel with ChangeNotifier {
|
|||||||
return x >= 0 && y >= 0 && x <= w && y <= h;
|
return x >= 0 && y >= 0 && x <= w && y <= h;
|
||||||
}
|
}
|
||||||
|
|
||||||
_handleTouchMode(Offset delta, Offset localPosition) {
|
_handleTouchMode(Offset delta, Offset localPosition) async {
|
||||||
bool isMoved = false;
|
bool isMoved = false;
|
||||||
if (_remoteWindowCoords.isNotEmpty &&
|
if (_remoteWindowCoords.isNotEmpty &&
|
||||||
_windowRect != null &&
|
_windowRect != null &&
|
||||||
@@ -2101,15 +2248,50 @@ class CursorModel with ChangeNotifier {
|
|||||||
coords.canvas.scale;
|
coords.canvas.scale;
|
||||||
x2 += coords.cursor.offset.dx;
|
x2 += coords.cursor.offset.dx;
|
||||||
y2 += coords.cursor.offset.dy;
|
y2 += coords.cursor.offset.dy;
|
||||||
parent.target?.inputModel.moveMouse(x2, y2);
|
await parent.target?.inputModel.moveMouse(x2, y2);
|
||||||
isMoved = true;
|
isMoved = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!isMoved) {
|
if (!isMoved) {
|
||||||
|
final rect = parent.target?.ffiModel.rect;
|
||||||
|
if (rect == null) {
|
||||||
|
// unreachable
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Offset? movementInRect(double x, double y, Rect r) {
|
||||||
|
final isXInRect = x >= r.left && x <= r.right;
|
||||||
|
final isYInRect = y >= r.top && y <= r.bottom;
|
||||||
|
if (!(isXInRect || isYInRect)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (x < r.left) {
|
||||||
|
x = r.left;
|
||||||
|
} else if (x > r.right) {
|
||||||
|
x = r.right;
|
||||||
|
}
|
||||||
|
if (y < r.top) {
|
||||||
|
y = r.top;
|
||||||
|
} else if (y > r.bottom) {
|
||||||
|
y = r.bottom;
|
||||||
|
}
|
||||||
|
return Offset(x, y);
|
||||||
|
}
|
||||||
|
|
||||||
final scale = parent.target?.canvasModel.scale ?? 1.0;
|
final scale = parent.target?.canvasModel.scale ?? 1.0;
|
||||||
_x += delta.dx / scale;
|
var movement =
|
||||||
_y += delta.dy / scale;
|
movementInRect(_x + delta.dx / scale, _y + delta.dy / scale, rect);
|
||||||
parent.target?.inputModel.moveMouse(_x, _y);
|
if (movement == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
movement = movementInRect(movement.dx, movement.dy, getVisibleRect());
|
||||||
|
if (movement == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_x = movement.dx;
|
||||||
|
_y = movement.dy;
|
||||||
|
await parent.target?.inputModel.moveMouse(_x, _y);
|
||||||
}
|
}
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
@@ -2686,6 +2868,7 @@ class FFI {
|
|||||||
canvasModel.scale,
|
canvasModel.scale,
|
||||||
ffiModel.pi.currentDisplay);
|
ffiModel.pi.currentDisplay);
|
||||||
}
|
}
|
||||||
|
imageModel.callbacksOnFirstImage.clear();
|
||||||
await imageModel.update(null);
|
await imageModel.update(null);
|
||||||
cursorModel.clear();
|
cursorModel.clear();
|
||||||
ffiModel.clear();
|
ffiModel.clear();
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ class ServerModel with ChangeNotifier {
|
|||||||
bool _inputOk = false;
|
bool _inputOk = false;
|
||||||
bool _audioOk = false;
|
bool _audioOk = false;
|
||||||
bool _fileOk = false;
|
bool _fileOk = false;
|
||||||
|
bool _clipboardOk = false;
|
||||||
bool _showElevation = false;
|
bool _showElevation = false;
|
||||||
bool hideCm = false;
|
bool hideCm = false;
|
||||||
int _connectStatus = 0; // Rendezvous Server status
|
int _connectStatus = 0; // Rendezvous Server status
|
||||||
@@ -59,6 +60,8 @@ class ServerModel with ChangeNotifier {
|
|||||||
|
|
||||||
bool get fileOk => _fileOk;
|
bool get fileOk => _fileOk;
|
||||||
|
|
||||||
|
bool get clipboardOk => _clipboardOk;
|
||||||
|
|
||||||
bool get showElevation => _showElevation;
|
bool get showElevation => _showElevation;
|
||||||
|
|
||||||
int get connectStatus => _connectStatus;
|
int get connectStatus => _connectStatus;
|
||||||
@@ -209,6 +212,10 @@ class ServerModel with ChangeNotifier {
|
|||||||
_fileOk = fileOption != 'N';
|
_fileOk = fileOption != 'N';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// clipboard
|
||||||
|
final clipOption = await bind.mainGetOption(key: kOptionEnableClipboard);
|
||||||
|
_clipboardOk = clipOption != 'N';
|
||||||
|
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -315,6 +322,14 @@ class ServerModel with ChangeNotifier {
|
|||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
toggleClipboard() async {
|
||||||
|
_clipboardOk = !clipboardOk;
|
||||||
|
bind.mainSetOption(
|
||||||
|
key: kOptionEnableClipboard,
|
||||||
|
value: clipboardOk ? defaultOptionYes : 'N');
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
toggleInput() async {
|
toggleInput() async {
|
||||||
if (clients.isNotEmpty) {
|
if (clients.isNotEmpty) {
|
||||||
await showClientsMayNotBeChangedAlert(parent.target);
|
await showClientsMayNotBeChangedAlert(parent.target);
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -25,6 +26,8 @@ class StateGlobal {
|
|||||||
|
|
||||||
final isPortrait = false.obs;
|
final isPortrait = false.obs;
|
||||||
|
|
||||||
|
final updateUrl = ''.obs;
|
||||||
|
|
||||||
String _inputSource = '';
|
String _inputSource = '';
|
||||||
|
|
||||||
// Use for desktop -> remote toolbar -> resolution
|
// Use for desktop -> remote toolbar -> resolution
|
||||||
|
|||||||
@@ -1801,6 +1801,26 @@ class RustdeskImpl {
|
|||||||
throw UnimplementedError("mainMaxEncryptLen");
|
throw UnimplementedError("mainMaxEncryptLen");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool mainAudioSupportLoopback({dynamic hint}) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<String> sessionReadLocalEmptyDirsRecursiveSync(
|
||||||
|
{required UuidValue sessionId,
|
||||||
|
required String path,
|
||||||
|
required bool includeHidden,
|
||||||
|
dynamic hint}) {
|
||||||
|
throw UnimplementedError("sessionReadLocalEmptyDirsRecursiveSync");
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> sessionReadRemoteEmptyDirsRecursiveSync(
|
||||||
|
{required UuidValue sessionId,
|
||||||
|
required String path,
|
||||||
|
required bool includeHidden,
|
||||||
|
dynamic hint}) {
|
||||||
|
throw UnimplementedError("sessionReadRemoteEmptyDirsRecursiveSync");
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> sessionRenameFile(
|
Future<void> sessionRenameFile(
|
||||||
{required UuidValue sessionId,
|
{required UuidValue sessionId,
|
||||||
required int actId,
|
required int actId,
|
||||||
|
|||||||
@@ -14,8 +14,13 @@ 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);
|
||||||
|
|
||||||
// Implements GApplication::activate.
|
// Implements GApplication::activate.
|
||||||
static void my_application_activate(GApplication* application) {
|
static void my_application_activate(GApplication* application) {
|
||||||
MyApplication* self = MY_APPLICATION(application);
|
MyApplication* self = MY_APPLICATION(application);
|
||||||
@@ -39,9 +44,10 @@ static void my_application_activate(GApplication* application) {
|
|||||||
// If running on Wayland assume the header bar will work (may need changing
|
// If running on Wayland assume the header bar will work (may need changing
|
||||||
// if future cases occur).
|
// if future cases occur).
|
||||||
gboolean use_header_bar = TRUE;
|
gboolean use_header_bar = TRUE;
|
||||||
|
GdkScreen* screen = NULL;
|
||||||
#ifdef GDK_WINDOWING_X11
|
#ifdef GDK_WINDOWING_X11
|
||||||
GdkScreen* screen = gtk_window_get_screen(window);
|
screen = gtk_window_get_screen(window);
|
||||||
if (GDK_IS_X11_SCREEN(screen)) {
|
if (screen != NULL && GDK_IS_X11_SCREEN(screen)) {
|
||||||
const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen);
|
const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen);
|
||||||
if (g_strcmp0(wm_name, "GNOME Shell") != 0) {
|
if (g_strcmp0(wm_name, "GNOME Shell") != 0) {
|
||||||
use_header_bar = FALSE;
|
use_header_bar = FALSE;
|
||||||
@@ -66,16 +72,19 @@ 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, 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));
|
||||||
|
|
||||||
gtk_widget_grab_focus(GTK_WIDGET(view));
|
gtk_widget_grab_focus(GTK_WIDGET(view));
|
||||||
@@ -121,3 +130,48 @@ MyApplication* my_application_new() {
|
|||||||
"flags", G_APPLICATION_NON_UNIQUE,
|
"flags", G_APPLICATION_NON_UNIQUE,
|
||||||
nullptr));
|
nullptr));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
GtkWidget *find_gl_area(GtkWidget *widget)
|
||||||
|
{
|
||||||
|
if (GTK_IS_GL_AREA(widget)) {
|
||||||
|
return widget;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (GTK_IS_CONTAINER(widget)) {
|
||||||
|
GList *children = gtk_container_get_children(GTK_CONTAINER(widget));
|
||||||
|
for (GList *iter = children; iter != NULL; iter = g_list_next(iter)) {
|
||||||
|
GtkWidget *child = GTK_WIDGET(iter->data);
|
||||||
|
GtkWidget *gl_area = find_gl_area(child);
|
||||||
|
if (gl_area != NULL) {
|
||||||
|
g_list_free(children);
|
||||||
|
return gl_area;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
g_list_free(children);
|
||||||
|
}
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://github.com/flutter/flutter/issues/152154
|
||||||
|
// Remove this workaround when flutter version is updated.
|
||||||
|
void try_set_transparent(GtkWindow* window, GdkScreen* screen, FlView* view)
|
||||||
|
{
|
||||||
|
GtkWidget *gl_area = NULL;
|
||||||
|
|
||||||
|
printf("Try setting transparent\n");
|
||||||
|
|
||||||
|
gl_area = find_gl_area(GTK_WIDGET(view));
|
||||||
|
if (gl_area != NULL) {
|
||||||
|
gtk_gl_area_set_has_alpha(GTK_GL_AREA(gl_area), TRUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (screen != NULL) {
|
||||||
|
GdkVisual *visual = NULL;
|
||||||
|
gtk_widget_set_app_paintable(GTK_WIDGET(window), TRUE);
|
||||||
|
visual = gdk_screen_get_rgba_visual(screen);
|
||||||
|
if (visual != NULL && gdk_screen_is_composited(screen)) {
|
||||||
|
gtk_widget_set_visual(GTK_WIDGET(window), visual);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -37,10 +37,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: archive
|
name: archive
|
||||||
sha256: "22600aa1e926be775fa5fe7e6894e7fb3df9efda8891c73f70fb3262399a432d"
|
sha256: cb6a278ef2dbb298455e1a713bda08524a175630ec643a242c399c932a0a1f7d
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.4.10"
|
version: "3.6.1"
|
||||||
args:
|
args:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -335,7 +335,7 @@ packages:
|
|||||||
description:
|
description:
|
||||||
path: "."
|
path: "."
|
||||||
ref: HEAD
|
ref: HEAD
|
||||||
resolved-ref: "519350f1f40746798299e94786197d058353bac9"
|
resolved-ref: "4f562ab49d289cfa36bfda7cff12746ec0200033"
|
||||||
url: "https://github.com/rustdesk-org/rustdesk_desktop_multi_window"
|
url: "https://github.com/rustdesk-org/rustdesk_desktop_multi_window"
|
||||||
source: git
|
source: git
|
||||||
version: "0.1.0"
|
version: "0.1.0"
|
||||||
@@ -384,10 +384,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: extended_text
|
name: extended_text
|
||||||
sha256: "7f382de3af12992e34bd72ddd36becf90c4720900af126cb9859f0189af71ffe"
|
sha256: "38c1cac571d6eaf406f4b80040c1f88561e7617ad90795aac6a1be0a8d0bb676"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "13.0.0"
|
version: "14.0.0"
|
||||||
extended_text_library:
|
extended_text_library:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -408,10 +408,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: ffi
|
name: ffi
|
||||||
sha256: "7bf0adc28a23d395f19f3f1eb21dd7cfd1dd9f8e1c50051c069122e6853bc878"
|
sha256: "16ed7b077ef01ad6170a3d0c57caa4a112a38d7a2ed5602e0aca9ca6f3d98da6"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.0"
|
version: "2.1.3"
|
||||||
ffigen:
|
ffigen:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
@@ -530,15 +530,6 @@ packages:
|
|||||||
url: "https://github.com/rustdesk-org/flutter_gpu_texture_renderer"
|
url: "https://github.com/rustdesk-org/flutter_gpu_texture_renderer"
|
||||||
source: git
|
source: git
|
||||||
version: "0.0.1"
|
version: "0.0.1"
|
||||||
flutter_improved_scrolling:
|
|
||||||
dependency: "direct main"
|
|
||||||
description:
|
|
||||||
path: "."
|
|
||||||
ref: HEAD
|
|
||||||
resolved-ref: "62f09545149f320616467c306c8c5f71714a18e6"
|
|
||||||
url: "https://github.com/rustdesk-org/flutter_improved_scrolling"
|
|
||||||
source: git
|
|
||||||
version: "0.0.3"
|
|
||||||
flutter_keyboard_visibility:
|
flutter_keyboard_visibility:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -745,10 +736,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: image_picker
|
name: image_picker
|
||||||
sha256: b6951e25b795d053a6ba03af5f710069c99349de9341af95155d52665cb4607c
|
sha256: "021834d9c0c3de46bf0fe40341fa07168407f694d9b2bb18d532dc1261867f7a"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.8.9"
|
version: "1.1.2"
|
||||||
image_picker_android:
|
image_picker_android:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -793,10 +784,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: image_picker_platform_interface
|
name: image_picker_platform_interface
|
||||||
sha256: fa4e815e6fcada50e35718727d83ba1c92f1edf95c0b4436554cec301b56233b
|
sha256: "9ec26d410ff46f483c5519c29c02ef0e02e13a543f882b152d4bfd2f06802f80"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.9.3"
|
version: "2.10.0"
|
||||||
image_picker_windows:
|
image_picker_windows:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -865,18 +856,18 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: material_color_utilities
|
name: material_color_utilities
|
||||||
sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a"
|
sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.8.0"
|
version: "0.11.1"
|
||||||
meta:
|
meta:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: meta
|
name: meta
|
||||||
sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04
|
sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.11.0"
|
version: "1.15.0"
|
||||||
mime:
|
mime:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -1037,14 +1028,6 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.8"
|
version: "2.1.8"
|
||||||
pointycastle:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: pointycastle
|
|
||||||
sha256: "43ac87de6e10afabc85c445745a7b799e04de84cebaa4fd7bf55a5e1e9604d29"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "3.7.4"
|
|
||||||
pool:
|
pool:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -1285,10 +1268,11 @@ packages:
|
|||||||
texture_rgba_renderer:
|
texture_rgba_renderer:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: texture_rgba_renderer
|
path: "."
|
||||||
sha256: cb048abdd800468ca40749ca10d1db9d1e6a055d1cde6234c05191293f0c7d61
|
ref: "42797e0f03141dc2b585f76c64a13974508058b4"
|
||||||
url: "https://pub.dev"
|
resolved-ref: "42797e0f03141dc2b585f76c64a13974508058b4"
|
||||||
source: hosted
|
url: "https://github.com/rustdesk-org/flutter_texture_rgba_renderer"
|
||||||
|
source: git
|
||||||
version: "0.0.16"
|
version: "0.0.16"
|
||||||
timing:
|
timing:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
@@ -1325,10 +1309,11 @@ packages:
|
|||||||
uni_links:
|
uni_links:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: uni_links
|
path: uni_links
|
||||||
sha256: "051098acfc9e26a9fde03b487bef5d3d228ca8f67693480c6f33fd4fbb8e2b6e"
|
ref: f416118d843a7e9ed117c7bb7bdc2deda5a9e86f
|
||||||
url: "https://pub.dev"
|
resolved-ref: f416118d843a7e9ed117c7bb7bdc2deda5a9e86f
|
||||||
source: hosted
|
url: "https://github.com/rustdesk-org/uni_links"
|
||||||
|
source: git
|
||||||
version: "0.5.1"
|
version: "0.5.1"
|
||||||
uni_links_desktop:
|
uni_links_desktop:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
@@ -1366,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:
|
||||||
@@ -1558,24 +1543,24 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: win32
|
name: win32
|
||||||
sha256: "464f5674532865248444b4c3daca12bd9bf2d7c47f759ce2617986e7229494a8"
|
sha256: "68d1e89a91ed61ad9c370f9f8b6effed9ae5e0ede22a270bdfa6daf79fc2290a"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "5.2.0"
|
version: "5.5.4"
|
||||||
win32_registry:
|
win32_registry:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: win32_registry
|
name: win32_registry
|
||||||
sha256: "41fd8a189940d8696b1b810efb9abcf60827b6cbfab90b0c43e8439e3a39d85a"
|
sha256: "21ec76dfc731550fd3e2ce7a33a9ea90b828fdf19a5c3bcf556fa992cfa99852"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.2"
|
version: "1.1.5"
|
||||||
window_manager:
|
window_manager:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
path: "."
|
path: "."
|
||||||
ref: HEAD
|
ref: HEAD
|
||||||
resolved-ref: f19acdb008645366339444a359a45c3257c8b32e
|
resolved-ref: "85789bfe6e4cfaf4ecc00c52857467fdb7f26879"
|
||||||
url: "https://github.com/rustdesk-org/window_manager"
|
url: "https://github.com/rustdesk-org/window_manager"
|
||||||
source: git
|
source: git
|
||||||
version: "0.3.6"
|
version: "0.3.6"
|
||||||
@@ -1583,8 +1568,8 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
path: "plugins/window_size"
|
path: "plugins/window_size"
|
||||||
ref: a738913c8ce2c9f47515382d40827e794a334274
|
ref: eb3964990cf19629c89ff8cb4a37640c7b3d5601
|
||||||
resolved-ref: a738913c8ce2c9f47515382d40827e794a334274
|
resolved-ref: eb3964990cf19629c89ff8cb4a37640c7b3d5601
|
||||||
url: "https://github.com/google/flutter-desktop-embedding.git"
|
url: "https://github.com/google/flutter-desktop-embedding.git"
|
||||||
source: git
|
source: git
|
||||||
version: "0.1.0"
|
version: "0.1.0"
|
||||||
@@ -1629,5 +1614,5 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "0.2.1"
|
version: "0.2.1"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=3.3.0 <4.0.0"
|
dart: ">=3.5.0 <4.0.0"
|
||||||
flutter: ">=3.19.0"
|
flutter: ">=3.24.0"
|
||||||
|
|||||||
@@ -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.2+51
|
version: 1.3.7+56
|
||||||
|
|
||||||
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:
|
||||||
@@ -46,7 +47,7 @@ dependencies:
|
|||||||
http: ^1.1.0
|
http: ^1.1.0
|
||||||
qr_code_scanner: ^1.0.0
|
qr_code_scanner: ^1.0.0
|
||||||
zxing2: ^0.2.0
|
zxing2: ^0.2.0
|
||||||
image_picker: ^0.8.5
|
image_picker: ^1.1.2
|
||||||
image: ^4.0.17
|
image: ^4.0.17
|
||||||
back_button_interceptor: ^6.0.1
|
back_button_interceptor: ^6.0.1
|
||||||
flutter_rust_bridge: "1.80.1"
|
flutter_rust_bridge: "1.80.1"
|
||||||
@@ -62,7 +63,7 @@ dependencies:
|
|||||||
git:
|
git:
|
||||||
url: https://github.com/google/flutter-desktop-embedding.git
|
url: https://github.com/google/flutter-desktop-embedding.git
|
||||||
path: plugins/window_size
|
path: plugins/window_size
|
||||||
ref: a738913c8ce2c9f47515382d40827e794a334274
|
ref: eb3964990cf19629c89ff8cb4a37640c7b3d5601
|
||||||
get: ^4.6.5
|
get: ^4.6.5
|
||||||
visibility_detector: ^0.4.0+2
|
visibility_detector: ^0.4.0+2
|
||||||
contextmenu: ^3.0.0
|
contextmenu: ^3.0.0
|
||||||
@@ -71,14 +72,11 @@ dependencies:
|
|||||||
debounce_throttle: ^2.0.0
|
debounce_throttle: ^2.0.0
|
||||||
file_picker: ^5.1.0
|
file_picker: ^5.1.0
|
||||||
flutter_svg: ^2.0.5
|
flutter_svg: ^2.0.5
|
||||||
flutter_improved_scrolling:
|
uni_links:
|
||||||
# currently, we use flutter 3.10.0+.
|
|
||||||
#
|
|
||||||
# for flutter 3.0.5, please use official version(just comment code below).
|
|
||||||
# if build rustdesk by flutter >=3.3, please use our custom pub below (uncomment code below).
|
|
||||||
git:
|
git:
|
||||||
url: https://github.com/rustdesk-org/flutter_improved_scrolling
|
url: https://github.com/rustdesk-org/uni_links
|
||||||
uni_links: ^0.5.1
|
path: uni_links
|
||||||
|
ref: f416118d843a7e9ed117c7bb7bdc2deda5a9e86f
|
||||||
uni_links_desktop: ^0.1.6 # use 0.1.6 to make flutter 3.13 works
|
uni_links_desktop: ^0.1.6 # use 0.1.6 to make flutter 3.13 works
|
||||||
path: ^1.8.1
|
path: ^1.8.1
|
||||||
auto_size_text: ^3.0.0
|
auto_size_text: ^3.0.0
|
||||||
@@ -87,7 +85,10 @@ dependencies:
|
|||||||
password_strength: ^0.2.0
|
password_strength: ^0.2.0
|
||||||
flutter_launcher_icons: ^0.13.1
|
flutter_launcher_icons: ^0.13.1
|
||||||
flutter_keyboard_visibility: ^5.4.0
|
flutter_keyboard_visibility: ^5.4.0
|
||||||
texture_rgba_renderer: ^0.0.16
|
texture_rgba_renderer:
|
||||||
|
git:
|
||||||
|
url: https://github.com/rustdesk-org/flutter_texture_rgba_renderer
|
||||||
|
ref: 42797e0f03141dc2b585f76c64a13974508058b4
|
||||||
percent_indicator: ^4.2.2
|
percent_indicator: ^4.2.2
|
||||||
dropdown_button2: ^2.0.0
|
dropdown_button2: ^2.0.0
|
||||||
flutter_gpu_texture_renderer:
|
flutter_gpu_texture_renderer:
|
||||||
@@ -104,7 +105,7 @@ dependencies:
|
|||||||
pull_down_button: ^0.9.3
|
pull_down_button: ^0.9.3
|
||||||
device_info_plus: ^9.1.0
|
device_info_plus: ^9.1.0
|
||||||
qr_flutter: ^4.1.0
|
qr_flutter: ^4.1.0
|
||||||
extended_text: 13.0.0
|
extended_text: 14.0.0
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
icons_launcher: ^2.0.4
|
icons_launcher: ^2.0.4
|
||||||
@@ -186,3 +187,4 @@ flutter:
|
|||||||
#
|
#
|
||||||
# For details regarding fonts from package dependencies,
|
# For details regarding fonts from package dependencies,
|
||||||
# see https://flutter.dev/custom-fonts/#from-packages
|
# see https://flutter.dev/custom-fonts/#from-packages
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ use std::{
|
|||||||
fs::File,
|
fs::File,
|
||||||
io::{BufRead, BufReader, Read, Seek},
|
io::{BufRead, BufReader, Read, Seek},
|
||||||
os::unix::prelude::PermissionsExt,
|
os::unix::prelude::PermissionsExt,
|
||||||
path::PathBuf,
|
path::{Path, PathBuf},
|
||||||
sync::atomic::{AtomicU64, Ordering},
|
sync::atomic::{AtomicU64, Ordering},
|
||||||
time::SystemTime,
|
time::SystemTime,
|
||||||
};
|
};
|
||||||
@@ -51,7 +51,7 @@ pub(super) struct LocalFile {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl LocalFile {
|
impl LocalFile {
|
||||||
pub fn try_open(path: &PathBuf) -> Result<Self, CliprdrError> {
|
pub fn try_open(path: &Path) -> Result<Self, CliprdrError> {
|
||||||
let mt = std::fs::metadata(path).map_err(|e| CliprdrError::FileError {
|
let mt = std::fs::metadata(path).map_err(|e| CliprdrError::FileError {
|
||||||
path: path.clone(),
|
path: path.clone(),
|
||||||
err: e,
|
err: e,
|
||||||
@@ -219,7 +219,7 @@ impl LocalFile {
|
|||||||
|
|
||||||
pub(super) fn construct_file_list(paths: &[PathBuf]) -> Result<Vec<LocalFile>, CliprdrError> {
|
pub(super) fn construct_file_list(paths: &[PathBuf]) -> Result<Vec<LocalFile>, CliprdrError> {
|
||||||
fn constr_file_lst(
|
fn constr_file_lst(
|
||||||
path: &PathBuf,
|
path: &Path,
|
||||||
file_list: &mut Vec<LocalFile>,
|
file_list: &mut Vec<LocalFile>,
|
||||||
visited: &mut HashSet<PathBuf>,
|
visited: &mut HashSet<PathBuf>,
|
||||||
) -> Result<(), CliprdrError> {
|
) -> Result<(), CliprdrError> {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use std::{
|
use std::{
|
||||||
path::PathBuf,
|
path::{Path, PathBuf},
|
||||||
sync::{mpsc::Sender, Arc},
|
sync::{mpsc::Sender, Arc},
|
||||||
time::Duration,
|
time::Duration,
|
||||||
};
|
};
|
||||||
@@ -74,7 +74,7 @@ trait SysClipboard: Send + Sync {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
fn get_sys_clipboard(ignore_path: &PathBuf) -> Result<Box<dyn SysClipboard>, CliprdrError> {
|
fn get_sys_clipboard(ignore_path: &Path) -> Result<Box<dyn SysClipboard>, CliprdrError> {
|
||||||
#[cfg(feature = "wayland")]
|
#[cfg(feature = "wayland")]
|
||||||
{
|
{
|
||||||
unimplemented!()
|
unimplemented!()
|
||||||
@@ -88,7 +88,7 @@ fn get_sys_clipboard(ignore_path: &PathBuf) -> Result<Box<dyn SysClipboard>, Cli
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
fn get_sys_clipboard(ignore_path: &PathBuf) -> Result<Box<dyn SysClipboard>, CliprdrError> {
|
fn get_sys_clipboard(ignore_path: &Path) -> Result<Box<dyn SysClipboard>, CliprdrError> {
|
||||||
use ns_clipboard::*;
|
use ns_clipboard::*;
|
||||||
let ns_pb = NsPasteboard::new(ignore_path)?;
|
let ns_pb = NsPasteboard::new(ignore_path)?;
|
||||||
Ok(Box::new(ns_pb) as Box<_>)
|
Ok(Box::new(ns_pb) as Box<_>)
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
use std::{collections::BTreeSet, path::PathBuf};
|
use std::{
|
||||||
|
collections::BTreeSet,
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
};
|
||||||
|
|
||||||
use cacao::pasteboard::{Pasteboard, PasteboardName};
|
use cacao::pasteboard::{Pasteboard, PasteboardName};
|
||||||
use hbb_common::log;
|
use hbb_common::log;
|
||||||
@@ -30,7 +33,7 @@ pub struct NsPasteboard {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl NsPasteboard {
|
impl NsPasteboard {
|
||||||
pub fn new(ignore_path: &PathBuf) -> Result<Self, CliprdrError> {
|
pub fn new(ignore_path: &Path) -> Result<Self, CliprdrError> {
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
ignore_path: ignore_path.to_owned(),
|
ignore_path: ignore_path.to_owned(),
|
||||||
former_file_list: Mutex::new(vec![]),
|
former_file_list: Mutex::new(vec![]),
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ use crate::CliprdrError;
|
|||||||
// url encode and decode is needed
|
// url encode and decode is needed
|
||||||
const ENCODE_SET: percent_encoding::AsciiSet = percent_encoding::CONTROLS.add(b' ').remove(b'/');
|
const ENCODE_SET: percent_encoding::AsciiSet = percent_encoding::CONTROLS.add(b' ').remove(b'/');
|
||||||
|
|
||||||
pub(super) fn encode_path_to_uri(path: &PathBuf) -> io::Result<String> {
|
pub(super) fn encode_path_to_uri(path: &Path) -> io::Result<String> {
|
||||||
let encoded =
|
let encoded =
|
||||||
percent_encoding::percent_encode(path.to_str()?.as_bytes(), &ENCODE_SET).to_string();
|
percent_encoding::percent_encode(path.to_str()?.as_bytes(), &ENCODE_SET).to_string();
|
||||||
format!("file://{}", encoded)
|
format!("file://{}", encoded)
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
use std::{collections::BTreeSet, path::PathBuf};
|
use std::{
|
||||||
|
collections::BTreeSet,
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
};
|
||||||
|
|
||||||
use hbb_common::log;
|
use hbb_common::log;
|
||||||
use once_cell::sync::OnceCell;
|
use once_cell::sync::OnceCell;
|
||||||
@@ -26,7 +29,7 @@ pub struct X11Clipboard {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl X11Clipboard {
|
impl X11Clipboard {
|
||||||
pub fn new(ignore_path: &PathBuf) -> Result<Self, CliprdrError> {
|
pub fn new(ignore_path: &Path) -> Result<Self, CliprdrError> {
|
||||||
let clipboard = get_clip()?;
|
let clipboard = get_clip()?;
|
||||||
let text_uri_list = clipboard
|
let text_uri_list = clipboard
|
||||||
.setter
|
.setter
|
||||||
|
|||||||
@@ -211,6 +211,11 @@ struct wf_clipboard
|
|||||||
BOOL sync;
|
BOOL sync;
|
||||||
UINT32 capabilities;
|
UINT32 capabilities;
|
||||||
|
|
||||||
|
// This flag is not really needed,
|
||||||
|
// but we can use it to double confirm that files can only be pasted after `Ctrl+C`.
|
||||||
|
// Not sure `is_file_descriptor_from_remote()` is engough to check all cases on all Windows.
|
||||||
|
BOOL copied;
|
||||||
|
|
||||||
size_t map_size;
|
size_t map_size;
|
||||||
size_t map_capacity;
|
size_t map_capacity;
|
||||||
formatMapping *format_mappings;
|
formatMapping *format_mappings;
|
||||||
@@ -263,6 +268,8 @@ static UINT cliprdr_send_request_filecontents(wfClipboard *clipboard, UINT32 con
|
|||||||
ULONG index, UINT32 flag, DWORD positionhigh,
|
ULONG index, UINT32 flag, DWORD positionhigh,
|
||||||
DWORD positionlow, ULONG request);
|
DWORD positionlow, ULONG request);
|
||||||
|
|
||||||
|
static BOOL is_file_descriptor_from_remote();
|
||||||
|
|
||||||
static void CliprdrDataObject_Delete(CliprdrDataObject *instance);
|
static void CliprdrDataObject_Delete(CliprdrDataObject *instance);
|
||||||
|
|
||||||
static CliprdrEnumFORMATETC *CliprdrEnumFORMATETC_New(ULONG nFormats, FORMATETC *pFormatEtc);
|
static CliprdrEnumFORMATETC *CliprdrEnumFORMATETC_New(ULONG nFormats, FORMATETC *pFormatEtc);
|
||||||
@@ -712,6 +719,15 @@ static HRESULT STDMETHODCALLTYPE CliprdrDataObject_GetData(IDataObject *This, FO
|
|||||||
if (!clipboard)
|
if (!clipboard)
|
||||||
return E_INVALIDARG;
|
return E_INVALIDARG;
|
||||||
|
|
||||||
|
// If `Ctrl+C` is not pressed yet, do not handle the file paste, and empty the clipboard.
|
||||||
|
if (!clipboard->copied) {
|
||||||
|
if (try_open_clipboard(clipboard->hwnd)) {
|
||||||
|
EmptyClipboard();
|
||||||
|
CloseClipboard();
|
||||||
|
}
|
||||||
|
return E_UNEXPECTED;
|
||||||
|
}
|
||||||
|
|
||||||
if ((idx = cliprdr_lookup_format(instance, pFormatEtc)) == -1)
|
if ((idx = cliprdr_lookup_format(instance, pFormatEtc)) == -1)
|
||||||
{
|
{
|
||||||
// empty clipboard here?
|
// empty clipboard here?
|
||||||
@@ -1479,6 +1495,8 @@ static UINT cliprdr_send_format_list(wfClipboard *clipboard, UINT32 connID)
|
|||||||
|
|
||||||
// send
|
// send
|
||||||
rc = clipboard->context->ClientFormatList(clipboard->context, &formatList);
|
rc = clipboard->context->ClientFormatList(clipboard->context, &formatList);
|
||||||
|
// No need to check `rc`, `copied` is only used to indicate `Ctrl+C` is pressed.
|
||||||
|
clipboard->copied = TRUE;
|
||||||
|
|
||||||
for (index = 0; index < numFormats; index++)
|
for (index = 0; index < numFormats; index++)
|
||||||
{
|
{
|
||||||
@@ -2274,7 +2292,9 @@ static UINT wf_cliprdr_monitor_ready(CliprdrClientContext *context,
|
|||||||
if (rc != CHANNEL_RC_OK)
|
if (rc != CHANNEL_RC_OK)
|
||||||
return rc;
|
return rc;
|
||||||
|
|
||||||
return cliprdr_send_format_list(clipboard, monitorReady->connID);
|
return rc;
|
||||||
|
// Don't send format list here, because we don't want to paste files copied before the connection.
|
||||||
|
// return cliprdr_send_format_list(clipboard, monitorReady->connID);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -2321,11 +2341,20 @@ static UINT wf_cliprdr_server_format_list(CliprdrClientContext *context,
|
|||||||
UINT32 i;
|
UINT32 i;
|
||||||
formatMapping *mapping;
|
formatMapping *mapping;
|
||||||
CLIPRDR_FORMAT *format;
|
CLIPRDR_FORMAT *format;
|
||||||
wfClipboard *clipboard = (wfClipboard *)context->Custom;
|
wfClipboard *clipboard = NULL;
|
||||||
|
|
||||||
|
if (!context || !formatList)
|
||||||
|
return ERROR_INTERNAL_ERROR;
|
||||||
|
|
||||||
|
clipboard = (wfClipboard *)context->Custom;
|
||||||
|
if (!clipboard)
|
||||||
|
return ERROR_INTERNAL_ERROR;
|
||||||
|
|
||||||
if (!clear_format_map(clipboard))
|
if (!clear_format_map(clipboard))
|
||||||
return ERROR_INTERNAL_ERROR;
|
return ERROR_INTERNAL_ERROR;
|
||||||
|
|
||||||
|
clipboard->copied = TRUE;
|
||||||
|
|
||||||
for (i = 0; i < formatList->numFormats; i++)
|
for (i = 0; i < formatList->numFormats; i++)
|
||||||
{
|
{
|
||||||
format = &(formatList->formats[i]);
|
format = &(formatList->formats[i]);
|
||||||
@@ -3060,6 +3089,19 @@ wf_cliprdr_server_file_contents_response(CliprdrClientContext *context,
|
|||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BOOL is_file_descriptor_from_remote()
|
||||||
|
{
|
||||||
|
UINT fsid = 0;
|
||||||
|
if (IsClipboardFormatAvailable(CF_HDROP)) {
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
fsid = RegisterClipboardFormat(CFSTR_FILEDESCRIPTORW);
|
||||||
|
if (IsClipboardFormatAvailable(fsid)) {
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
BOOL wf_cliprdr_init(wfClipboard *clipboard, CliprdrClientContext *cliprdr)
|
BOOL wf_cliprdr_init(wfClipboard *clipboard, CliprdrClientContext *cliprdr)
|
||||||
{
|
{
|
||||||
if (!clipboard || !cliprdr)
|
if (!clipboard || !cliprdr)
|
||||||
@@ -3071,6 +3113,7 @@ BOOL wf_cliprdr_init(wfClipboard *clipboard, CliprdrClientContext *cliprdr)
|
|||||||
clipboard->map_size = 0;
|
clipboard->map_size = 0;
|
||||||
clipboard->hUser32 = LoadLibraryA("user32.dll");
|
clipboard->hUser32 = LoadLibraryA("user32.dll");
|
||||||
clipboard->data_obj = NULL;
|
clipboard->data_obj = NULL;
|
||||||
|
clipboard->copied = FALSE;
|
||||||
|
|
||||||
if (clipboard->hUser32)
|
if (clipboard->hUser32)
|
||||||
{
|
{
|
||||||
@@ -3126,14 +3169,18 @@ BOOL wf_cliprdr_uninit(wfClipboard *clipboard, CliprdrClientContext *cliprdr)
|
|||||||
if (!clipboard || !cliprdr)
|
if (!clipboard || !cliprdr)
|
||||||
return FALSE;
|
return FALSE;
|
||||||
|
|
||||||
|
clipboard->copied = FALSE;
|
||||||
cliprdr->Custom = NULL;
|
cliprdr->Custom = NULL;
|
||||||
|
|
||||||
/* discard all contexts in clipboard */
|
/* discard all contexts in clipboard */
|
||||||
if (try_open_clipboard(clipboard->hwnd))
|
if (try_open_clipboard(clipboard->hwnd))
|
||||||
{
|
{
|
||||||
if (!EmptyClipboard())
|
if (is_file_descriptor_from_remote())
|
||||||
{
|
{
|
||||||
DEBUG_CLIPRDR("EmptyClipboard failed with 0x%x", GetLastError());
|
if (!EmptyClipboard())
|
||||||
|
{
|
||||||
|
DEBUG_CLIPRDR("EmptyClipboard failed with 0x%x", GetLastError());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (!CloseClipboard())
|
if (!CloseClipboard())
|
||||||
{
|
{
|
||||||
@@ -3227,6 +3274,8 @@ BOOL wf_do_empty_cliprdr(wfClipboard *clipboard)
|
|||||||
return FALSE;
|
return FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
clipboard->copied = FALSE;
|
||||||
|
|
||||||
if (WaitForSingleObject(clipboard->data_obj_mutex, INFINITE) != WAIT_OBJECT_0)
|
if (WaitForSingleObject(clipboard->data_obj_mutex, INFINITE) != WAIT_OBJECT_0)
|
||||||
{
|
{
|
||||||
return FALSE;
|
return FALSE;
|
||||||
@@ -3248,10 +3297,14 @@ BOOL wf_do_empty_cliprdr(wfClipboard *clipboard)
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!EmptyClipboard())
|
if (is_file_descriptor_from_remote())
|
||||||
{
|
{
|
||||||
rc = FALSE;
|
if (!EmptyClipboard())
|
||||||
|
{
|
||||||
|
rc = FALSE;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!CloseClipboard())
|
if (!CloseClipboard())
|
||||||
{
|
{
|
||||||
// critical error!!!
|
// critical error!!!
|
||||||
|
|||||||
@@ -345,7 +345,7 @@ fn convert_to_tfc_key(key: Key) -> Option<TFC_Key> {
|
|||||||
Key::Numpad9 => TFC_Key::N9,
|
Key::Numpad9 => TFC_Key::N9,
|
||||||
Key::Decimal => TFC_Key::NumpadDecimal,
|
Key::Decimal => TFC_Key::NumpadDecimal,
|
||||||
Key::Clear => TFC_Key::NumpadClear,
|
Key::Clear => TFC_Key::NumpadClear,
|
||||||
Key::Pause => TFC_Key::PlayPause,
|
Key::Pause => TFC_Key::Pause,
|
||||||
Key::Print => TFC_Key::Print,
|
Key::Print => TFC_Key::Print,
|
||||||
Key::Snapshot => TFC_Key::PrintScreen,
|
Key::Snapshot => TFC_Key::PrintScreen,
|
||||||
Key::Insert => TFC_Key::Insert,
|
Key::Insert => TFC_Key::Insert,
|
||||||
|
|||||||
1
libs/hbb_common
Submodule
1
libs/hbb_common
Submodule
Submodule libs/hbb_common added at 49c6b24a7a
3
libs/hbb_common/.gitignore
vendored
3
libs/hbb_common/.gitignore
vendored
@@ -1,3 +0,0 @@
|
|||||||
/target
|
|
||||||
**/*.rs.bk
|
|
||||||
Cargo.lock
|
|
||||||
@@ -1,65 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "hbb_common"
|
|
||||||
version = "0.1.0"
|
|
||||||
authors = ["open-trade <info@opentradesolutions.com>"]
|
|
||||||
edition = "2018"
|
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
flexi_logger = { version = "0.27", features = ["async"] }
|
|
||||||
protobuf = { version = "3.4", features = ["with-bytes"] }
|
|
||||||
tokio = { version = "1.38", features = ["full"] }
|
|
||||||
tokio-util = { version = "0.7", features = ["full"] }
|
|
||||||
futures = "0.3"
|
|
||||||
bytes = { version = "1.6", features = ["serde"] }
|
|
||||||
log = "0.4"
|
|
||||||
env_logger = "0.10"
|
|
||||||
socket2 = { version = "0.3", features = ["reuseport"] }
|
|
||||||
zstd = "0.13"
|
|
||||||
anyhow = "1.0"
|
|
||||||
futures-util = "0.3"
|
|
||||||
directories-next = "2.0"
|
|
||||||
rand = "0.8"
|
|
||||||
serde_derive = "1.0"
|
|
||||||
serde = "1.0"
|
|
||||||
serde_json = "1.0"
|
|
||||||
lazy_static = "1.4"
|
|
||||||
confy = { git = "https://github.com/rustdesk-org/confy" }
|
|
||||||
dirs-next = "2.0"
|
|
||||||
filetime = "0.2"
|
|
||||||
sodiumoxide = "0.2"
|
|
||||||
regex = "1.8"
|
|
||||||
tokio-socks = { git = "https://github.com/rustdesk-org/tokio-socks" }
|
|
||||||
chrono = "0.4"
|
|
||||||
backtrace = "0.3"
|
|
||||||
libc = "0.2"
|
|
||||||
dlopen = "0.1"
|
|
||||||
toml = "0.7"
|
|
||||||
uuid = { version = "1.3", features = ["v4"] }
|
|
||||||
# new sysinfo issue: https://github.com/rustdesk/rustdesk/pull/6330#issuecomment-2270871442
|
|
||||||
sysinfo = { git = "https://github.com/rustdesk-org/sysinfo", branch = "rlim_max" }
|
|
||||||
thiserror = "1.0"
|
|
||||||
httparse = "1.5"
|
|
||||||
base64 = "0.22"
|
|
||||||
url = "2.2"
|
|
||||||
|
|
||||||
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
|
|
||||||
mac_address = "1.1"
|
|
||||||
machine-uid = { git = "https://github.com/rustdesk-org/machine-uid" }
|
|
||||||
[target.'cfg(not(any(target_os = "macos", target_os = "windows")))'.dependencies]
|
|
||||||
tokio-rustls = { version = "0.26", features = ["logging", "tls12", "ring"], default-features = false }
|
|
||||||
rustls-platform-verifier = "0.3.1"
|
|
||||||
rustls-pki-types = "1.4"
|
|
||||||
[target.'cfg(any(target_os = "macos", target_os = "windows"))'.dependencies]
|
|
||||||
tokio-native-tls ="0.3"
|
|
||||||
|
|
||||||
[build-dependencies]
|
|
||||||
protobuf-codegen = { version = "3.4" }
|
|
||||||
|
|
||||||
[target.'cfg(target_os = "windows")'.dependencies]
|
|
||||||
winapi = { version = "0.3", features = ["winuser", "synchapi", "pdh", "memoryapi", "sysinfoapi"] }
|
|
||||||
|
|
||||||
[target.'cfg(target_os = "macos")'.dependencies]
|
|
||||||
osascript = "0.3"
|
|
||||||
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
fn main() {
|
|
||||||
let out_dir = format!("{}/protos", std::env::var("OUT_DIR").unwrap());
|
|
||||||
|
|
||||||
std::fs::create_dir_all(&out_dir).unwrap();
|
|
||||||
|
|
||||||
protobuf_codegen::Codegen::new()
|
|
||||||
.pure()
|
|
||||||
.out_dir(out_dir)
|
|
||||||
.inputs(["protos/rendezvous.proto", "protos/message.proto"])
|
|
||||||
.include("protos")
|
|
||||||
.customize(protobuf_codegen::Customize::default().tokio_bytes(true))
|
|
||||||
.run()
|
|
||||||
.expect("Codegen failed.");
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user